package oms3.compiler; import java.io.File; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.ToolProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * */ public final class Compiler { private static Logger log = LoggerFactory.getLogger("oms3.sim"); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); MemoryOutputJavaFileManager fileManager; ClassLoader loader; Map<String, Class<?>> cache = new HashMap<String, Class<?>>(); List<String> compilerOptions = new ArrayList<String>(); // private static Compiler instance; public static synchronized Compiler singleton(URLClassLoader parent) { if (instance == null) { instance = new Compiler(parent); } return instance; } private Compiler(URLClassLoader parent) { // If not running on JDK, compiler will be null if (compiler == null) { throw new Error("Compiler not available. This may happen if " + "running on JRE instead of JDK. Please use a full JDK 1.6." + "javax.tools.ToolProvider.getSystemJavaCompiler() returned null."); } log.debug("Instantiating new compiler " + parent); fileManager = new MemoryOutputJavaFileManager(compiler.getStandardFileManager(null, null, null)); loader = new JavaFileManagerClassLoader(fileManager, parent); // create a classpath for the compiler StringBuilder b = new StringBuilder(); URL[] cp = parent.getURLs(); for (int i = 0; i < cp.length; i++) { b.append(File.pathSeparatorChar); b.append(cp[i].getFile()); } /* * This is necessary to add all jars of an app when running inside a * container */ try { URL[] cl = ((URLClassLoader) Thread.currentThread().getContextClassLoader()).getURLs(); for (URL url : cl) { b.append(File.pathSeparatorChar).append(url.getFile()); } } catch (ClassCastException e) { log.error("Context class loader is not a URLClassLoader"); } // set compiler's classpath to be same as the runtime's compilerOptions.addAll(Arrays.asList("-cp", System.getProperty("java.class.path") + b.toString())); } /** * Compiles a single source file and loads the class with a default class * loader. The default class loader is the one used to load the test case * class. * * @param name * the name of the class to compile. * @param code * the source code of the class. * * @return the compiled class. */ public synchronized Class<?> compileSource(String name, String code) throws Exception { Class<?> c = cache.get(name); if (c == null) { c = compileSource0(name, code); cache.put(name, c); } return c; } public synchronized Class<?> getCompiledClass(String name) { try { return Class.forName(name); } catch (ClassNotFoundException e) { try { return loader.loadClass(name); } catch (ClassNotFoundException ex) { return cache.get(name); } } } /** * Compiles multiple sources file and loads the classes. * * @param sourceFiles * the source files to compile. * @param parentLoader * the parent class loader to use when loading classes. * * @return a map of compiled classes. This maps class names to Class objects. * @throws Exception * */ private Class<?> compileSource0(String className, String sourceCode) throws Exception { List<MemorySourceJavaFileObject> compUnits = new ArrayList<MemorySourceJavaFileObject>(1); compUnits.add(new MemorySourceJavaFileObject(className + ".java", sourceCode)); DiagnosticCollector<JavaFileObject> diag = new DiagnosticCollector<JavaFileObject>(); log.debug("Running compiler with options: " + compilerOptions); Boolean result = compiler.getTask(null, fileManager, diag, compilerOptions, null, compUnits).call(); if (result.equals(Boolean.FALSE)) { throw new RuntimeException(diag.getDiagnostics().toString()); } try { String classDotName = className.replace('/', '.'); return Class.forName(classDotName, true, loader); } catch (ClassNotFoundException e) { throw e; } } }