package net.jueb.util4j.hotSwap.memoryScript.impl;
import net.jueb.util4j.hotSwap.memoryScript.MemoryCompiler;
import javax.tools.*;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject.Kind;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.Map.Entry;
/**
* Compile a String or other {@link CharSequence}, returning a Java
* {@link Class} instance that may be instantiated. This class is a Facade
* around {@link JavaCompiler} for a narrower use case, but a bit easier to use.
* <p/>
* To compile a String containing source for a Java class which implements
* MyInterface:
* <p/>
* <pre>
* ClassLoader classLoader = MyClass.class.getClassLoader(); // optional; null is also OK
* List<Diagnostic> diagnostics = new ArrayList<Diagnostic>(); // optional; null is also OK
* JavaStringCompiler<Object> compiler = new JavaStringCompiler<MyInterface>(classLoader,
* null);
* try {
* Class<MyInterface> newClass = compiler.compile("com.mypackage.NewClass",
* stringContaininSourceForNewClass, diagnostics, MyInterface);
* MyInterface instance = newClass.newInstance();
* instance.someOperation(someArgs);
* } catch (JavaStringCompilerException e) {
* handle(e);
* } catch (IllegalAccessException e) {
* handle(e);
* }
* </pre>
* <p/>
* The source can be in a String, {@link StringBuffer}, or your own class which
* implements {@link CharSequence}. If you implement your own, it must be
* thread safe (preferably, immutable.)
*
* @author <a href="mailto:David.Biesack@sas.com">David J. Biesack</a>
*/
public class CharSequenceCompiler<T> implements MemoryCompiler {
// Compiler requires source files with a ".java" extension:
static final String JAVA_EXTENSION = ".java";
private final ClassLoaderImpl classLoader;
// The compiler instance that this facade uses.
private final JavaCompiler compiler;
// The compiler options (such as "-target" "1.5").
private final List<String> options;
// collect compiler diagnostics in this instance.
private DiagnosticCollector<JavaFileObject> diagnostics;
// The FileManager which will store source and class "files".
private final FileManagerImpl javaFileManager;
/**
* Construct a new instance which delegates to the named class loader.
*
* @param loader the application ClassLoader. The compiler will look through to
* this // class loader for dependent classes
* @param options The compiler options (such as "-target" "1.5"). See the usage
* for javac
* @throws IllegalStateException if the Java compiler cannot be loaded.
*/
public CharSequenceCompiler(ClassLoader loader, Iterable<String> options) {
compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
throw new IllegalStateException("Cannot find the system Java compiler. "
+ "Check that your class path includes tools.jar");
}
classLoader = new ClassLoaderImpl(loader);
diagnostics = new DiagnosticCollector<>();
final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics,
null, null);
// create our FileManager which chains to the default file manager
// and our ClassLoader
javaFileManager = new FileManagerImpl(fileManager, classLoader);
this.options = new ArrayList<>();
if (options != null) { // make a save copy of input options
for (String option : options) {
this.options.add(option);
}
}
if (loader instanceof URLClassLoader && (!loader.getClass().getName().equals("sun.misc.Launcher$AppClassLoader"))) {
try {
URLClassLoader urlClassLoader = (URLClassLoader) loader;
List<File> path = new ArrayList<File>();
for (URL url : urlClassLoader.getURLs()) {
File file = new File(url.getFile());
path.add(file);
}
fileManager.setLocation(StandardLocation.CLASS_PATH, path);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public synchronized Class<?> compile(String qualifiedClassName, String javaSource) throws Exception {
DiagnosticCollector<JavaFileObject> diagnosticsList = new DiagnosticCollector<>();
if (diagnosticsList != null)
diagnostics = diagnosticsList;
else
diagnostics = new DiagnosticCollector<JavaFileObject>();
Map<String, CharSequence> classes = new HashMap<String, CharSequence>(1);
classes.put(qualifiedClassName, javaSource);
Map<String, Class<T>> compiled = compile(classes, diagnosticsList);
return compiled.get(qualifiedClassName);
}
/**
* Compile Java source in <var>javaSource</name> and return the resulting
* class.
* <p/>
* Thread safety: this method is thread safe if the <var>javaSource</var>
* and <var>diagnosticsList</var> are isolated to this thread.
*
* @param qualifiedClassName The fully qualified class name.
* @param javaSource Complete java source, including a package statement and a class,
* interface, or annotation declaration.
* @param diagnosticsList Any diagnostics generated by compiling the source are added to
* this collector.
* @param types zero or more Class objects representing classes or interfaces
* that the resulting class must be assignable (castable) to.
* @return a Class which is generated by compiling the source
* @throws CharSequenceCompilerException if the source cannot be compiled - for example, if it contains
* syntax or semantic errors or if dependent classes cannot be
* found.
* @throws ClassCastException if the generated class is not assignable to all the optional
* <var>types</var>.
*/
public synchronized Class<T> compile(final String qualifiedClassName,
final CharSequence javaSource,
final DiagnosticCollector<JavaFileObject> diagnosticsList,
final Class<?>... types) throws CharSequenceCompilerException,
ClassCastException {
if (diagnosticsList != null)
diagnostics = diagnosticsList;
else
diagnostics = new DiagnosticCollector<JavaFileObject>();
Map<String, CharSequence> classes = new HashMap<String, CharSequence>(1);
classes.put(qualifiedClassName, javaSource);
Map<String, Class<T>> compiled = compile(classes, diagnosticsList);
Class<T> newClass = compiled.get(qualifiedClassName);
return castable(newClass, types);
}
/**
* Compile multiple Java source strings and return a Map containing the
* resulting classes.
* <p/>
* Thread safety: this method is thread safe if the <var>classes</var> and
* <var>diagnosticsList</var> are isolated to this thread.
*
* @param classes A Map whose keys are qualified class names and whose values are
* the Java source strings containing the definition of the class.
* A map value may be null, indicating that compiled class is
* expected, although no source exists for it (it may be a
* non-public class contained in one of the other strings.)
* @par