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;
  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 
  79     private static int getNextCount() {
  80         return count++;
  81     }
  82 
  83     ////// end statics
  84 
  85     private JavaCompiler javac;
  86     private String nameBase;
  87 
  88     public GeneratedClassLoader() {
  89         javac = ToolProvider.getSystemJavaCompiler();
  90         nameBase = "TestSimpleClass";
  91     }
  92 
  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 
  99     private String getBigString(int which) {
 100         return bigstr + which;
 101     }
 102 
 103     private String getClassName(int count) {
 104         return nameBase + count;
 105     }
 106 
 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 
 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      */
 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      */
 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 }