ClassFileInstaller.java revision 2224:2a8815d86b93
1/*
2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24import java.io.ByteArrayInputStream;
25import java.io.File;
26import java.io.FileInputStream;
27import java.io.FileOutputStream;
28import java.io.FileNotFoundException;
29import java.io.InputStream;
30import java.io.ByteArrayInputStream;
31import java.nio.file.Files;
32import java.nio.file.Path;
33import java.nio.file.Paths;
34import java.nio.file.StandardCopyOption;
35import java.util.zip.ZipEntry;
36import java.util.zip.ZipOutputStream;
37
38/**
39 * Dump a class file for a class on the class path in the current directory, or
40 * in the specified JAR file. This class is usually used when you build a class
41 * from a test library, but want to use this class in a sub-process.
42 *
43 * For example, to build the following library class:
44 * test/lib/sun/hotspot/WhiteBox.java
45 *
46 * You would use the following tags:
47 *
48 * @library /test/lib
49 * @build sun.hotspot.WhiteBox
50 *
51 * JTREG would build the class file under
52 * ${JTWork}/classes/test/lib/sun/hotspot/WhiteBox.class
53 *
54 * With you run your main test class using "@run main MyMainClass", JTREG would setup the
55 * -classpath to include "${JTWork}/classes/test/lib/", so MyMainClass would be able to
56 * load the WhiteBox class.
57 *
58 * However, if you run a sub process, and do not wish to use the exact same -classpath,
59 * You can use ClassFileInstaller to ensure that WhiteBox is available in the current
60 * directory of your test:
61 *
62 * @run main ClassFileInstaller sun.hotspot.WhiteBox
63 *
64 * Or, you can use the -jar option to store the class in the specified JAR file. If a relative
65 * path name is given, the JAR file would be relative to the current directory of
66 *
67 * @run main ClassFileInstaller -jar myjar.jar sun.hotspot.WhiteBox
68 */
69public class ClassFileInstaller {
70    /**
71     * You can enable debug tracing of ClassFileInstaller by running JTREG with
72     * jtreg -DClassFileInstaller.debug=true ... <names of tests>
73     */
74    public static boolean DEBUG = Boolean.getBoolean("ClassFileInstaller.debug");
75
76    /**
77     * @param args The names of the classes to dump
78     * @throws Exception
79     */
80    public static void main(String... args) throws Exception {
81        if (args.length > 1 && args[0].equals("-jar")) {
82            if (args.length < 2) {
83                throw new RuntimeException("Usage: ClassFileInstaller <options> <classes>\n" +
84                                           "where possible options include:\n" +
85                                           "  -jar <path>             Write to the JAR file <path>");
86            }
87            writeJar(args[1], null, args, 2, args.length);
88        } else {
89            if (DEBUG) {
90                System.out.println("ClassFileInstaller: Writing to " + System.getProperty("user.dir"));
91            }
92            for (String arg : args) {
93                writeClassToDisk(arg);
94            }
95        }
96    }
97
98    public static class Manifest {
99        private InputStream in;
100
101        private Manifest(InputStream in) {
102            this.in = in;
103        }
104
105        static Manifest fromSourceFile(String fileName) throws Exception {
106            String pathName = System.getProperty("test.src") + File.separator + fileName;
107            return new Manifest(new FileInputStream(pathName));
108        }
109
110        // Example:
111        //  String manifest = "Premain-Class: RedefineClassHelper\n" +
112        //                "Can-Redefine-Classes: true\n";
113        //  ClassFileInstaller.writeJar("redefineagent.jar",
114        //    ClassFileInstaller.Manifest.fromString(manifest),
115        //    "RedefineClassHelper");
116        static Manifest fromString(String manifest) throws Exception {
117            return new Manifest(new ByteArrayInputStream(manifest.getBytes()));
118        }
119
120        public InputStream getInputStream() {
121            return in;
122        }
123    }
124
125    private static void writeJar(String jarFile, Manifest manifest, String classes[], int from, int to) throws Exception {
126        if (DEBUG) {
127            System.out.println("ClassFileInstaller: Writing to " + getJarPath(jarFile));
128        }
129
130        (new File(jarFile)).delete();
131        FileOutputStream fos = new FileOutputStream(jarFile);
132        ZipOutputStream zos = new ZipOutputStream(fos);
133
134        // The manifest must be the first or second entry. See comments in JarInputStream
135        // constructor and JDK-5046178.
136        if (manifest != null) {
137            writeToDisk(zos, "META-INF/MANIFEST.MF", manifest.getInputStream());
138        }
139
140        for (int i=from; i<to; i++) {
141            writeClassToDisk(zos, classes[i]);
142        }
143
144        zos.close();
145        fos.close();
146    }
147
148    /*
149     * You can call ClassFileInstaller.writeJar() from your main test class instead of
150     * using "@run ClassFileInstaller -jar ...". E.g.,
151     *
152     * String jarPath = ClassFileInstaller.getJarPath("myjar.jar", "sun.hotspot.WhiteBox")
153     *
154     * If you call this API, make sure you build ClassFileInstaller with the following tags:
155     *
156     * @library testlibrary
157     * @build ClassFileInstaller
158     */
159    public static String writeJar(String jarFile, String... classes) throws Exception {
160        writeJar(jarFile, null, classes, 0, classes.length);
161        return getJarPath(jarFile);
162    }
163
164    public static String writeJar(String jarFile, Manifest manifest, String... classes) throws Exception {
165        writeJar(jarFile, manifest, classes, 0, classes.length);
166        return getJarPath(jarFile);
167    }
168
169    /**
170     * This returns the absolute path to the file specified in "@ClassFileInstaller -jar myjar.jar",
171     * In your test program, instead of using the JAR file name directly:
172     *
173     * String jarPath = "myjar.jar";
174     *
175     * you should call this function, like:
176     *
177     * String jarPath = ClassFileInstaller.getJarPath("myjar.jar")
178     *
179     * The reasons are:
180     * (1) Using absolute path makes it easy to cut-and-paste from the JTR file and rerun your
181     *     test in any directory.
182     * (2) In the future, we may make the JAR file name unique to avoid clobbering
183     *     during parallel JTREG execution.
184     *
185     */
186    public static String getJarPath(String jarFileName) {
187        return new File(jarFileName).getAbsolutePath();
188    }
189
190    public static void writeClassToDisk(String className) throws Exception {
191        writeClassToDisk((ZipOutputStream)null, className);
192    }
193    private static void writeClassToDisk(ZipOutputStream zos, String className) throws Exception {
194        writeClassToDisk(zos, className, "");
195    }
196
197    public static void writeClassToDisk(String className, String prependPath) throws Exception {
198        writeClassToDisk(null, className, prependPath);
199    }
200    private static void writeClassToDisk(ZipOutputStream zos, String className, String prependPath) throws Exception {
201        ClassLoader cl = ClassFileInstaller.class.getClassLoader();
202
203        // Convert dotted class name to a path to a class file
204        String pathName = className.replace('.', '/').concat(".class");
205        InputStream is = cl.getResourceAsStream(pathName);
206        if (is == null) {
207            throw new RuntimeException("Failed to find " + pathName);
208        }
209        if (prependPath.length() > 0) {
210            pathName = prependPath + "/" + pathName;
211        }
212        writeToDisk(zos, pathName, is);
213    }
214
215    public static void writeClassToDisk(String className, byte[] bytecode) throws Exception {
216        writeClassToDisk(null, className, bytecode);
217    }
218    private static void writeClassToDisk(ZipOutputStream zos, String className, byte[] bytecode) throws Exception {
219        writeClassToDisk(zos, className, bytecode, "");
220    }
221
222    public static void writeClassToDisk(String className, byte[] bytecode, String prependPath) throws Exception {
223        writeClassToDisk(null, className, bytecode, prependPath);
224    }
225    private static void writeClassToDisk(ZipOutputStream zos, String className, byte[] bytecode, String prependPath) throws Exception {
226        // Convert dotted class name to a path to a class file
227        String pathName = className.replace('.', '/').concat(".class");
228        if (prependPath.length() > 0) {
229            pathName = prependPath + "/" + pathName;
230        }
231        writeToDisk(zos, pathName, new ByteArrayInputStream(bytecode));
232    }
233
234    private static void writeToDisk(ZipOutputStream zos, String pathName, InputStream is) throws Exception {
235        if (DEBUG) {
236            System.out.println("ClassFileInstaller: Writing " + pathName);
237        }
238        if (zos != null) {
239            ZipEntry ze = new ZipEntry(pathName);
240            zos.putNextEntry(ze);
241            byte[] buf = new byte[1024];
242            int len;
243            while ((len = is.read(buf))>0){
244                zos.write(buf, 0, len);
245            }
246        } else {
247            // Create the class file's package directory
248            Path p = Paths.get(pathName);
249            if (pathName.contains("/")) {
250                Files.createDirectories(p.getParent());
251            }
252            // Create the class file
253            Files.copy(is, p, StandardCopyOption.REPLACE_EXISTING);
254        }
255        is.close();
256    }
257}
258