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