Skip to content

Instantly share code, notes, and snippets.

@vshank77
Created November 4, 2014 13:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vshank77/09b2534c60d456835f28 to your computer and use it in GitHub Desktop.
Save vshank77/09b2534c60d456835f28 to your computer and use it in GitHub Desktop.
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;
}
}
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);
}
}
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;
}
}
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);
}
}
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