blob: 9818ee73c8e0580222b159ea3cf56a4e1718f4ce [file] [log] [blame]
/*
* 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);
}
}
}