Skip to content

Instantly share code, notes, and snippets.

@pfmiles
Created April 10, 2014 03:31
Show Gist options
  • Save pfmiles/10340430 to your computer and use it in GitHub Desktop.
Save pfmiles/10340430 to your computer and use it in GitHub Desktop.
利用Java compiler API的内存动态编译工具
package xxx;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import javax.tools.FileObject;
import xxx.FlyingCompilationException;
/**
* @author pf-miles 2014-4-6 下午6:14:45
*/
public class DiskJarFile implements FileObject {
private File file;
private URI uri;
public DiskJarFile(String absolutePath){
file = new File(absolutePath);
if (!file.exists()) throw new FlyingCompilationException("Disk jar file: '" + absolutePath
+ "' does not exist.");
this.uri = URI.create("file://" + file.getAbsolutePath());
}
public URI toUri() {
return uri;
}
public String getName() {
return this.file.getName();
}
public InputStream openInputStream() throws IOException {
return new FileInputStream(this.file);
}
public OutputStream openOutputStream() throws IOException {
throw new UnsupportedOperationException();
}
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
throw new UnsupportedOperationException();
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
throw new UnsupportedOperationException();
}
public Writer openWriter() throws IOException {
throw new UnsupportedOperationException();
}
public long getLastModified() {
return this.file.lastModified();
}
public boolean delete() {
throw new UnsupportedOperationException();
}
/**
* 取得磁盘文件绝对路径
*/
public String getAbsolutePath() {
return this.file.getAbsolutePath();
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((uri == null) ? 0 : uri.hashCode());
return result;
}
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
DiskJarFile other = (DiskJarFile) obj;
if (uri == null) {
if (other.uri != null) return false;
} else if (!uri.equals(other.uri)) return false;
return true;
}
}
package xxx;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import xxx.DiskJarFile;
import xxx.JavaSourceFile;
/**
* 将传入的内存java文件结合内存classpath,编译为内存class文件
*
* @author pf-miles 2014-4-3 下午4:29:31
*/
public class FlyingCompileUtil {
/**
* 传入源码以及classpath中的jar包,编译出class文件(内存文件)
*
* @param srcs 源码文件
* @param cpJarFiles 准备放入classpath的jar包(存在于硬盘上),可为null
* @return 编译结果
*/
public static CompilationResult compile(Set<JavaSourceFile> srcs, Set<DiskJarFile> cpJarFiles) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
FlyingFileManager fileManager = new FlyingFileManager(compiler.getStandardFileManager(null, null, null));
try {
List<String> options = Arrays.asList("-classpath", toClassPathStr(cpJarFiles));
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StringWriter out = new StringWriter();
CompilationTask task = compiler.getTask(out, fileManager, diagnostics, options, null, srcs);
if (!task.call()) {
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
out.append("Error on line " + diagnostic.getLineNumber() + " in " + diagnostic).append('\n');
}
return new CompilationResult(out.toString());
} else {
try {
return new CompilationResult(fileManager.list(StandardLocation.CLASS_OUTPUT, null,
new HashSet<Kind>(Arrays.asList(Kind.CLASS)), true));
} catch (IOException e) {
throw new FlyingCompilationException(e);
}
}
} finally {
try {
fileManager.close();
} catch (IOException e) {
throw new FlyingCompilationException(e);
}
}
}
// 生成动编译的classpath,应包括所有传入的jar包
private static String toClassPathStr(Set<DiskJarFile> cpJars) {
Set<String> ps = new HashSet<String>();
if (cpJars != null) for (DiskJarFile f : cpJars) {
ps.add(f.getAbsolutePath());
}
StringBuilder sb = new StringBuilder();
for (String s : ps) {
if (sb.length() != 0) sb.append(File.pathSeparator);
sb.append(s);
}
return sb.toString();
}
}
package xxx;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import org.apache.commons.lang.StringUtils;
import xxx.JavaClassFile;
import xxx.JavaSourceFile;
/**
* 虚拟一个内存中的文件系统,用作动态编译,其结构如下:
*
* <pre>
* &#47;
* \....src/
* \....target/
* </pre>
*
* 其中,src是源码目录,target是编译好的class文件目录
*
* @author pf-miles 2014-4-6 下午4:29:14
*/
public class FlyingFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
// all source files, in 'src/ directory'
private LinkedHashMap<String, Object> srcs = new LinkedHashMap<String, Object>();
// all compiled class files, in 'target/ directory'
private LinkedHashMap<String, Object> classes = new LinkedHashMap<String, Object>();
protected FlyingFileManager(StandardJavaFileManager fileManager){
super(fileManager);
}
public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse)
throws IOException {
if (location instanceof StandardLocation) {
switch ((StandardLocation) location) {
case CLASS_OUTPUT:
if (!kinds.contains(Kind.CLASS)) return Collections.emptyList();
return listMemFile(this.classes, packageName, recurse);
case CLASS_PATH:
LinkedHashSet<JavaFileObject> fs = new LinkedHashSet<JavaFileObject>();
if (kinds.contains(Kind.CLASS)) {
fs = listMemFile(this.classes, packageName, recurse);
}
for (JavaFileObject jfo : super.list(location, packageName, kinds, recurse)) {
fs.add(jfo);
}
return fs;
case SOURCE_OUTPUT:
case SOURCE_PATH:
if (!kinds.contains(Kind.SOURCE)) return Collections.emptyList();
return listMemFile(this.srcs, packageName, recurse);
case ANNOTATION_PROCESSOR_PATH:
case PLATFORM_CLASS_PATH:
default:
return super.list(location, packageName, kinds, recurse);
}
} else {
return Collections.emptyList();
}
}
@SuppressWarnings("unchecked")
private LinkedHashSet<JavaFileObject> listMemFile(LinkedHashMap<String, Object> memFiles, String pkg,
boolean recurse) {
LinkedHashSet<JavaFileObject> ret = new LinkedHashSet<JavaFileObject>();
// cd pkgPath
Deque<String> pkgPath = resolvePkgPath(pkg);
while (!pkgPath.isEmpty()) {
Object f = memFiles.get(pkgPath.pop());
if (f == null || !(f instanceof Map)) {
return ret;
} else {
memFiles = (LinkedHashMap<String, Object>) f;
}
}
List<LinkedHashMap<String, Object>> subDirs = new ArrayList<LinkedHashMap<String, Object>>();
for (Map.Entry<String, Object> e : memFiles.entrySet()) {
Object f = e.getValue();
if (f instanceof JavaFileObject) {
ret.add((JavaFileObject) f);
} else {
subDirs.add((LinkedHashMap<String, Object>) f);
}
}
if (recurse) ret.addAll(dumpJavaFileObjects(subDirs));
return ret;
}
@SuppressWarnings("unchecked")
private LinkedHashSet<JavaFileObject> dumpJavaFileObjects(List<LinkedHashMap<String, Object>> dirs) {
LinkedHashSet<JavaFileObject> ret = new LinkedHashSet<JavaFileObject>();
for (LinkedHashMap<String, Object> dir : dirs) {
for (Map.Entry<String, Object> e : dir.entrySet()) {
if (e.getValue() instanceof JavaFileObject) {
ret.add((JavaFileObject) e.getValue());
} else {
ret.addAll(dumpJavaFileObjects(Arrays.asList((LinkedHashMap<String, Object>) e.getValue())));
}
}
}
return ret;
}
private Deque<String> resolvePkgPath(String pkg) {
Deque<String> ret = new ArrayDeque<String>();
if (pkg != null) for (String s : pkg.split("\\.")) {
ret.add(s);
}
return ret;
}
public String inferBinaryName(Location location, JavaFileObject file) {
if (StandardLocation.CLASS_OUTPUT.equals(location) || StandardLocation.CLASS_PATH.equals(location)
|| StandardLocation.SOURCE_OUTPUT.equals(location) || StandardLocation.SOURCE_PATH.equals(location)) {
if (file instanceof JavaSourceFile) {
return null;
} else if (file instanceof JavaClassFile) {
return ((JavaClassFile) file).getBinaryClassName();
}
}
return super.inferBinaryName(location, file);
}
public boolean isSameFile(FileObject a, FileObject b) {
if (a == b) return true;
Class<?> ac = a.getClass();
Class<?> bc = b.getClass();
if (ac.equals(bc)) {
if (JavaClassFile.class.equals(ac) || JavaSourceFile.class.equals(ac)) {
return a.equals(b);
} else {
return super.isSameFile(a, b);
}
} else {
return false;
}
}
public boolean hasLocation(Location location) {
if (!(location instanceof StandardLocation)) {
return false;
}
switch ((StandardLocation) location) {
case CLASS_OUTPUT:
case SOURCE_OUTPUT:
case CLASS_PATH:
case SOURCE_PATH:
return true;
case ANNOTATION_PROCESSOR_PATH:
case PLATFORM_CLASS_PATH:
default:
return false;
}
}
public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException {
if (Kind.SOURCE != kind && Kind.CLASS != kind) return null;
if (!(location instanceof StandardLocation)) return null;
switch ((StandardLocation) location) {
case CLASS_OUTPUT:
return this.getMemFileByClassName(className, classes);
case CLASS_PATH:
JavaFileObject f = this.getMemFileByClassName(className, classes);
if (f == null) {
return super.getJavaFileForInput(location, className, kind);
} else {
return f;
}
case SOURCE_OUTPUT:
case SOURCE_PATH:
return this.getMemFileByClassName(className, srcs);
case ANNOTATION_PROCESSOR_PATH:
case PLATFORM_CLASS_PATH:
default:
return null;
}
}
// 根据类名获取内存文件,找不到则返回null
@SuppressWarnings("unchecked")
private JavaFileObject getMemFileByClassName(String className, LinkedHashMap<String, Object> memFiles) {
if (className.contains(".")) {
String pkg = StringUtils.substringBeforeLast(className, ".");
// cd pkgPath
Deque<String> pkgPath = resolvePkgPath(pkg);
while (!pkgPath.isEmpty()) {
Object f = memFiles.get(pkgPath.pop());
if (f == null || !(f instanceof Map)) {
return null;
} else {
memFiles = (LinkedHashMap<String, Object>) f;
}
}
Object o = memFiles.get(StringUtils.substringAfterLast(className, "."));
if (o instanceof JavaFileObject) {
return (JavaFileObject) o;
} else {
return null;
}
} else {
Object o = memFiles.get(className);
if (o instanceof JavaFileObject) {
return (JavaFileObject) o;
} else {
return null;
}
}
}
public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling)
throws IOException {
if (Kind.CLASS != kind && Kind.SOURCE != kind) throw new IOException("Unsupported output kind: " + kind);
if (!(location instanceof StandardLocation)) throw new IOException("Unsupported output location: " + location);
switch ((StandardLocation) location) {
case CLASS_OUTPUT:
return getOrCreateMemFileByClassName(className, this.classes, JavaClassFile.class);
case SOURCE_OUTPUT:
return getOrCreateMemFileByClassName(className, this.srcs, JavaSourceFile.class);
case CLASS_PATH:
case SOURCE_PATH:
case ANNOTATION_PROCESSOR_PATH:
case PLATFORM_CLASS_PATH:
default:
throw new IOException("Unsupported output location: " + location);
}
}
// 获取一个内存文件用作输出,找不到则新建
@SuppressWarnings("unchecked")
private JavaFileObject getOrCreateMemFileByClassName(String className, LinkedHashMap<String, Object> memFiles,
Class<? extends JavaFileObject> fileType) {
if (className.contains(".")) {
String pkg = StringUtils.substringBeforeLast(className, ".");
// cd pkgPath
Deque<String> pkgPath = resolvePkgPath(pkg);
while (!pkgPath.isEmpty()) {
String cur = pkgPath.pop();
Object f = memFiles.get(cur);
if (f != null && !(f instanceof Map)) {
throw new FlyingCompilationException("Conflict class & pkg name: " + className + ", at: " + cur);
} else if (f == null) {
LinkedHashMap<String, Object> dir = new LinkedHashMap<String, Object>();
memFiles.put(cur, dir);
memFiles = dir;
} else {
memFiles = (LinkedHashMap<String, Object>) f;
}
}
String simpleName = StringUtils.substringAfterLast(className, ".");
Object o = memFiles.get(simpleName);
if (o != null && !(o instanceof JavaFileObject)) {
throw new FlyingCompilationException("Conflict class & pkg name: " + className + ", at: " + simpleName);
} else if (o == null) {
JavaFileObject obj = null;
if (JavaSourceFile.class.equals(fileType)) {
obj = new JavaSourceFile(simpleName + ".java", pkg, "");
} else {
obj = new JavaClassFile(simpleName + ".class", pkg, null);
}
memFiles.put(simpleName, obj);
return obj;
} else {
return (JavaFileObject) o;
}
} else {
Object o = memFiles.get(className);
if (o != null && !(o instanceof JavaFileObject)) {
throw new FlyingCompilationException("Conflict class & pkg name: " + className + ", at: " + className);
} else if (o == null) {
JavaFileObject obj = null;
if (JavaSourceFile.class.equals(fileType)) {
obj = new JavaSourceFile(className + ".java", "", "");
} else {
obj = new JavaClassFile(className + ".class", "", null);
}
memFiles.put(className, obj);
return obj;
} else {
return (JavaFileObject) o;
}
}
}
public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
if (location instanceof StandardLocation) {
switch ((StandardLocation) location) {
case CLASS_OUTPUT:
if (relativeName.endsWith(".class")) {
return this.getJavaFileForInput(location, resolveClassName(packageName, relativeName),
Kind.CLASS);
} else {
return null;
}
case CLASS_PATH:
if (relativeName.endsWith(".class")) {
JavaFileObject f = this.getJavaFileForInput(location,
resolveClassName(packageName, relativeName),
Kind.CLASS);
if (f == null) {
return super.getFileForInput(location, packageName, relativeName);
} else {
return f;
}
} else {
return super.getFileForInput(location, packageName, relativeName);
}
case SOURCE_OUTPUT:
case SOURCE_PATH:
if (relativeName.endsWith(".java")) {
return this.getJavaFileForInput(location, resolveClassName(packageName, relativeName),
Kind.SOURCE);
} else {
return null;
}
case ANNOTATION_PROCESSOR_PATH:
case PLATFORM_CLASS_PATH:
default:
return super.getFileForInput(location, packageName, relativeName);
}
} else {
return super.getFileForInput(location, packageName, relativeName);
}
}
private String resolveClassName(String pkg, String fileName) {
if (StringUtils.isNotBlank(pkg)) {
return pkg + "." + StringUtils.substringBeforeLast(fileName, ".");
} else {
return StringUtils.substringBeforeLast(fileName, ".");
}
}
public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling)
throws IOException {
if (!(location instanceof StandardLocation)) throw new IOException("Unsupported output location: " + location);
switch ((StandardLocation) location) {
case CLASS_OUTPUT:
if (relativeName.endsWith(".class")) {
return this.getJavaFileForOutput(location, resolveClassName(packageName, relativeName), Kind.CLASS,
sibling);
} else {
throw new IOException("Unsupported output file type in class output location: " + relativeName);
}
case SOURCE_OUTPUT:
if (relativeName.endsWith(".java")) {
return this.getJavaFileForOutput(location, resolveClassName(packageName, relativeName),
Kind.SOURCE, sibling);
} else {
throw new IOException("Unsupported output file type in source output location: " + relativeName);
}
case CLASS_PATH:
case SOURCE_PATH:
case ANNOTATION_PROCESSOR_PATH:
case PLATFORM_CLASS_PATH:
default:
throw new IOException("Unsupported output location: " + location);
}
}
}
package xxx;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import javax.tools.SimpleJavaFileObject;
import org.apache.commons.lang.StringUtils;
/**
* A compiled java class file in memory.
*
* @author pf-miles 2014-4-3 下午3:20:03
*/
public class JavaClassFile extends SimpleJavaFileObject {
private String fileName;
private String binaryName;
private byte[] data;
public JavaClassFile(String fileName, String pkg, byte[] data){
super(genMemFileUri(fileName, pkg), Kind.CLASS);
this.fileName = fileName;
this.data = data;
if (StringUtils.isNotBlank(pkg)) {
this.binaryName = pkg + "." + StringUtils.substringBeforeLast(fileName, ".");
} else {
this.binaryName = StringUtils.substringBeforeLast(fileName, ".");
}
}
private static URI genMemFileUri(String fileName, String pkg) {
String pkgPath = "";
if (StringUtils.isNotBlank(pkg)) pkgPath = pkg.replaceAll("\\.", "/") + "/";
return URI.create("mem:///target/" + pkgPath + fileName);
}
public String getName() {
return this.fileName;
}
public InputStream openInputStream() throws IOException {
return new ByteArrayInputStream(this.data);
}
public OutputStream openOutputStream() throws IOException {
return new ByteArrayOutputStream() {
public void close() throws IOException {
super.close();
JavaClassFile.this.data = this.toByteArray();
}
};
}
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
throw new UnsupportedOperationException();
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
throw new UnsupportedOperationException();
}
public Writer openWriter() throws IOException {
throw new UnsupportedOperationException();
}
public boolean delete() {
throw new UnsupportedOperationException();
}
public String toString() {
return uri.toString();
}
public String getBinaryClassName() {
return this.binaryName;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((uri == null) ? 0 : uri.hashCode());
return result;
}
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
DiskJarFile other = (DiskJarFile) obj;
if (uri == null) {
if (other.toUri() != null) return false;
} else if (!uri.equals(other.toUri())) return false;
return true;
}
}
package xxx;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.net.URI;
import javax.tools.SimpleJavaFileObject;
import org.apache.commons.lang.StringUtils;
public class JavaSourceFile extends SimpleJavaFileObject {
private String fileName;
private String code;
/**
* 创建内存java源码文件
*
* @param fileName 文件名
* @param pkg 包名
* @param code 代码
*/
public JavaSourceFile(String fileName, String pkg, String code){
super(genMemFileUri(fileName, pkg), Kind.SOURCE);
this.fileName = fileName;
this.code = code;
}
private static URI genMemFileUri(String fileName, String pkg) {
String pkgPath = "";
if (StringUtils.isNotBlank(pkg)) pkgPath = pkg.replaceAll("\\.", "/") + "/";
return URI.create("mem:///src/" + pkgPath + fileName);
}
public String getName() {
return this.fileName;
}
public InputStream openInputStream() throws IOException {
return new ByteArrayInputStream(this.code.getBytes());
}
public OutputStream openOutputStream() throws IOException {
throw new UnsupportedOperationException();
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return this.code;
}
public Writer openWriter() throws IOException {
throw new UnsupportedOperationException();
}
public boolean delete() {
throw new UnsupportedOperationException();
}
public String toString() {
return uri.toString();
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((uri == null) ? 0 : uri.hashCode());
return result;
}
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
DiskJarFile other = (DiskJarFile) obj;
if (uri == null) {
if (other.toUri() != null) return false;
} else if (!uri.equals(other.toUri())) return false;
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment