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    *      https://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      /**
49       * Check if we can invoke Sun's Java compiler.
50       *
51       * @return true if it is possible, false otherwise
52       */
53      private static boolean comSunToolsJavacMain() {
54          try {
55              final Class<?> javac = ClassCreatorTest.class.getClassLoader().loadClass("com.sun.tools.javac.Main");
56              return javac != null;
57          } catch (final Exception xany) {
58              return false;
59          }
60      }
61      //private final JexlEngine jexl;
62      private final File base;
63      private File packageDir;
64  
65      private int seed;
66      private String ctorBody = "";
67      private String className;
68      private String sourceName;
69  
70      private ClassLoader loader;
71  
72      public ClassCreator(final JexlEngine theJexl, final File theBase) throws Exception {
73          //jexl = theJexl;
74          base = theBase;
75      }
76  
77      public void clear() {
78          seed = 0;
79          ctorBody = "";
80          packageDir = null;
81          className = null;
82          sourceName = null;
83          packageDir = null;
84          loader = null;
85      }
86  
87      Class<?> compile() throws Exception {
88          final String source = packageDir.getPath() + "/" + sourceName;
89          final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
90          final DiagnosticCollector<JavaFileObject> diagnosticsCollector = new DiagnosticCollector<>();
91          final boolean success;
92          try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnosticsCollector, null, null)) {
93              final Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromStrings(Collections.singletonList(source));
94  
95              final List<String> options = new ArrayList<>();
96              options.add("-classpath");
97              // only add hbase classes to classpath. This is a little bit tricky: assume
98              // the classpath is {hbaseSrc}/target/classes.
99              final String currentDir = new File(".").getAbsolutePath();
100             final String classpath = currentDir + File.separator + "target" + File.separator + "classes"
101             // + File.pathSeparator + System.getProperty("java.class.path")
102                     + File.pathSeparator + System.getProperty("surefire.test.class.path");
103 
104             options.add(classpath);
105             // LOG.debug("Setting classpath to: " + classpath);
106 
107             final JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnosticsCollector, options, null, compilationUnits);
108             success = task.call();
109         }
110         if (success) {
111             return getClassLoader().loadClass(GEN_CLASS + className);
112         }
113         final List<Diagnostic<? extends JavaFileObject>> diagnostics = diagnosticsCollector.getDiagnostics();
114         for (final Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) {
115             // read error details from the diagnostic object
116             System.out.println(diagnostic.getMessage(null));
117 
118         }
119         return null;
120     }
121 
122     public Class<?> createClass() throws Exception {
123         return createClass(false);
124     }
125 
126     public Class<?> createClass(final boolean ftor) throws Exception {
127         // generate, compile & validate
128         generate(ftor);
129         final Class<?> clazz = compile();
130         if (clazz == null) {
131             throw new Exception("failed to compile foo" + seed);
132         }
133         if (ftor) {
134             return clazz;
135         }
136         final Object v = validate(clazz);
137         if (v instanceof Integer && (Integer) v == seed) {
138             return clazz;
139         }
140         throw new Exception("failed to validate foo" + seed);
141     }
142 
143     void generate(final boolean ftor) throws Exception {
144         try (final FileWriter writer = new FileWriter(new File(packageDir, sourceName), false)) {
145             writer.write("package ");
146             writer.write(GEN_PACKAGE);
147             writer.write(";\n");
148             if (ftor) {
149                 writer.write("import " + JEXL_PACKAGE + ".JexlContext;");
150                 writer.write(";\n");
151             }
152             writer.write("public class " + className);
153             writer.write(" {\n");
154             if (ftor) {
155                 writer.write("public " + className + "(JexlContext ctxt) {\n");
156                 writer.write(ctorBody);
157                 writer.write(" }\n");
158             }
159             writer.write("private int value =");
160             writer.write(Integer.toString(seed));
161             writer.write(";\n");
162             writer.write(" public void setValue(int v) {");
163             writer.write(" value = v;");
164             writer.write(" }\n");
165             writer.write(" public int getValue() {");
166             writer.write(" return value;");
167             writer.write(" }\n");
168             writer.write(" }\n");
169             writer.flush();
170         }
171     }
172 
173     public Class<?> getClassInstance() throws Exception {
174         return getClassLoader().loadClass(getClassName());
175     }
176 
177     public ClassLoader getClassLoader() throws Exception {
178         if (loader == null) {
179             final URL classpath = new File(base, Integer.toString(seed)).toURI().toURL();
180             loader = new URLClassLoader(new URL[]{classpath}, getClass().getClassLoader());
181         }
182         return loader;
183     }
184 
185     public String getClassName() {
186         return GEN_CLASS + className;
187     }
188 
189     Object newInstance(final Class<?> clazz, final JexlContext ctxt) throws Exception {
190         return clazz.getConstructor(JexlContext.class).newInstance(ctxt);
191     }
192 
193     public void setCtorBody(final String arg) {
194         ctorBody = arg;
195     }
196 
197 //    Class<?> compile0() throws Exception {
198 //        String source = packageDir.getPath() + "/" + sourceName;
199 //        Class<?> javac = getClassLoader().loadClass("com.sun.tools.javac.Main");
200 //        if (javac == null) {
201 //            return null;
202 //        }
203 //        Integer r;
204 //        try {
205 //            r = (Integer) jexl.invokeMethod(javac, "compile", source);
206 //            if (r.intValue() >= 0) {
207 //                return getClassLoader().loadClass(GEN_CLASS + className);
208 //            }
209 //        } catch (JexlException xignore) {
210 //            // ignore
211 //        }
212 //        r = (Integer) jexl.invokeMethod(javac, "compile", (Object) new String[]{source});
213 //        if (r.intValue() >= 0) {
214 //            return getClassLoader().loadClass(GEN_CLASS + className);
215 //        }
216 //        return null;
217 //    }
218 
219     public void setSeed(final int s) {
220         seed = s;
221         className = "foo" + s;
222         sourceName = className + ".java";
223         packageDir = new File(base, seed + GEN_PATH);
224         packageDir.mkdirs();
225         loader = null;
226     }
227 
228     Object validate(final Class<?> clazz) throws Exception {
229         final Class<?>[] params = {};
230         final Object[] paramsObj = {};
231         final Object iClass = clazz.getConstructor().newInstance();
232         final Method thisMethod = clazz.getDeclaredMethod("getValue", params);
233         return thisMethod.invoke(iClass, paramsObj);
234     }
235 
236 }