cn.hutool.core.compiler.JavaSourceCompiler Maven / Gradle / Ivy
package cn.hutool.core.compiler;
import cn.hutool.core.collection.CollUtil;
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;
import cn.hutool.core.util.ClassLoaderUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.URLUtil;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Java 源码编译器
* 通过此类可以动态编译java源码,并加载到ClassLoader,从而动态获取加载的类。
* JavaSourceCompiler支持加载的源码类型包括:
*
* - 源码文件
* - 源码文件源码字符串
*
*
* 使用方法如下:
*
* 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");
*
*
* @author lzpeng
*/
public class JavaSourceCompiler {
/**
* 待编译的资源,支持:
*
*
* - 源码字符串,使用{@link StringResource}
* - 源码文件、源码jar包或源码zip包,亦或者文件夹,使用{@link FileResource}
*
* 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
*/
private final List sourceList = new ArrayList<>();
/**
* 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包
*/
private final List libraryFileList = new ArrayList<>();
/**
* 编译类时使用的父类加载器
*/
private final ClassLoader parentClassLoader;
/**
* 创建Java源码编译器
*
* @param parent 父类加载器
* @return Java源码编译器
*/
public static JavaSourceCompiler create(ClassLoader parent) {
return new JavaSourceCompiler(parent);
}
/**
* 构造
*
* @param parent 父类加载器,null则使用默认类加载器
*/
private JavaSourceCompiler(ClassLoader parent) {
this.parentClassLoader = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil::getClassLoader);
}
/**
* 向编译器中加入待编译的资源
* 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
*
* @param resources 待编译的资源,支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
* @return Java源码编译器
*/
public JavaSourceCompiler addSource(Resource... resources) {
if (ArrayUtil.isNotEmpty(resources)) {
this.sourceList.addAll(Arrays.asList(resources));
}
return this;
}
/**
* 向编译器中加入待编译的文件
* 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
*
* @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
* @return Java源码编译器
*/
public JavaSourceCompiler addSource(File... files) {
if (ArrayUtil.isNotEmpty(files)) {
for (File file : files) {
this.sourceList.add(new FileResource(file));
}
}
return this;
}
/**
* 向编译器中加入待编译的源码Map
*
* @param sourceCodeMap 源码Map key: 类名 value 源码
* @return Java源码编译器
*/
public JavaSourceCompiler addSource(Map sourceCodeMap) {
if (MapUtil.isNotEmpty(sourceCodeMap)) {
sourceCodeMap.forEach(this::addSource);
}
return this;
}
/**
* 向编译器中加入待编译的源码
*
* @param className 类名
* @param sourceCode 源码
* @return Java文件编译器
*/
public JavaSourceCompiler addSource(String className, String sourceCode) {
if (className != null && sourceCode != null) {
this.sourceList.add(new StringResource(sourceCode, className));
}
return this;
}
/**
* 加入编译Java源码时所需要的jar包,jar包中必须为字节码
*
* @param files 编译Java源码时所需要的jar包
* @return Java源码编译器
*/
public JavaSourceCompiler addLibrary(File... files) {
if (ArrayUtil.isNotEmpty(files)) {
this.libraryFileList.addAll(Arrays.asList(files));
}
return this;
}
/**
* 编译所有文件并返回类加载器
*
* @return 类加载器
*/
public ClassLoader compile() {
return compile(null);
}
/**
* 编译所有文件并返回类加载器
*
* @param options 编译参数
* @return 类加载器
*/
public ClassLoader compile(List options) {
// 获得classPath
final List classPath = getClassPath();
final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0]));
final URLClassLoader ucl = URLClassLoader.newInstance(urLs, this.parentClassLoader);
if (sourceList.isEmpty()) {
// 没有需要编译的源码文件返回加载zip或jar包的类加载器
return ucl;
}
// 创建编译器
final JavaClassFileManager javaFileManager = new JavaClassFileManager(ucl, CompilerUtil.getFileManager());
// classpath
if (null == options) {
options = new ArrayList<>();
}
if (false == classPath.isEmpty()) {
final List cp = CollUtil.map(classPath, File::getAbsolutePath, true);
options.add("-cp");
options.add(CollUtil.join(cp, FileUtil.isWindows() ? ";" : ":"));
}
// 编译文件
final DiagnosticCollector super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
final List javaFileObjectList = getJavaFileObject();
final CompilationTask task = CompilerUtil.getTask(javaFileManager, diagnosticCollector, options, javaFileObjectList);
try {
if (task.call()) {
// 加载编译后的类
return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
}
} finally {
IoUtil.close(javaFileManager);
}
//编译失败,收集错误信息
throw new CompilerException(DiagnosticUtil.getMessages(diagnosticCollector));
}
/**
* 获得编译源码时需要的classpath
*
* @return 编译源码时需要的classpath
*/
private List getClassPath() {
List classPathFileList = new ArrayList<>();
for (File file : libraryFileList) {
List jarOrZipFile = FileUtil.loopFiles(file, (subFile) -> JavaFileObjectUtil.isJarOrZipFile(subFile.getName()));
classPathFileList.addAll(jarOrZipFile);
if (file.isDirectory()) {
classPathFileList.add(file);
}
}
return classPathFileList;
}
/**
* 获得待编译的Java文件对象
*
* @return 待编译的Java文件对象
*/
private List getJavaFileObject() {
final List list = new ArrayList<>();
for (Resource resource : this.sourceList) {
if (resource instanceof FileResource) {
final File file = ((FileResource) resource).getFile();
FileUtil.walkFiles(file, (subFile) -> list.addAll(JavaFileObjectUtil.getJavaFileObjects(subFile)));
} else {
list.add(new JavaSourceFileObject(resource.getName(), resource.getStream()));
}
}
return list;
}
/**
* 通过源码Map获得Java文件对象
*
* @param sourceCodeMap 源码Map
* @return Java文件对象集合
*/
private Collection getJavaFileObjectByMap(final Map sourceCodeMap) {
if (MapUtil.isNotEmpty(sourceCodeMap)) {
return sourceCodeMap.entrySet().stream()
.map(entry -> new JavaSourceFileObject(entry.getKey(), entry.getValue(), CharsetUtil.CHARSET_UTF_8))
.collect(Collectors.toList());
}
return Collections.emptySet();
}
/**
* 通过.java文件创建Java文件对象
*
* @param file .java文件
* @return Java文件对象
*/
private JavaFileObject getJavaFileObjectByJavaFile(final File file) {
return new JavaSourceFileObject(file.toURI());
}
}