1 /* 2 * Copyright (c) 2013, 2018, 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 24 import java.io.ByteArrayInputStream; 25 import java.io.File; 26 import java.io.FileInputStream; 27 import java.io.FileOutputStream; 28 import java.io.FileNotFoundException; 29 import java.io.InputStream; 30 import java.io.ByteArrayInputStream; 31 import java.nio.file.Files; 32 import java.nio.file.Path; 33 import java.nio.file.Paths; 34 import java.nio.file.StandardCopyOption; 35 import java.util.zip.ZipEntry; 36 import 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 */ 69 public 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 }