1 /*
2  * Copyright (c) 2013, 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 import java.io.DataInputStream;
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.FileWriter;
28 import java.io.IOException;
29 import java.io.PrintWriter;
30 import javax.tools.JavaCompiler;
31 import javax.tools.ToolProvider;
32 
33 /**
34  * A class loader that generates new classes.
35  * The generated classes are made by first emitting java sources with nested
36  * static classes, these are then compiled and the class files are read back.
37  * Some efforts are made to make the class instances unique and of not insignificant
38  * size.
39  */
40 public class GeneratedClassLoader extends ClassLoader {
41     /**
42      * Holds a pair of class bytecodes and class name (for use with defineClass).
43      */
44     private static class GeneratedClass {
45         public byte[] bytes;
46         public String name;
GeneratedClass(byte[] bytes, String name)47         public GeneratedClass(byte[] bytes, String name) {
48             this.bytes = bytes; this.name = name;
49         }
50     }
51 
52     /**
53      * Used to uniquely name every class generated.
54      */
55     private static int count = 0;
56     /**
57      * Used to enable/disable keeping the class files and java sources for
58      * the generated classes.
59      */
60     private static boolean deleteFiles = Boolean.parseBoolean(
61         System.getProperty("GeneratedClassLoader.deleteFiles", "true"));
62 
63     private static String bigstr =
64         "Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
65         + "In facilisis scelerisque vehicula. Donec congue nisi a "
66         + "leo posuere placerat lobortis felis ultrices. Pellentesque "
67         + "habitant morbi tristique senectus et netus et malesuada "
68         + "fames ac turpis egestas. Nam tristique velit at felis "
69         + "iaculis at tempor sem vestibulum. Sed adipiscing lectus "
70         + "non mi molestie sagittis. Morbi eu purus urna. Nam tempor "
71         + "tristique massa eget semper. Mauris cursus, nulla et ornare "
72         + "vehicula, leo dolor scelerisque metus, sit amet rutrum erat "
73         + "sapien quis dui. Nullam eleifend risus et velit accumsan sed "
74         + "suscipit felis pulvinar. Nullam faucibus suscipit gravida. "
75         + "Pellentesque habitant morbi tristique senectus et netus et "
76         + "malesuada fames ac turpis egestas. Nullam ut massa augue, "
77         + "nec viverra mauris.";
78 
getNextCount()79     private static int getNextCount() {
80         return count++;
81     }
82 
83     ////// end statics
84 
85     private JavaCompiler javac;
86     private String nameBase;
87 
GeneratedClassLoader()88     public GeneratedClassLoader() {
89         javac = ToolProvider.getSystemJavaCompiler();
90         nameBase = "TestSimpleClass";
91     }
92 
getBigValue(int which)93     private long getBigValue(int which) {
94         // > 65536 is too large to encode in the bytecode
95         // so this will force us to emit a constant pool entry for this int
96         return (long)which + 65537;
97     }
98 
getBigString(int which)99     private String getBigString(int which) {
100         return bigstr + which;
101     }
102 
getClassName(int count)103     private String getClassName(int count) {
104         return nameBase + count;
105     }
106 
generateSource(int count, int sizeFactor, int numClasses)107     private String generateSource(int count, int sizeFactor, int numClasses) {
108         StringBuilder sb = new StringBuilder();
109         sb.append("public class ").append(getClassName(count)).append("{\n");
110         for (int j = 0; j < numClasses; ++j) {
111             sb.append("public static class ")
112               .append("Class")
113               .append(j)
114               .append("{\n");
115             for (int i = 0; i < sizeFactor; ++i) {
116                 int value = i;
117                 sb.append("private long field")
118                   .append(i).append(" = ")
119                   .append(getBigValue(value++))
120                   .append(";\n");
121                 sb.append("public long method")
122                   .append(i)
123                   .append("() {\n");
124                 sb.append("return ")
125                   .append(getBigValue(value++))
126                   .append(";");
127                 sb.append("}\n");
128                 sb.append("private String str").append(i)
129                   .append(" = \"")
130                   .append(getBigString(i))
131                   .append("\";");
132             }
133             sb.append("\n}");
134         }
135         sb.append("\n}");
136         return sb.toString();
137     }
138 
getGeneratedClass(int sizeFactor, int numClasses)139     private GeneratedClass[] getGeneratedClass(int sizeFactor, int numClasses) throws IOException {
140         int uniqueCount = getNextCount();
141         String src = generateSource(uniqueCount, sizeFactor, numClasses);
142         String className = getClassName(uniqueCount);
143         File file = new File(className + ".java");
144         try (PrintWriter pw = new PrintWriter(new FileWriter(file))) {
145             pw.append(src);
146             pw.flush();
147         }
148         int exitcode = javac.run(null, null, null, file.getCanonicalPath());
149         if (exitcode != 0) {
150             throw new RuntimeException("javac failure when compiling: " +
151                     file.getCanonicalPath());
152         } else {
153             if (deleteFiles) {
154                 file.delete();
155             }
156         }
157         GeneratedClass[] gc = new GeneratedClass[numClasses];
158         for (int i = 0; i < numClasses; ++i) {
159             String name = className + "$" + "Class" + i;
160             File classFile = new File(name + ".class");
161             byte[] bytes;
162             try (DataInputStream dis = new DataInputStream(new FileInputStream(classFile))) {
163                 bytes = new byte[dis.available()];
164                 dis.readFully(bytes);
165             }
166             if (deleteFiles) {
167                 classFile.delete();
168             }
169             gc[i] = new GeneratedClass(bytes, name);
170         }
171         if (deleteFiles) {
172             new File(className + ".class").delete();
173         }
174         return gc;
175     }
176 
177     /**
178      * Generate a single class, compile it and load it.
179      * @param sizeFactor Fuzzy measure of how large the class should be.
180      * @return the Class instance.
181      * @throws IOException
182      */
generateClass(int sizeFactor)183     public Class<?> generateClass(int sizeFactor) throws IOException {
184         return getGeneratedClasses(sizeFactor, 1)[0];
185     }
186 
187     /**
188      * Generate several classes, compile and load them.
189      * @param sizeFactor Fuzzy measure of how large each class should be.
190      * @param numClasses The number of classes to create
191      * @return an array of the Class instances.
192      * @throws IOException
193      */
getGeneratedClasses(int sizeFactor, int numClasses)194     public Class<?>[] getGeneratedClasses(int sizeFactor, int numClasses) throws IOException {
195         GeneratedClass[] gc = getGeneratedClass(sizeFactor, numClasses);
196         Class<?>[] classes = new Class[numClasses];
197         for (int i = 0; i < numClasses; ++i) {
198             classes[i] = defineClass(gc[i].name, gc[i].bytes, 0 , gc[i].bytes.length);
199         }
200         return classes;
201     }
202 }
203