JavaXT
|
|
Compiler Classpackage javaxt.orm; import java.io.*; import java.net.*; import java.util.*; import javax.tools.*; import java.nio.charset.Charset; import static javaxt.utils.Console.console; //****************************************************************************** //** Compiler //****************************************************************************** /** * Used to compile ORM models into Java classes. The classes are ephemeral * meaning they are not saved anywhere on disk (no class files or jar files). * Instead the classes are stored in memory and are tied to the lifecycle of * the JVM. * ******************************************************************************/ public class Compiler { private Class[] classes; private HashMap<String, SimpleJavaFileObject> outputFiles; private URLClassLoader urlClassLoader; private DiagnosticListener<JavaFileObject> listener = null; private Locale locale = null; private Charset charset = Charset.defaultCharset(); //************************************************************************** //** Constructor //************************************************************************** public Compiler(Model[] models) throws Exception { if (models==null || models.length==0){ classes = new Class[0]; } else{ outputFiles = new HashMap<>(); urlClassLoader = getClassLoader(); //Convert models into an ArrayList ArrayList<Model> arr = new ArrayList<>(); for (Model model : models){ arr.add(model); } //Compile classes ArrayList<Class> classes = new ArrayList<>(); int numErrors = 0; while (classes.size()<models.length){ try{ Model model = arr.get(0); classes.add(compile(model)); arr.remove(0); } catch(Exception e){ numErrors++; //The following logic assumes that the compile error is due //to an ordering issue where one class depends on another //but the dependency hasn't been compiled yet. So we'll //shuffle the list of models and try again. if (arr.size()==1) throw e; String firstClass = getClassName(arr.get(0)); while (true){ Collections.shuffle(arr); String className = getClassName(arr.get(0)); if (!className.equals(firstClass)) break; } } //Safety switch if (numErrors>(models.length*models.length)){ throw new Exception("Failed to compile " + getClassName(arr.get(0))); } } //Convert the class list into an array. Sorts the classes to match //the order of the input models. this.classes = new Class[classes.size()]; for (int i=0; i<models.length; i++){ String className = getClassName(models[i]); for (Class c : classes){ String name = c.getPackageName() + "." + c.getSimpleName(); if (name.equals(className)){ this.classes[i] = c; break; } } } } } //************************************************************************** //** getClasses //************************************************************************** /** Returns classes generated by the compiler for each model */ public Class[] getClasses(){ return classes; } //************************************************************************** //** compile //************************************************************************** /** Used to compile a model and return a class */ private Class compile(Model model) throws Exception { JavaCompiler c = ToolProvider.getSystemJavaCompiler(); String className = getClassName(model); //Create input file object SimpleJavaFileObject src = new SimpleJavaFileObject( URI.create("string:///" + model.getName() + ".java"), JavaFileObject.Kind.SOURCE) { public CharSequence getCharContent(boolean ignoreEncodingErrors) { return model.getJavaCode(); } }; //Create output file object SimpleJavaFileObject cls = new SimpleJavaFileObject( URI.create("runtime:///" + model.getName() + ".class"), JavaFileObject.Kind.CLASS) { private ByteArrayOutputStream out = new ByteArrayOutputStream(); public OutputStream openOutputStream() throws IOException { //console.log("openOutputStream"); return out; } public InputStream openInputStream() throws IOException { //console.log("openInputStream"); return new ByteArrayInputStream(out.toByteArray()); } }; outputFiles.put(className, cls); //Create in-memory file manager StandardJavaFileManager fm = c.getStandardFileManager(listener, locale, charset); JavaFileManager fileManager = new ForwardingJavaFileManager(fm) { public JavaFileObject getJavaFileForOutput( JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { return outputFiles.get(className); } public Iterable list(JavaFileManager.Location location, String packageName, Set kinds, boolean recurse) throws IOException { if (location==StandardLocation.CLASS_PATH){ ArrayList<SimpleJavaFileObject> arr = new ArrayList<>(); Iterator<String> it = outputFiles.keySet().iterator(); while (it.hasNext()){ String name = it.next(); if (name.startsWith(packageName + ".")){ if (!name.equals(className)){ //don't include the current file since it hasn't been compiled yet arr.add(outputFiles.get(name)); } } } if (!arr.isEmpty()) return arr; } return super.list(location, packageName, kinds, recurse); } public String inferBinaryName(JavaFileManager.Location location, JavaFileObject file) { if (location==StandardLocation.CLASS_PATH){ Iterator<String> it = outputFiles.keySet().iterator(); while (it.hasNext()){ String name = it.next(); SimpleJavaFileObject f = outputFiles.get(name); if (file==f) return name; } } return super.inferBinaryName(location, file); } }; //Compile class JavaCompiler.CompilationTask task = c.getTask( null, fileManager, listener, Collections.emptySet(), Collections.emptySet(), Collections.singleton(src) ); if (task.call()) { return urlClassLoader.loadClass(className); } else{ throw new Exception("Failed to compile " + className); } } //************************************************************************** //** getClassLoader //************************************************************************** /** Returns a custom class loader used to find classes created by this class */ private URLClassLoader getClassLoader(){ return new URLClassLoader(new URL[0]){ protected Class<?> findClass(final String name) throws ClassNotFoundException { SimpleJavaFileObject f = outputFiles.get(name); if (f!=null){ try (InputStream is = f.openInputStream()) { ByteArrayOutputStream out = new ByteArrayOutputStream(); int x; byte[] buffer = new byte[256]; while ((x = is.read(buffer, 0, buffer.length)) != -1) { out.write(buffer, 0, x); } out.flush(); byte[] classBytes = out.toByteArray(); return defineClass(name, classBytes, 0, classBytes.length); } catch(Exception e){ e.printStackTrace(); } } return super.findClass(name); } }; } //************************************************************************** //** getClassName //************************************************************************** private String getClassName(Model model){ return model.getPackageName() + "." + model.getName(); } } |