|
|
@@ -2,6 +2,9 @@ package cn.hutool.core.compiler;
|
|
|
|
|
|
import cn.hutool.core.io.FileUtil;
|
|
|
import cn.hutool.core.io.IoUtil;
|
|
|
+import cn.hutool.core.io.resource.FileResource;
|
|
|
+import cn.hutool.core.io.resource.Resource;
|
|
|
+import cn.hutool.core.io.resource.StringResource;
|
|
|
import cn.hutool.core.map.MapUtil;
|
|
|
import cn.hutool.core.util.ArrayUtil;
|
|
|
import cn.hutool.core.util.CharsetUtil;
|
|
|
@@ -11,7 +14,6 @@ import cn.hutool.core.util.URLUtil;
|
|
|
|
|
|
import javax.tools.DiagnosticCollector;
|
|
|
import javax.tools.JavaCompiler.CompilationTask;
|
|
|
-import javax.tools.JavaFileManager;
|
|
|
import javax.tools.JavaFileObject;
|
|
|
import javax.tools.StandardLocation;
|
|
|
import java.io.File;
|
|
|
@@ -21,22 +23,44 @@ import java.util.ArrayList;
|
|
|
import java.util.Arrays;
|
|
|
import java.util.Collection;
|
|
|
import java.util.Collections;
|
|
|
-import java.util.LinkedHashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
* Java 源码编译器
|
|
|
+ * <p>通过此类可以动态编译java源码,并加载到ClassLoader,从而动态获取加载的类。</p>
|
|
|
+ * <p>JavaSourceCompiler支持加载的源码类型包括:</p>
|
|
|
+ * <ul>
|
|
|
+ * <li>源码文件</li>
|
|
|
+ * <li>源码文件源码字符串</li>
|
|
|
+ * </ul>
|
|
|
+ *
|
|
|
+ * <p>使用方法如下:</p>
|
|
|
+ * <pre>
|
|
|
+ * ClassLoader classLoader = JavaSourceCompiler.create(null)
|
|
|
+ * .addSource(FileUtil.file("test-compile/b/B.java"))
|
|
|
+ * .addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java"))
|
|
|
+ * // 增加编译依赖的类库
|
|
|
+ * .addLibrary(libFile)
|
|
|
+ * .compile();
|
|
|
+ * Class<?> clazz = classLoader.loadClass("c.C");
|
|
|
+ * </pre>
|
|
|
*
|
|
|
* @author lzpeng
|
|
|
*/
|
|
|
public class JavaSourceCompiler {
|
|
|
|
|
|
/**
|
|
|
- * 待编译的文件 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
|
|
|
+ * 待编译的资源,支持:
|
|
|
+ *
|
|
|
+ * <ul>
|
|
|
+ * <li>源码字符串,使用{@link StringResource}</li>
|
|
|
+ * <li>源码文件、源码jar包或源码zip包,亦或者文件夹,使用{@link FileResource}</li>
|
|
|
+ * </ul>
|
|
|
+ * 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
|
|
|
*/
|
|
|
- private final List<File> sourceFileList = new ArrayList<>();
|
|
|
+ private final List<Resource> sourceList = new ArrayList<>();
|
|
|
|
|
|
/**
|
|
|
* 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包
|
|
|
@@ -44,15 +68,19 @@ public class JavaSourceCompiler {
|
|
|
private final List<File> libraryFileList = new ArrayList<>();
|
|
|
|
|
|
/**
|
|
|
- * 源码映射 key: 类名 value: 类源码
|
|
|
- */
|
|
|
- private final Map<String, String> sourceCodeMap = new LinkedHashMap<>();
|
|
|
-
|
|
|
- /**
|
|
|
* 编译类时使用的父类加载器
|
|
|
*/
|
|
|
private final ClassLoader parentClassLoader;
|
|
|
|
|
|
+ /**
|
|
|
+ * 创建Java源码编译器
|
|
|
+ *
|
|
|
+ * @param parent 父类加载器
|
|
|
+ * @return Java源码编译器
|
|
|
+ */
|
|
|
+ public static JavaSourceCompiler create(ClassLoader parent) {
|
|
|
+ return new JavaSourceCompiler(parent);
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
* 构造
|
|
|
@@ -63,27 +91,32 @@ public class JavaSourceCompiler {
|
|
|
this.parentClassLoader = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil.getClassLoader());
|
|
|
}
|
|
|
|
|
|
-
|
|
|
/**
|
|
|
- * 创建Java源码编译器
|
|
|
+ * 向编译器中加入待编译的资源<br>
|
|
|
+ * 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
|
|
*
|
|
|
- * @param parent 父类加载器
|
|
|
+ * @param resources 待编译的资源,支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
|
|
* @return Java源码编译器
|
|
|
*/
|
|
|
- public static JavaSourceCompiler create(ClassLoader parent) {
|
|
|
- return new JavaSourceCompiler(parent);
|
|
|
+ public JavaSourceCompiler addSource(Resource... resources) {
|
|
|
+ if (ArrayUtil.isNotEmpty(resources)) {
|
|
|
+ this.sourceList.addAll(Arrays.asList(resources));
|
|
|
+ }
|
|
|
+ return this;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
/**
|
|
|
- * 向编译器中加入待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
|
|
+ * 向编译器中加入待编译的文件<br>
|
|
|
+ * 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
|
|
*
|
|
|
* @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
|
|
* @return Java源码编译器
|
|
|
*/
|
|
|
- public JavaSourceCompiler addSource(final File... files) {
|
|
|
+ public JavaSourceCompiler addSource(File... files) {
|
|
|
if (ArrayUtil.isNotEmpty(files)) {
|
|
|
- this.sourceFileList.addAll(Arrays.asList(files));
|
|
|
+ for (File file : files) {
|
|
|
+ this.sourceList.add(new FileResource(file));
|
|
|
+ }
|
|
|
}
|
|
|
return this;
|
|
|
}
|
|
|
@@ -94,36 +127,36 @@ public class JavaSourceCompiler {
|
|
|
* @param sourceCodeMap 源码Map key: 类名 value 源码
|
|
|
* @return Java源码编译器
|
|
|
*/
|
|
|
- public JavaSourceCompiler addSource(final Map<String, String> sourceCodeMap) {
|
|
|
+ public JavaSourceCompiler addSource(Map<String, String> sourceCodeMap) {
|
|
|
if (MapUtil.isNotEmpty(sourceCodeMap)) {
|
|
|
- this.sourceCodeMap.putAll(sourceCodeMap);
|
|
|
+ sourceCodeMap.forEach(this::addSource);
|
|
|
}
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 加入编译Java源码时所需要的jar包
|
|
|
+ * 向编译器中加入待编译的源码
|
|
|
*
|
|
|
- * @param files 编译Java源码时所需要的jar包
|
|
|
- * @return Java源码编译器
|
|
|
+ * @param className 类名
|
|
|
+ * @param sourceCode 源码
|
|
|
+ * @return Java文件编译器
|
|
|
*/
|
|
|
- public JavaSourceCompiler addLibrary(final File... files) {
|
|
|
- if (ArrayUtil.isNotEmpty(files)) {
|
|
|
- this.libraryFileList.addAll(Arrays.asList(files));
|
|
|
+ public JavaSourceCompiler addSource(String className, String sourceCode) {
|
|
|
+ if (className != null && sourceCode != null) {
|
|
|
+ this.sourceList.add(new StringResource(sourceCode, className));
|
|
|
}
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 向编译器中加入待编译的源码Map
|
|
|
+ * 加入编译Java源码时所需要的jar包,jar包中必须为字节码
|
|
|
*
|
|
|
- * @param className 类名
|
|
|
- * @param sourceCode 源码
|
|
|
- * @return Java文件编译器
|
|
|
+ * @param files 编译Java源码时所需要的jar包
|
|
|
+ * @return Java源码编译器
|
|
|
*/
|
|
|
- public JavaSourceCompiler addSource(final String className, final String sourceCode) {
|
|
|
- if (className != null && sourceCode != null) {
|
|
|
- this.sourceCodeMap.put(className, sourceCode);
|
|
|
+ public JavaSourceCompiler addLibrary(File... files) {
|
|
|
+ if (ArrayUtil.isNotEmpty(files)) {
|
|
|
+ this.libraryFileList.addAll(Arrays.asList(files));
|
|
|
}
|
|
|
return this;
|
|
|
}
|
|
|
@@ -138,15 +171,13 @@ public class JavaSourceCompiler {
|
|
|
final List<File> classPath = getClassPath();
|
|
|
final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0]));
|
|
|
final URLClassLoader ucl = URLClassLoader.newInstance(urLs, this.parentClassLoader);
|
|
|
- if (sourceCodeMap.isEmpty() && sourceFileList.isEmpty()) {
|
|
|
- // 没有需要编译的源码
|
|
|
+ if (sourceList.isEmpty()) {
|
|
|
+ // 没有需要编译的源码文件返回加载zip或jar包的类加载器
|
|
|
return ucl;
|
|
|
}
|
|
|
|
|
|
- // 没有需要编译的源码文件返回加载zip或jar包的类加载器
|
|
|
-
|
|
|
// 创建编译器
|
|
|
- final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, CompilerUtil.getFileManager());
|
|
|
+ final JavaClassFileManager javaFileManager = new JavaClassFileManager(ucl, CompilerUtil.getFileManager());
|
|
|
|
|
|
// classpath
|
|
|
final List<String> options = new ArrayList<>();
|
|
|
@@ -160,7 +191,7 @@ public class JavaSourceCompiler {
|
|
|
final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
|
|
|
final List<JavaFileObject> javaFileObjectList = getJavaFileObject();
|
|
|
final CompilationTask task = CompilerUtil.getTask(javaFileManager, diagnosticCollector, options, javaFileObjectList);
|
|
|
- try{
|
|
|
+ try {
|
|
|
if (task.call()) {
|
|
|
// 加载编译后的类
|
|
|
return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
|
|
|
@@ -180,7 +211,7 @@ public class JavaSourceCompiler {
|
|
|
private List<File> getClassPath() {
|
|
|
List<File> classPathFileList = new ArrayList<>();
|
|
|
for (File file : libraryFileList) {
|
|
|
- List<File> jarOrZipFile = FileUtil.loopFiles(file, (subFile)-> JavaFileObjectUtil.isJarOrZipFile(subFile.getName()));
|
|
|
+ List<File> jarOrZipFile = FileUtil.loopFiles(file, (subFile) -> JavaFileObjectUtil.isJarOrZipFile(subFile.getName()));
|
|
|
classPathFileList.addAll(jarOrZipFile);
|
|
|
if (file.isDirectory()) {
|
|
|
classPathFileList.add(file);
|
|
|
@@ -195,16 +226,18 @@ public class JavaSourceCompiler {
|
|
|
* @return 待编译的Java文件对象
|
|
|
*/
|
|
|
private List<JavaFileObject> getJavaFileObject() {
|
|
|
- final List<JavaFileObject> collection = new ArrayList<>();
|
|
|
+ final List<JavaFileObject> list = new ArrayList<>();
|
|
|
|
|
|
- // 源码文件
|
|
|
- for (File file : sourceFileList) {
|
|
|
- FileUtil.walkFiles(file, (subFile)-> collection.addAll(JavaFileObjectUtil.getJavaFileObjects(file)));
|
|
|
+ for (Resource resource : this.sourceList) {
|
|
|
+ if (resource instanceof FileResource) {
|
|
|
+ final File file = ((FileResource) resource).getFile();
|
|
|
+ FileUtil.walkFiles(file, (subFile) -> list.addAll(JavaFileObjectUtil.getJavaFileObjects(file)));
|
|
|
+ } else {
|
|
|
+ list.add(new JavaSourceFileObject(resource.getName(), resource.getStream()));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // 源码Map
|
|
|
- collection.addAll(getJavaFileObjectByMap(this.sourceCodeMap));
|
|
|
- return collection;
|
|
|
+ return list;
|
|
|
}
|
|
|
|
|
|
/**
|