View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3;
18  
19  import java.io.File;
20  import java.io.FileWriter;
21  import java.lang.reflect.Method;
22  import java.net.URL;
23  import java.net.URLClassLoader;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.List;
27  
28  import javax.tools.Diagnostic;
29  import javax.tools.DiagnosticCollector;
30  import javax.tools.JavaCompiler;
31  import javax.tools.JavaFileObject;
32  import javax.tools.StandardJavaFileManager;
33  import javax.tools.ToolProvider;
34  
35  /**
36   * Helper class to test GC / reference interactions. Dynamically creates a class
37   * by compiling generated source Java code and load it through a dedicated class
38   * loader.
39   */
40  public class ClassCreator {
41  
42      public static final boolean canRun = true; //comSunToolsJavacMain();
43      static final String JEXL_PACKAGE = "org.apache.commons.jexl3";
44      static final String GEN_PACKAGE = "org.apache.commons.jexl3.generated";
45      static final String GEN_PATH = "/" + GEN_PACKAGE.replace(".", "/"); ///org/apache/commons/jexl3/generated";
46      static final String GEN_CLASS = GEN_PACKAGE + ".";
47      /**
48       * Check if we can invoke Sun's Java compiler.
49       *
50       * @return true if it is possible, false otherwise
51       */
52      private static boolean comSunToolsJavacMain() {
53          try {
54              final Class<?> javac = ClassCreatorTest.class.getClassLoader().loadClass("com.sun.tools.javac.Main");
55              return javac != null;
56          } catch (final Exception xany) {
57              return false;
58          }
59      }
60      //private final JexlEngine jexl;
61      private final File base;
62      private File packageDir;
63  
64      private int seed;
65      private String ctorBody = "";
66      private String className;
67      private String sourceName;
68  
69      private ClassLoader loader;
70  
71      public ClassCreator(final JexlEngine theJexl, final File theBase) throws Exception {
72          //jexl = theJexl;
73          base = theBase;
74      }
75  
76      public void clear() {
77          seed = 0;
78          ctorBody = "";
79          packageDir = null;
80          className = null;
81          sourceName = null;
82          packageDir = null;
83          loader = null;
84      }
85  
86      Class<?> compile() throws Exception {
87          final String source = packageDir.getPath() + "/" + sourceName;
88          final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
89          final DiagnosticCollector<JavaFileObject> diagnosticsCollector = new DiagnosticCollector<>();
90          final boolean success;
91          try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnosticsCollector, null, null)) {
92              final Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromStrings(Collections.singletonList(source));
93  
94              final List<String> options = new ArrayList<>();
95              options.add("-classpath");
96              // only add hbase classes to classpath. This is a little bit tricky: assume
97              // the classpath is {hbaseSrc}/target/classes.
98              final String currentDir = new File(".").getAbsolutePath();
99              final String classpath = currentDir + File.separator + "target" + File.separator + "classes"
100             // + File.pathSeparator + System.getProperty("java.class.path")
101                     + File.pathSeparator + System.getProperty("surefire.test.class.path");
102 
103             options.add(classpath);
104             // LOG.debug("Setting classpath to: " + classpath);
105 
106             final JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnosticsCollector, options, null, compilationUnits);
107             success = task.call();
108         }
109         if (success) {
110             return getClassLoader().loadClass(GEN_CLASS + className);
111         }
112         final List<Diagnostic<? extends JavaFileObject>> diagnostics = diagnosticsCollector.getDiagnostics();
113         for (final Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) {
114             // read error details from the diagnostic object
115             System.out.println(diagnostic.getMessage(null));
116 
117         }
118         return null;
119     }
120 
121     public Class<?> createClass() throws Exception {
122         return createClass(false);
123     }
124 
125     public Class<?> createClass(final boolean ftor) throws Exception {
126         // generate, compile & validate
127         generate(ftor);
128         final Class<?> clazz = compile();
129         if (clazz == null) {
130             throw new Exception("failed to compile foo" + seed);
131         }
132         if (ftor) {
133             return clazz;
134         }
135         final Object v = validate(clazz);
136         if (v instanceof Integer && (Integer) v == seed) {
137             return clazz;
138         }
139         throw new Exception("failed to validate foo" + seed);
140     }
141 
142     void generate(final boolean ftor) throws Exception {
143         try (final FileWriter writer = new FileWriter(new File(packageDir, sourceName), false)) {
144             writer.write("package ");
145             writer.write(GEN_PACKAGE);
146             writer.write(";\n");
147             if (ftor) {
148                 writer.write("import " + JEXL_PACKAGE + ".JexlContext;");
149                 writer.write(";\n");
150             }
151             writer.write("public class " + className);
152             writer.write(" {\n");
153             if (ftor) {
154                 writer.write("public " + className + "(JexlContext ctxt) {\n");
155                 writer.write(ctorBody);
156                 writer.write(" }\n");
157             }
158             writer.write("private int value =");
159             writer.write(Integer.toString(seed));
160             writer.write(";\n");
161             writer.write(" public void setValue(int v) {");
162             writer.write(" value = v;");
163             writer.write(" }\n");
164             writer.write(" public int getValue() {");
165             writer.write(" return value;");
166             writer.write(" }\n");
167             writer.write(" }\n");
168             writer.flush();
169         }
170     }
171 
172     public Class<?> getClassInstance() throws Exception {
173         return getClassLoader().loadClass(getClassName());
174     }
175 
176     public ClassLoader getClassLoader() throws Exception {
177         if (loader == null) {
178             final URL classpath = new File(base, Integer.toString(seed)).toURI().toURL();
179             loader = new URLClassLoader(new URL[]{classpath}, getClass().getClassLoader());
180         }
181         return loader;
182     }
183 
184     public String getClassName() {
185         return GEN_CLASS + className;
186     }
187 
188     Object newInstance(final Class<?> clazz, final JexlContext ctxt) throws Exception {
189         return clazz.getConstructor(JexlContext.class).newInstance(ctxt);
190     }
191 
192     public void setCtorBody(final String arg) {
193         ctorBody = arg;
194     }
195 
196 //    Class<?> compile0() throws Exception {
197 //        String source = packageDir.getPath() + "/" + sourceName;
198 //        Class<?> javac = getClassLoader().loadClass("com.sun.tools.javac.Main");
199 //        if (javac == null) {
200 //            return null;
201 //        }
202 //        Integer r;
203 //        try {
204 //            r = (Integer) jexl.invokeMethod(javac, "compile", source);
205 //            if (r.intValue() >= 0) {
206 //                return getClassLoader().loadClass(GEN_CLASS + className);
207 //            }
208 //        } catch (JexlException xignore) {
209 //            // ignore
210 //        }
211 //        r = (Integer) jexl.invokeMethod(javac, "compile", (Object) new String[]{source});
212 //        if (r.intValue() >= 0) {
213 //            return getClassLoader().loadClass(GEN_CLASS + className);
214 //        }
215 //        return null;
216 //    }
217 
218     public void setSeed(final int s) {
219         seed = s;
220         className = "foo" + s;
221         sourceName = className + ".java";
222         packageDir = new File(base, seed + GEN_PATH);
223         packageDir.mkdirs();
224         loader = null;
225     }
226 
227     Object validate(final Class<?> clazz) throws Exception {
228         final Class<?>[] params = {};
229         final Object[] paramsObj = {};
230         final Object iClass = clazz.getConstructor().newInstance();
231         final Method thisMethod = clazz.getDeclaredMethod("getValue", params);
232         return thisMethod.invoke(iClass, paramsObj);
233     }
234 
235 }