/**
* Licensed to JumpMind Inc under one or more contributor
* license agreements. See the NOTICE file distributed
* with this work for additional information regarding
* copyright ownership. JumpMind Inc licenses this file
* to you under the GNU General Public License, version 3.0 (GPLv3)
* (the "License"); you may not use this file except in compliance
* with the License.
*
* You should have received a copy of the GNU General Public License,
* version 3.0 (GPLv3) along with this library; if not, see
* <http://www.gnu.org/licenses/>.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jumpmind.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.security.SecureClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Compile Java code in memory, load it, and return a new instance. The class name is changed to make it unique
* and the caller should cast to a parent class or interface. On subsequent calls, if the same Java code
* is passed again, the cached object is returned. Otherwise, a new class is compiled, loaded, and an instance returned.
*
*/
@IgnoreJRERequirement
@SuppressWarnings({ "rawtypes" })
public class SimpleClassCompiler {
protected final static String REGEX_CLASS = "public\\s*class\\s*(\\w*)";
protected static SimpleClassCompiler instance;
protected Map<Integer, Object> objectMap = new HashMap<Integer, Object>();
protected int classSuffix;
private static Logger log = LoggerFactory.getLogger(SimpleClassCompiler.class);
public static SimpleClassCompiler getInstance() {
if (instance == null) {
instance = new SimpleClassCompiler();
}
return instance;
}
public Object getCompiledClass(String javaCode) throws Exception {
Integer id = javaCode.hashCode();
Object javaObject = objectMap.get(id);
if (javaObject == null ) {
String className = getNextClassName();
String origClassName = null;
Pattern pattern = Pattern.compile(REGEX_CLASS);
Matcher matcher = pattern.matcher(javaCode);
if (matcher.find()) {
origClassName = matcher.group(1);
}
javaCode = javaCode.replaceAll(REGEX_CLASS, "public class " + className);
log.info("Compiling class '" + origClassName + "'");
if (log.isDebugEnabled()) {
log.debug("Compiling code: \n" + javaCode);
}
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
throw new SimpleClassCompilerException("Missing Java compiler: the JDK is required for compiling classes.");
}
JavaFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null));
DiagnosticCollector<JavaFileObject> diag = new DiagnosticCollector<JavaFileObject>();
List<JavaFileObject> javaFiles = new ArrayList<JavaFileObject>();
javaFiles.add(new JavaObjectFromString(className, javaCode));
Boolean success = compiler.getTask(null, fileManager, diag, null, null, javaFiles).call();
if (success) {
log.debug("Compilation has succeeded");
Class<?> clazz = fileManager.getClassLoader(null).loadClass(className);
if (clazz != null) {
javaObject = clazz.newInstance();
objectMap.put(id, javaObject);
} else {
throw new SimpleClassCompilerException("The '"+className+"' class could not be located");
}
} else {
log.error("Compilation of '" + origClassName + "' failed");
for (Diagnostic diagnostic : diag.getDiagnostics()) {
log.error(origClassName + " at line " + diagnostic.getLineNumber() + ", column " + diagnostic.getColumnNumber() + ": " +
diagnostic.getMessage(null));
}
throw new SimpleClassCompilerException(diag.getDiagnostics());
}
}
return javaObject;
}
protected synchronized String getNextClassName() {
return getClass().getSimpleName() + (classSuffix++);
}
@IgnoreJRERequirement
class JavaObjectFromString extends SimpleJavaFileObject {
private String data = null;
public JavaObjectFromString(String className, String data) throws Exception {
super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.data = data;
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return data;
}
}
@IgnoreJRERequirement
class JavaClassObject extends SimpleJavaFileObject {
protected final ByteArrayOutputStream bos = new ByteArrayOutputStream();
public JavaClassObject(String name, Kind kind) {
super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
}
public byte[] getBytes() {
return bos.toByteArray();
}
@Override
public OutputStream openOutputStream() throws IOException {
return bos;
}
}
@IgnoreJRERequirement
public class ClassFileManager extends ForwardingJavaFileManager {
private HashMap<String, JavaClassObject> jclassObjects = new HashMap<String, JavaClassObject>();
@SuppressWarnings("unchecked")
public ClassFileManager(StandardJavaFileManager standardManager) {
super(standardManager);
}
@Override
public ClassLoader getClassLoader(Location location) {
return new SecureClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
JavaClassObject jclassObject = jclassObjects.get(name);
if (jclassObject != null) {
byte[] bytes = jclassObject.getBytes();
return super.defineClass(name, bytes, 0, bytes.length);
} else {
return null;
}
}
};
}
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling)
throws IOException {
JavaClassObject jclassObject = new JavaClassObject(className, kind);
jclassObjects.put(className, jclassObject);
return jclassObject;
}
}
}