ClassFileInstaller.java 9.9 KB
Newer Older
1
/*
2
 * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

24 25 26 27 28
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
29
import java.io.InputStream;
30
import java.io.ByteArrayInputStream;
31 32 33 34
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
35 36
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
37 38

/**
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
 * Dump a class file for a class on the class path in the current directory, or
 * in the specified JAR file. This class is usually used when you build a class
 * from a test library, but want to use this class in a sub-process.
 *
 * For example, to build the following library class:
 * test/lib/sun/hotspot/WhiteBox.java
 *
 * You would use the following tags:
 *
 * @library /test/lib
 * @build sun.hotspot.WhiteBox
 *
 * JTREG would build the class file under
 * ${JTWork}/classes/test/lib/sun/hotspot/WhiteBox.class
 *
 * With you run your main test class using "@run main MyMainClass", JTREG would setup the
 * -classpath to include "${JTWork}/classes/test/lib/", so MyMainClass would be able to
 * load the WhiteBox class.
 *
 * However, if you run a sub process, and do not wish to use the exact same -classpath,
 * You can use ClassFileInstaller to ensure that WhiteBox is available in the current
 * directory of your test:
 *
 * @run main ClassFileInstaller sun.hotspot.WhiteBox
 *
 * Or, you can use the -jar option to store the class in the specified JAR file. If a relative
 * path name is given, the JAR file would be relative to the current directory of
 *
 * @run main ClassFileInstaller -jar myjar.jar sun.hotspot.WhiteBox
68 69
 */
public class ClassFileInstaller {
70 71 72 73 74 75
    /**
     * You can enable debug tracing of ClassFileInstaller by running JTREG with
     * jtreg -DClassFileInstaller.debug=true ... <names of tests>
     */
    public static boolean DEBUG = Boolean.getBoolean("ClassFileInstaller.debug");

76 77 78 79 80
    /**
     * @param args The names of the classes to dump
     * @throws Exception
     */
    public static void main(String... args) throws Exception {
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
        if (args.length > 1 && args[0].equals("-jar")) {
            if (args.length < 2) {
                throw new RuntimeException("Usage: ClassFileInstaller <options> <classes>\n" +
                                           "where possible options include:\n" +
                                           "  -jar <path>             Write to the JAR file <path>");
            }
            writeJar(args[1], null, args, 2, args.length);
        } else {
            if (DEBUG) {
                System.out.println("ClassFileInstaller: Writing to " + System.getProperty("user.dir"));
            }
            for (String arg : args) {
                writeClassToDisk(arg);
            }
        }
    }

    public static class Manifest {
        private InputStream in;

        private Manifest(InputStream in) {
            this.in = in;
        }

        static Manifest fromSourceFile(String fileName) throws Exception {
            String pathName = System.getProperty("test.src") + File.separator + fileName;
            return new Manifest(new FileInputStream(pathName));
        }

        // Example:
        //  String manifest = "Premain-Class: RedefineClassHelper\n" +
        //                "Can-Redefine-Classes: true\n";
        //  ClassFileInstaller.writeJar("redefineagent.jar",
        //    ClassFileInstaller.Manifest.fromString(manifest),
        //    "RedefineClassHelper");
        static Manifest fromString(String manifest) throws Exception {
            return new Manifest(new ByteArrayInputStream(manifest.getBytes()));
        }

        public InputStream getInputStream() {
            return in;
        }
    }

    private static void writeJar(String jarFile, Manifest manifest, String classes[], int from, int to) throws Exception {
        if (DEBUG) {
            System.out.println("ClassFileInstaller: Writing to " + getJarPath(jarFile));
        }

        (new File(jarFile)).delete();
        FileOutputStream fos = new FileOutputStream(jarFile);
        ZipOutputStream zos = new ZipOutputStream(fos);

        // The manifest must be the first or second entry. See comments in JarInputStream
        // constructor and JDK-5046178.
        if (manifest != null) {
            writeToDisk(zos, "META-INF/MANIFEST.MF", manifest.getInputStream());
        }

        for (int i=from; i<to; i++) {
            writeClassToDisk(zos, classes[i]);
        }

        zos.close();
        fos.close();
    }

    /*
     * You can call ClassFileInstaller.writeJar() from your main test class instead of
     * using "@run ClassFileInstaller -jar ...". E.g.,
     *
     * String jarPath = ClassFileInstaller.getJarPath("myjar.jar", "sun.hotspot.WhiteBox")
     *
     * If you call this API, make sure you build ClassFileInstaller with the following tags:
     *
     * @library testlibrary
     * @build ClassFileInstaller
     */
    public static String writeJar(String jarFile, String... classes) throws Exception {
        writeJar(jarFile, null, classes, 0, classes.length);
        return getJarPath(jarFile);
    }
163

164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
    public static String writeJar(String jarFile, Manifest manifest, String... classes) throws Exception {
        writeJar(jarFile, manifest, classes, 0, classes.length);
        return getJarPath(jarFile);
    }

    /**
     * This returns the absolute path to the file specified in "@ClassFileInstaller -jar myjar.jar",
     * In your test program, instead of using the JAR file name directly:
     *
     * String jarPath = "myjar.jar";
     *
     * you should call this function, like:
     *
     * String jarPath = ClassFileInstaller.getJarPath("myjar.jar")
     *
     * The reasons are:
     * (1) Using absolute path makes it easy to cut-and-paste from the JTR file and rerun your
     *     test in any directory.
     * (2) In the future, we may make the JAR file name unique to avoid clobbering
     *     during parallel JTREG execution.
     *
     */
    public static String getJarPath(String jarFileName) {
        return new File(jarFileName).getAbsolutePath();
    }

    public static void writeClassToDisk(String className) throws Exception {
        writeClassToDisk((ZipOutputStream)null, className);
    }
    private static void writeClassToDisk(ZipOutputStream zos, String className) throws Exception {
        writeClassToDisk(zos, className, "");
    }
196

197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
    public static void writeClassToDisk(String className, String prependPath) throws Exception {
        writeClassToDisk(null, className, prependPath);
    }
    private static void writeClassToDisk(ZipOutputStream zos, String className, String prependPath) throws Exception {
        ClassLoader cl = ClassFileInstaller.class.getClassLoader();

        // Convert dotted class name to a path to a class file
        String pathName = className.replace('.', '/').concat(".class");
        InputStream is = cl.getResourceAsStream(pathName);
        if (is == null) {
            throw new RuntimeException("Failed to find " + pathName);
        }
        if (prependPath.length() > 0) {
            pathName = prependPath + "/" + pathName;
        }
        writeToDisk(zos, pathName, is);
    }

    public static void writeClassToDisk(String className, byte[] bytecode) throws Exception {
        writeClassToDisk(null, className, bytecode);
    }
    private static void writeClassToDisk(ZipOutputStream zos, String className, byte[] bytecode) throws Exception {
        writeClassToDisk(zos, className, bytecode, "");
    }

    public static void writeClassToDisk(String className, byte[] bytecode, String prependPath) throws Exception {
        writeClassToDisk(null, className, bytecode, prependPath);
    }
    private static void writeClassToDisk(ZipOutputStream zos, String className, byte[] bytecode, String prependPath) throws Exception {
        // Convert dotted class name to a path to a class file
        String pathName = className.replace('.', '/').concat(".class");
        if (prependPath.length() > 0) {
            pathName = prependPath + "/" + pathName;
        }
        writeToDisk(zos, pathName, new ByteArrayInputStream(bytecode));
    }

    private static void writeToDisk(ZipOutputStream zos, String pathName, InputStream is) throws Exception {
        if (DEBUG) {
            System.out.println("ClassFileInstaller: Writing " + pathName);
        }
        if (zos != null) {
            ZipEntry ze = new ZipEntry(pathName);
            zos.putNextEntry(ze);
            byte[] buf = new byte[1024];
            int len;
            while ((len = is.read(buf))>0){
                zos.write(buf, 0, len);
            }
        } else {
247 248
            // Create the class file's package directory
            Path p = Paths.get(pathName);
249 250 251
            if (pathName.contains("/")) {
                Files.createDirectories(p.getParent());
            }
252 253 254
            // Create the class file
            Files.copy(is, p, StandardCopyOption.REPLACE_EXISTING);
        }
255
        is.close();
256 257
    }
}