Created
November 4, 2014 13:41
-
-
Save vshank77/09b2534c60d456835f28 to your computer and use it in GitHub Desktop.
Code Compilation inspired by http://www.ibm.com/developerworks/java/library/j-jcomp/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.google.common.collect.ImmutableMap; | |
import javax.tools.*; | |
import java.net.URI; | |
import java.net.URISyntaxException; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import static com.google.common.base.Preconditions.checkNotNull; | |
import static com.google.common.collect.Lists.newArrayList; | |
import static java.util.Arrays.asList; | |
import static javax.tools.JavaCompiler.CompilationTask; | |
/** | |
* inspired by http://www.ibm.com/developerworks/java/library/j-jcomp/ | |
*/ | |
public final class CharSequenceCompiler<T> { | |
static final String JAVA_EXTENSION = ".java"; | |
private final JavaCompiler compiler; | |
private final ClassLoaderImpl classLoader; | |
private final FileManagerImpl javaFileManager; | |
private final List<String> options; | |
public CharSequenceCompiler(String... options) { | |
compiler = checkNotNull(ToolProvider.getSystemJavaCompiler(), "unable to find system jdk compiler"); | |
classLoader = new ClassLoaderImpl(CharSequenceCompiler.class.getClassLoader()); | |
javaFileManager = new FileManagerImpl(compiler.getStandardFileManager( | |
nonNullDiag(null), null, null), classLoader); | |
this.options = newArrayList(asList(options)); | |
} | |
public Class<T> compile(String qualifiedClassName, CharSequence javaSource, DiagnosticCollector<JavaFileObject> | |
diagnostics, Class<?>... assignables) { | |
Map<String, Class<T>> compiled = compile(ImmutableMap.of(qualifiedClassName, javaSource), diagnostics); | |
Class<T> newClass = compiled.get(qualifiedClassName); | |
return castable(newClass, assignables); | |
} | |
public synchronized Map<String, Class<T>> compile(Map<String, CharSequence> classes, | |
final DiagnosticCollector<JavaFileObject> diagnostics) { | |
List<JavaFileObject> sources = new ArrayList<>(); | |
for (Map.Entry<String, CharSequence> entry : classes.entrySet()) { | |
String qualifiedClassName = entry.getKey(); | |
CharSequence javaSource = entry.getValue(); | |
if (javaSource != null) { | |
final int dotPos = qualifiedClassName.lastIndexOf('.'); | |
final String className = dotPos == -1 ? qualifiedClassName : qualifiedClassName.substring(dotPos + 1); | |
final String packageName = dotPos == -1 ? "" : qualifiedClassName.substring(0, dotPos); | |
final JavaFileObjectImpl source = new JavaFileObjectImpl(className, javaSource); | |
sources.add(source); | |
javaFileManager.putFileForInput(StandardLocation.SOURCE_PATH, packageName, | |
className + JAVA_EXTENSION, source); | |
} | |
} | |
final CompilationTask task = compiler.getTask(null, javaFileManager, nonNullDiag(diagnostics), | |
options, null, sources); | |
final Boolean result = task.call(); | |
if (result == null || !result) { | |
throw new CompileException("Compilation failed", classes.keySet(), diagnostics); | |
} | |
try { | |
Map<String, Class<T>> compiled = new HashMap<>(); | |
for (String qualifiedClassName : classes.keySet()) { | |
final Class<T> newClass = loadClass(qualifiedClassName); | |
compiled.put(qualifiedClassName, newClass); | |
} | |
return compiled; | |
} catch (Exception ex) { | |
throw new CompileException(ex.getMessage(), ex, classes.keySet(), diagnostics); | |
} | |
} | |
@SuppressWarnings("unchecked") | |
public Class<T> loadClass(final String qualifiedClassName) | |
throws ClassNotFoundException { | |
return (Class<T>) classLoader.loadClass(qualifiedClassName); | |
} | |
static URI toURI(String name) { | |
try { | |
return new URI(name); | |
} catch (URISyntaxException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
private static <T> Class<T> castable(Class<T> newClass, Class<?>... types) | |
throws ClassCastException { | |
for (Class<?> type : types) | |
if (!type.isAssignableFrom(newClass)) { | |
throw new ClassCastException(type.getName()); | |
} | |
return newClass; | |
} | |
private static DiagnosticCollector<JavaFileObject> nonNullDiag(DiagnosticCollector<JavaFileObject> obj) { | |
return (obj == null) ? new DiagnosticCollector<JavaFileObject>() : obj; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.tools.JavaFileObject; | |
import java.io.ByteArrayInputStream; | |
import java.io.InputStream; | |
import java.util.Collection; | |
import java.util.Collections; | |
import java.util.Map; | |
import java.util.concurrent.ConcurrentHashMap; | |
final class ClassLoaderImpl extends ClassLoader { | |
private final Map<String, JavaFileObject> classes = new ConcurrentHashMap<>(); | |
public ClassLoaderImpl(final ClassLoader parentClassLoader) { | |
super(parentClassLoader); | |
} | |
public void add(String qualifiedClassName, JavaFileObject javaFile) { | |
classes.put(qualifiedClassName, javaFile); | |
} | |
public Collection<JavaFileObject> classes() { | |
return Collections.unmodifiableCollection(classes.values()); | |
} | |
@Override | |
protected Class<?> findClass(final String qualifiedClassName) throws ClassNotFoundException { | |
JavaFileObject file = classes.get(qualifiedClassName); | |
if (file != null) { | |
byte[] bytes = ((JavaFileObjectImpl) file).getByteCode(); | |
return defineClass(qualifiedClassName, bytes, 0, bytes.length); | |
} | |
try { | |
return Class.forName(qualifiedClassName); | |
} catch (ClassNotFoundException nf) { | |
return super.findClass(qualifiedClassName); | |
} | |
} | |
@Override | |
public InputStream getResourceAsStream(final String name) { | |
if (name.endsWith(".class")) { | |
String qualifiedClassName = name.substring(0, | |
name.length() - ".class".length()).replace('/', '.'); | |
JavaFileObjectImpl file = (JavaFileObjectImpl) classes.get(qualifiedClassName); | |
if (file != null) { | |
return new ByteArrayInputStream(file.getByteCode()); | |
} | |
} | |
return super.getResourceAsStream(name); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.google.common.collect.ImmutableSet; | |
import lombok.Getter; | |
import lombok.experimental.Accessors; | |
import javax.tools.DiagnosticCollector; | |
import javax.tools.JavaFileObject; | |
import java.util.Set; | |
@Getter | |
@Accessors(fluent = true) | |
@SuppressWarnings("serial") | |
public class CompileException extends RuntimeException { | |
private final Set<String> classes; | |
private final DiagnosticCollector<JavaFileObject> diagnostics; | |
public CompileException(String msg, Set<String> | |
classes, DiagnosticCollector<JavaFileObject> diagnostics) { | |
super(msg); | |
this.classes = ImmutableSet.copyOf(classes); | |
this.diagnostics = diagnostics; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.google.common.collect.Iterables; | |
import lombok.Getter; | |
import lombok.experimental.Accessors; | |
import javax.tools.*; | |
import java.io.IOException; | |
import java.net.URI; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Set; | |
import java.util.concurrent.ConcurrentHashMap; | |
import java.util.concurrent.ConcurrentMap; | |
import static javax.tools.JavaFileObject.Kind; | |
@Accessors(fluent = true) | |
final class FileManagerImpl extends ForwardingJavaFileManager<JavaFileManager> { | |
@Getter | |
private final ClassLoaderImpl classLoader; | |
private final ConcurrentMap<URI, JavaFileObject> fileObjects = new ConcurrentHashMap<>(); | |
public FileManagerImpl(JavaFileManager fileManager, ClassLoaderImpl classLoader) { | |
super(fileManager); | |
this.classLoader = classLoader; | |
} | |
public void putFileForInput(StandardLocation location, String packageName, String | |
relativeName, JavaFileObject file) { | |
fileObjects.putIfAbsent(uri(location, packageName, relativeName), file); | |
} | |
@Override | |
public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { | |
FileObject object = fileObjects.get(uri(location, packageName, relativeName)); | |
return (object != null) ? object : super.getFileForInput(location, packageName, relativeName); | |
} | |
@Override | |
public JavaFileObject getJavaFileForOutput(Location location, String qualifiedName, Kind | |
kind, FileObject outputFile) throws IOException { | |
JavaFileObject file = new JavaFileObjectImpl(qualifiedName, kind); | |
classLoader.add(qualifiedName, file); | |
return file; | |
} | |
@Override | |
public ClassLoader getClassLoader(Location location) { | |
return classLoader; | |
} | |
@Override | |
public String inferBinaryName(Location loc, JavaFileObject file) { | |
return (file instanceof JavaFileObjectImpl) ? file.getName() : super.inferBinaryName(loc, file); | |
} | |
@Override | |
public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> | |
kinds, boolean recurse) throws IOException { | |
List<JavaFileObject> result = new ArrayList<>(); | |
if (location == StandardLocation.CLASS_PATH && kinds.contains(Kind.CLASS)) { | |
for (JavaFileObject file : fileObjects.values()) { | |
if (file.getKind() == Kind.CLASS && file.getName().startsWith(packageName)) | |
result.add(file); | |
} | |
result.addAll(classLoader.classes()); | |
} else if (location == StandardLocation.SOURCE_PATH && kinds.contains(Kind.SOURCE)) { | |
for (JavaFileObject file : fileObjects.values()) { | |
if (file.getKind() == Kind.SOURCE && file.getName().startsWith(packageName)) | |
result.add(file); | |
} | |
} | |
Iterables.addAll(result, super.list(location, packageName, kinds, recurse)); | |
return result; | |
} | |
private static URI uri(Location location, String packageName, String relativeName) { | |
return CharSequenceCompiler.toURI(location.getName() + '/' + packageName + '/' + relativeName); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.tools.SimpleJavaFileObject; | |
import java.io.ByteArrayInputStream; | |
import java.io.ByteArrayOutputStream; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import static com.google.common.base.Preconditions.checkNotNull; | |
import static cuttlefish.generator.CharSequenceCompiler.JAVA_EXTENSION; | |
final class JavaFileObjectImpl extends SimpleJavaFileObject { | |
private final ByteArrayOutputStream byteCode = new ByteArrayOutputStream(); | |
private final CharSequence source; | |
public JavaFileObjectImpl(final String baseName, final CharSequence source) { | |
super(CharSequenceCompiler.toURI(baseName + JAVA_EXTENSION), Kind.SOURCE); | |
this.source = source; | |
} | |
public JavaFileObjectImpl(final String name, final Kind kind) { | |
super(CharSequenceCompiler.toURI(name), kind); | |
this.source = null; | |
} | |
public byte[] getByteCode() { | |
return byteCode.toByteArray(); | |
} | |
@Override | |
public CharSequence getCharContent(final boolean ignoreEncodingErrors) { | |
return checkNotNull(source, "source null in getCharContent()"); | |
} | |
@Override | |
public InputStream openInputStream() { | |
return new ByteArrayInputStream(getByteCode()); | |
} | |
@Override | |
public OutputStream openOutputStream() { | |
return byteCode; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment