aboutsummaryrefslogtreecommitdiff
path: root/test/lib/testlibrary/ClassFileInstaller.java
blob: 0f5b515a6c95a92f72fc8cb3631105a3f81fcc6e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
68
69
70
71
72
73
74
75
76
77
78
79
80
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
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
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
247
248
249
250
251
252
253
254
255
256
257
258
/*
 * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
 * 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.
 */

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 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
 */
public class ClassFileInstaller {
    /**
     * 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");

    /**
     * @param args The names of the classes to dump
     * @throws Exception
     */
    public static void main(String... args) throws Exception {
        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);
    }

    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, "");
    }

    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 {
            // Create the class file's package directory
            Path p = Paths.get(pathName);
            Path parent = p.getParent();
            if (parent != null) {
                Files.createDirectories(parent);
            }
            // Create the class file
            Files.copy(is, p, StandardCopyOption.REPLACE_EXISTING);
        }
        is.close();
    }
}