072459a055
Reviewed-by: erikj, ihse, ehelin
287 lines
9.9 KiB
Java
287 lines
9.9 KiB
Java
/*
|
|
* 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() +
|
|
" <template-file> <ouput-dir> <count>" );
|
|
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<String> 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<String> 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<String> optionList = new ArrayList<>();
|
|
optionList.addAll(Arrays.asList("-d", destDir));
|
|
StandardJavaFileManager sjfm = compiler.getStandardFileManager(null, null, null);
|
|
Iterable<? extends JavaFileObject> 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<Path>() {
|
|
|
|
@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);
|
|
}
|
|
|
|
}
|
|
}
|