1 /* 2 * Copyright (c) 2014, 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 package gc.g1.unloading; 25 26 import java.io.File; 27 import java.io.FileOutputStream; 28 import java.io.IOException; 29 import java.nio.file.FileVisitResult; 30 import java.nio.file.FileVisitor; 31 import java.nio.file.Files; 32 import java.nio.file.Path; 33 import java.nio.file.Paths; 34 import java.nio.file.attribute.BasicFileAttributes; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.List; 38 import java.util.jar.JarEntry; 39 import java.util.jar.JarOutputStream; 40 import java.util.jar.Manifest; 41 import javax.tools.JavaCompiler; 42 import javax.tools.JavaFileObject; 43 import javax.tools.StandardJavaFileManager; 44 import javax.tools.ToolProvider; 45 import jdk.internal.org.objectweb.asm.ClassReader; 46 import jdk.internal.org.objectweb.asm.ClassVisitor; 47 import jdk.internal.org.objectweb.asm.ClassWriter; 48 import jdk.internal.org.objectweb.asm.Opcodes; 49 50 /** 51 * Class that imitates shell script to produce jar file with many similar 52 * classes inside. 53 * 54 * The class generates sources, compiles the first one, applies magic of ASM 55 * to multiply classes and packs into classPool.jar 56 * 57 * Generation template is supposed to be ClassNNN.java.template 58 */ 59 public class GenClassPoolJar { 60 61 private final String templateFile; 62 private final String destDir; 63 private final int count; 64 65 private final File tmpArea; 66 private final File pkgDir; 67 68 private static final String JAR_NAME = "classPool.jar"; 69 private static final String PKG_DIR_NAME = "gc/g1/unloading/rootSetHelper/classesPool"; 70 main(String args[])71 public static void main(String args[]) { 72 new GenClassPoolJar(args).script(); 73 } 74 75 /** 76 * Creates generator and parses command line args. 77 * @param args command line args 78 */ GenClassPoolJar(String args[])79 public GenClassPoolJar(String args[]) { 80 if (args.length != 3) { 81 System.err.println("Usage:"); 82 System.err.println("java " + GenClassPoolJar.class.getCanonicalName() + 83 " <template-file> <ouput-dir> <count>" ); 84 throw new Error("Illegal number of parameters"); 85 } 86 templateFile = args[0]; 87 destDir = args[1]; 88 count = Integer.parseInt(args[2]); 89 90 tmpArea = new File(destDir, "tmp-area"); 91 pkgDir = new File(tmpArea, PKG_DIR_NAME); 92 93 } 94 /** 95 * Does everything. 96 */ script()97 public void script() { 98 long startTime = System.currentTimeMillis(); 99 System.out.println("Trying to produce: " + destDir + "/" + JAR_NAME); 100 try { 101 102 if (!pkgDir.exists() && !pkgDir.mkdirs()) { 103 throw new Error("Failed to create " + pkgDir); 104 } 105 106 107 String javaTemplate = readTemplate(templateFile); 108 File java0 = new File(pkgDir, "Class0.java"); 109 File class0 = new File(pkgDir, "Class0.class"); 110 writeSource(java0, generateSource(javaTemplate, 0)); 111 112 /* 113 * Generating and compiling all the sources is not our way - 114 * too easy and too slow. 115 * We compile just first class and use ASM to obtain others 116 * via instrumenting. 117 */ 118 File[] toCompile = {java0}; 119 compile(toCompile, tmpArea.getAbsolutePath()); 120 byte[] classTemplate = readFile(class0); // the first compiled class 121 createJar(new File(destDir, JAR_NAME), javaTemplate, classTemplate, count); 122 123 124 deleteFolder(tmpArea); 125 long endTime = System.currentTimeMillis(); 126 System.out.println("Success in " + ((endTime - startTime)/1000) + " seconds"); 127 } catch (Throwable whatever) { 128 throw new Error(whatever); 129 } 130 } 131 132 /** 133 * Generates source number num. 134 * @param template template to generate from 135 * @param num number 136 * @return content of java file 137 */ generateSource(String template, int num)138 String generateSource(String template, int num) { 139 return template.replaceAll("_NNN_", "" + num); 140 } 141 142 /** 143 * Reads content of the given file. 144 * @param file name of file to read 145 * @return file content 146 * @throws IOException if something bad has happened 147 */ readTemplate(String file)148 String readTemplate(String file) throws IOException { 149 if (!new File(file).exists()) { 150 throw new Error("Template " + file + " doesn't exist"); 151 } 152 List<String> lines = Files.readAllLines(Paths.get(file)); 153 StringBuilder sb = new StringBuilder(); 154 for (String line: lines) { 155 if (line.trim().startsWith("#")) { 156 continue; 157 } 158 sb.append(line).append(System.lineSeparator()); 159 } 160 return sb.toString(); 161 } 162 163 /** 164 * Writes given content to the given file. 165 * 166 * @param file to create 167 * @param content java source 168 * @throws IOException if something bad has happened 169 */ writeSource(File file, String content)170 void writeSource(File file, String content) throws IOException { 171 List<String> list = Arrays.asList(content.split(System.lineSeparator())); 172 Files.write(Paths.get(file.getAbsolutePath()), list); 173 } 174 175 176 /** 177 * Compiles given files into given folder. 178 * 179 * @param files to compile 180 * @param destDir where to compile 181 * @throws IOException 182 */ compile(File[] files, String destDir)183 void compile(File[] files, String destDir) throws IOException { 184 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 185 List<String> optionList = new ArrayList<>(); 186 optionList.addAll(Arrays.asList("-d", destDir)); 187 StandardJavaFileManager sjfm = compiler.getStandardFileManager(null, null, null); 188 Iterable<? extends JavaFileObject> fileObjects = sjfm.getJavaFileObjects(files); 189 JavaCompiler.CompilationTask task = compiler.getTask(null, null, null, optionList, null, fileObjects); 190 task.call(); 191 sjfm.close(); 192 } 193 194 /** 195 * Puts a number of classes and java sources in the given jar. 196 * 197 * @param jarFile name of jar file 198 * @param javaTemplate content of java source template 199 * @param classTemplate content of compiled java class 200 * @param count number of classes to generate 201 * @throws IOException 202 */ createJar(File jarFile, String javaTemplate, byte[] classTemplate, int count)203 void createJar(File jarFile, String javaTemplate, byte[] classTemplate, int count) throws IOException { 204 try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(jarFile), new Manifest())) { 205 for (int i = 1; i <= count; i++) { 206 String name = PKG_DIR_NAME + "/Class" + i; 207 jar.putNextEntry(new JarEntry(name + ".java")); 208 byte[] content = generateSource(javaTemplate, 0).getBytes(); 209 jar.write(content, 0, content.length); 210 211 jar.putNextEntry(new JarEntry(name + ".class")); 212 content = morphClass(classTemplate, name); 213 jar.write(content, 0, content.length); 214 } 215 } 216 } 217 readFile(File f)218 byte[] readFile(File f) throws IOException { 219 return Files.readAllBytes(Paths.get(f.getAbsolutePath())); 220 } 221 writeFile(File f, byte[] content)222 void writeFile(File f, byte[] content) throws IOException { 223 Files.write(Paths.get(f.getAbsolutePath()), content); 224 } 225 deleteFolder(File dir)226 void deleteFolder(File dir) throws IOException { 227 Files.walkFileTree(Paths.get(dir.getAbsolutePath()), new FileVisitor<Path>() { 228 229 @Override 230 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 231 return FileVisitResult.CONTINUE; 232 } 233 234 @Override 235 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 236 Files.delete(file); 237 return FileVisitResult.CONTINUE; 238 } 239 240 @Override 241 public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { 242 return FileVisitResult.CONTINUE; 243 } 244 245 @Override 246 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 247 Files.delete(dir); 248 return FileVisitResult.CONTINUE; 249 } 250 251 }); 252 } 253 254 /** 255 * Puts new name on the given class. 256 * 257 * @param classToMorph class file content 258 * @param newName new name 259 * @return new class file to write into class 260 */ morphClass(byte[] classToMorph, String newName)261 byte[] morphClass(byte[] classToMorph, String newName) { 262 ClassReader cr = new ClassReader(classToMorph); 263 ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS); 264 ClassVisitor cv = new ClassRenamer(cw, newName); 265 cr.accept(cv, 0); 266 return cw.toByteArray(); 267 } 268 269 /** 270 * Visitor to rename class. 271 */ 272 static class ClassRenamer extends ClassVisitor implements Opcodes { 273 private final String newName; 274 ClassRenamer(ClassVisitor cv, String newName)275 public ClassRenamer(ClassVisitor cv, String newName) { 276 super(ASM4, cv); 277 this.newName = newName; 278 } 279 280 @Override visit(int version, int access, String name, String signature, String superName, String[] interfaces)281 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 282 cv.visit(version, access, newName, signature, superName, interfaces); 283 } 284 285 } 286 } 287