/* * Copyright (c) 2014, 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. */ package gc.g1.unloading; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import jdk.internal.org.objectweb.asm.ClassReader; import jdk.internal.org.objectweb.asm.ClassVisitor; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.Opcodes; /** * Class that imitates shell script to produce jar file with many similar * classes inside. * * The class generates sources, compiles the first one, applies magic of ASM * to multiply classes and packs into classPool.jar * * Generation template is supposed to be ClassNNN.java.template */ public class GenClassPoolJar { private final String templateFile; private final String destDir; private final int count; private final File tmpArea; private final File pkgDir; private static final String JAR_NAME = "classPool.jar"; private static final String PKG_DIR_NAME = "gc/g1/unloading/rootSetHelper/classesPool"; public static void main(String args[]) { new GenClassPoolJar(args).script(); } /** * Creates generator and parses command line args. * @param args command line args */ public GenClassPoolJar(String args[]) { if (args.length != 3) { System.err.println("Usage:"); System.err.println("java " + GenClassPoolJar.class.getCanonicalName() + " " ); throw new Error("Illegal number of parameters"); } templateFile = args[0]; destDir = args[1]; count = Integer.parseInt(args[2]); tmpArea = new File(destDir, "tmp-area"); pkgDir = new File(tmpArea, PKG_DIR_NAME); } /** * Does everything. */ public void script() { long startTime = System.currentTimeMillis(); System.out.println("Trying to produce: " + destDir + "/" + JAR_NAME); try { if (!pkgDir.exists() && !pkgDir.mkdirs()) { throw new Error("Failed to create " + pkgDir); } String javaTemplate = readTemplate(templateFile); File java0 = new File(pkgDir, "Class0.java"); File class0 = new File(pkgDir, "Class0.class"); writeSource(java0, generateSource(javaTemplate, 0)); /* * Generating and compiling all the sources is not our way - * too easy and too slow. * We compile just first class and use ASM to obtain others * via instrumenting. */ File[] toCompile = {java0}; compile(toCompile, tmpArea.getAbsolutePath()); byte[] classTemplate = readFile(class0); // the first compiled class createJar(new File(destDir, JAR_NAME), javaTemplate, classTemplate, count); deleteFolder(tmpArea); long endTime = System.currentTimeMillis(); System.out.println("Success in " + ((endTime - startTime)/1000) + " seconds"); } catch (Throwable whatever) { throw new Error(whatever); } } /** * Generates source number num. * @param template template to generate from * @param num number * @return content of java file */ String generateSource(String template, int num) { return template.replaceAll("_NNN_", "" + num); } /** * Reads content of the given file. * @param file name of file to read * @return file content * @throws IOException if something bad has happened */ String readTemplate(String file) throws IOException { if (!new File(file).exists()) { throw new Error("Template " + file + " doesn't exist"); } List lines = Files.readAllLines(Paths.get(file)); StringBuilder sb = new StringBuilder(); for (String line: lines) { if (line.trim().startsWith("#")) { continue; } sb.append(line).append(System.lineSeparator()); } return sb.toString(); } /** * Writes given content to the given file. * * @param file to create * @param content java source * @throws IOException if something bad has happened */ void writeSource(File file, String content) throws IOException { List list = Arrays.asList(content.split(System.lineSeparator())); Files.write(Paths.get(file.getAbsolutePath()), list); } /** * Compiles given files into given folder. * * @param files to compile * @param destDir where to compile * @throws IOException */ void compile(File[] files, String destDir) throws IOException { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); List optionList = new ArrayList<>(); optionList.addAll(Arrays.asList("-d", destDir)); StandardJavaFileManager sjfm = compiler.getStandardFileManager(null, null, null); Iterable fileObjects = sjfm.getJavaFileObjects(files); JavaCompiler.CompilationTask task = compiler.getTask(null, null, null, optionList, null, fileObjects); task.call(); sjfm.close(); } /** * Puts a number of classes and java sources in the given jar. * * @param jarFile name of jar file * @param javaTemplate content of java source template * @param classTemplate content of compiled java class * @param count number of classes to generate * @throws IOException */ void createJar(File jarFile, String javaTemplate, byte[] classTemplate, int count) throws IOException { try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(jarFile), new Manifest())) { for (int i = 1; i <= count; i++) { String name = PKG_DIR_NAME + "/Class" + i; jar.putNextEntry(new JarEntry(name + ".java")); byte[] content = generateSource(javaTemplate, 0).getBytes(); jar.write(content, 0, content.length); jar.putNextEntry(new JarEntry(name + ".class")); content = morphClass(classTemplate, name); jar.write(content, 0, content.length); } } } byte[] readFile(File f) throws IOException { return Files.readAllBytes(Paths.get(f.getAbsolutePath())); } void writeFile(File f, byte[] content) throws IOException { Files.write(Paths.get(f.getAbsolutePath()), content); } void deleteFolder(File dir) throws IOException { Files.walkFileTree(Paths.get(dir.getAbsolutePath()), new FileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } }); } /** * Puts new name on the given class. * * @param classToMorph class file content * @param newName new name * @return new class file to write into class */ byte[] morphClass(byte[] classToMorph, String newName) { ClassReader cr = new ClassReader(classToMorph); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS); ClassVisitor cv = new ClassRenamer(cw, newName); cr.accept(cv, 0); return cw.toByteArray(); } /** * Visitor to rename class. */ static class ClassRenamer extends ClassVisitor implements Opcodes { private final String newName; public ClassRenamer(ClassVisitor cv, String newName) { super(ASM4, cv); this.newName = newName; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { cv.visit(version, access, newName, signature, superName, interfaces); } } }