add compile

This commit is contained in:
Looly
2020-11-29 00:32:04 +08:00
parent f126c6ec34
commit db8ff64bf9
6 changed files with 452 additions and 459 deletions

View File

@@ -23,6 +23,7 @@
* 【crypto 】 opt改为otp包issue#1257@Github * 【crypto 】 opt改为otp包issue#1257@Github
* 【cache 】 增加CacheListenerissue#1257@Github * 【cache 】 增加CacheListenerissue#1257@Github
* 【core 】 TimeInterval支持分组issue#1238@Github * 【core 】 TimeInterval支持分组issue#1238@Github
* 【core 】 增加compile包pr#1243@Github
### Bug修复 ### Bug修复
* 【cron 】 修复CronTimer可能死循环的问题issue#1224@Github * 【cron 】 修复CronTimer可能死循环的问题issue#1224@Github

View File

@@ -1,12 +1,16 @@
package cn.hutool.core.compiler; package cn.hutool.core.compiler;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ClassLoaderUtil;
import cn.hutool.core.util.ObjectUtil;
import javax.tools.FileObject; import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager; import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager; import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject; import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind; import javax.tools.JavaFileObject.Kind;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.SecureClassLoader; import java.security.SecureClassLoader;
import java.util.HashMap; import java.util.HashMap;
@@ -18,88 +22,78 @@ import java.util.Map;
* 我们采取此对象来管理运行时动态编译类生成的字节码 * 我们采取此对象来管理运行时动态编译类生成的字节码
* *
* @author lzpeng * @author lzpeng
* @see JavaSourceCompilerBak#compile() * @since 5.5.2
* @see com.sun.tools.javac.api.ClientCodeWrapper.WrappedJavaFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
*/ */
final class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> { class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
/** /**
* 存储java字节码文件对象映射 * 存储java字节码文件对象映射
*/ */
private final Map<String, JavaFileObject> javaFileObjectMap = new HashMap<>(); private final Map<String, JavaFileObject> javaFileObjectMap = new HashMap<>();
/** /**
* 加载动态编译生成类的父类加载器 * 加载动态编译生成类的父类加载器
*/ */
private final ClassLoader parent; private final ClassLoader parent;
/** /**
* 构造 * 构造
* *
* @param parent 父类加载器 * @param parent 父类加载器
* @param fileManager 字节码文件管理器 * @param fileManager 字节码文件管理器
* @see JavaSourceCompilerBak#compile() */
*/ protected JavaClassFileManager(ClassLoader parent, JavaFileManager fileManager) {
protected JavaClassFileManager(final ClassLoader parent, final JavaFileManager fileManager) { super(fileManager);
super(fileManager); this.parent = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil.getClassLoader());
if (parent == null) { }
this.parent = Thread.currentThread().getContextClassLoader();
} else {
this.parent = parent;
}
}
/** /**
* 获得动态编译生成的类的类加载器 * 获得动态编译生成的类的类加载器
* *
* @param location 源码位置 * @param location 源码位置
* @return 动态编译生成的类的类加载器 * @return 动态编译生成的类的类加载器
* @see JavaSourceCompilerBak#compile() */
*/ @Override
@Override public ClassLoader getClassLoader(final Location location) {
public ClassLoader getClassLoader(final Location location) { return new SecureClassLoader(parent) {
return new SecureClassLoader(parent) {
/** /**
* 查找类 * 查找类
* @param name 类名 * @param name 类名
* @return 类的class对象 * @return 类的class对象
* @throws ClassNotFoundException 未找到类异常 * @throws ClassNotFoundException 未找到类异常
*/ */
@Override @Override
protected Class<?> findClass(final String name) throws ClassNotFoundException { protected Class<?> findClass(final String name) throws ClassNotFoundException {
final JavaFileObject javaFileObject = javaFileObjectMap.get(name); final JavaFileObject javaFileObject = javaFileObjectMap.get(name);
if (javaFileObject != null) { if (null != javaFileObject) {
try { try(final InputStream inputStream = javaFileObject.openInputStream()){
final InputStream inputStream = javaFileObject.openInputStream(); final byte[] bytes = IoUtil.readBytes(inputStream);
final byte[] bytes = IoUtil.readBytes(inputStream); return defineClass(name, bytes, 0, bytes.length);
final Class<?> c = defineClass(name, bytes, 0, bytes.length); } catch (IOException e) {
return c; throw new IORuntimeException(e);
} catch (Exception e) { }
e.printStackTrace(); }
} throw new ClassNotFoundException(name);
} }
throw new ClassNotFoundException(name); };
} }
};
}
/** /**
* 获得Java字节码文件对象 * 获得Java字节码文件对象
* 编译器编译源码时会将Java源码对象编译转为Java字节码对象 * 编译器编译源码时会将Java源码对象编译转为Java字节码对象
* *
* @param location 源码位置 * @param location 源码位置
* @param className 类名 * @param className 类名
* @param kind 文件类型 * @param kind 文件类型
* @param sibling 将Java源码对象 * @param sibling 将Java源码对象
* @return Java字节码文件对象 * @return Java字节码文件对象
* @see com.sun.tools.javac.api.lientCodeWrapper.WrappedJavaFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject) */
*/ @Override
@Override public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) {
public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) { final JavaFileObject javaFileObject = new JavaClassFileObject(className, kind);
final JavaFileObject javaFileObject = new JavaClassFileObject(className, kind); javaFileObjectMap.put(className, javaFileObject);
javaFileObjectMap.put(className, javaFileObject); return javaFileObject;
return javaFileObject; }
}
} }

View File

@@ -14,50 +14,48 @@ import java.net.URI;
* @author lzpeng * @author lzpeng
* @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location * @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location
* @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject) * @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
* @see com.sun.tools.javac.jvm.ClassWriter.ClassWriter#writeClass(com.sun.tools.javac.code.Symbol.ClassSymbol) * @since 5.5.2
*/ */
final class JavaClassFileObject extends SimpleJavaFileObject { final class JavaClassFileObject extends SimpleJavaFileObject {
/** /**
* 字节码输出流 * 字节码输出流
*/ */
private final ByteArrayOutputStream byteArrayOutputStream; private final ByteArrayOutputStream byteArrayOutputStream;
/** /**
* 构造 * 构造
* *
* @param className 需要编译的类名 * @param className 需要编译的类名
* @param kind 需要编译的文件类型 * @param kind 需要编译的文件类型
* @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject) * @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
*/ */
protected JavaClassFileObject(final String className, final Kind kind) { protected JavaClassFileObject(final String className, final Kind kind) {
super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind); super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind);
this.byteArrayOutputStream = new ByteArrayOutputStream(); this.byteArrayOutputStream = new ByteArrayOutputStream();
} }
/** /**
* 获得字节码输入流 * 获得字节码输入流
* 编译器编辑源码后,我们将通过此输出流获得编译后的字节码,以便运行时加载类 * 编译器编辑源码后,我们将通过此输出流获得编译后的字节码,以便运行时加载类
* *
* @return 字节码输入流 * @return 字节码输入流
* @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location) * @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location)
*/ */
@Override @Override
public InputStream openInputStream() { public InputStream openInputStream() {
final byte[] bytes = byteArrayOutputStream.toByteArray(); return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
return new ByteArrayInputStream(bytes); }
}
/** /**
* 获得字节码输出流 * 获得字节码输出流
* 编译器编辑源码时,会将编译结果输出到本输出流中 * 编译器编辑源码时,会将编译结果输出到本输出流中
* *
* @return 字节码输出流 * @return 字节码输出流
* @see com.sun.tools.javac.jvm.ClassWriter.ClassWriter#writeClass(com.sun.tools.javac.code.Symbol.ClassSymbol) */
*/ @Override
@Override public OutputStream openOutputStream() {
public OutputStream openOutputStream() { return this.byteArrayOutputStream;
return this.byteArrayOutputStream; }
}
} }

View File

@@ -3,17 +3,31 @@ package cn.hutool.core.compiler;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil; 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 cn.hutool.core.util.URLUtil;
import javax.tools.*; import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject.Kind; import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
@@ -23,270 +37,269 @@ import java.util.zip.ZipFile;
* *
* @author lzpeng * @author lzpeng
*/ */
public final class JavaSourceCompiler { public class JavaSourceCompiler {
/** /**
* java 编译器 * java 编译器
*/ */
private static final JavaCompiler JAVA_COMPILER = ToolProvider.getSystemJavaCompiler(); private static final JavaCompiler JAVA_COMPILER = ToolProvider.getSystemJavaCompiler();
/** /**
* 待编译的文件 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包 * 待编译的文件 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
*/ */
private final List<File> sourceFileList = new ArrayList<>(); private final List<File> sourceFileList = new ArrayList<>();
/** /**
* 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包 * 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包
*/ */
private final List<File> libraryFileList = new ArrayList<>(); private final List<File> libraryFileList = new ArrayList<>();
/** /**
* 源码映射 key: 类名 value: 类源码 * 源码映射 key: 类名 value: 类源码
*/ */
private final Map<String, String> sourceCodeMap = new LinkedHashMap<>(); private final Map<String, String> sourceCodeMap = new LinkedHashMap<>();
/** /**
* 编译类时使用的父类加载器 * 编译类时使用的父类加载器
*/ */
private final ClassLoader parentClassLoader; private final ClassLoader parentClassLoader;
/** /**
* 构造 * 构造
* *
* @param parent 父类加载器 * @param parent 父类加载器
*/ */
private JavaSourceCompiler(ClassLoader parent) { private JavaSourceCompiler(ClassLoader parent) {
this.parentClassLoader = parent; this.parentClassLoader = parent;
} }
/** /**
* 创建Java源码编译器 * 创建Java源码编译器
* *
* @param parent 父类加载器 * @param parent 父类加载器
* @return Java源码编译器 * @return Java源码编译器
*/ */
public static JavaSourceCompiler create(ClassLoader parent) { public static JavaSourceCompiler create(ClassLoader parent) {
return new JavaSourceCompiler(parent); return new JavaSourceCompiler(parent);
} }
/** /**
* 向编译器中加入待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包 * 向编译器中加入待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
* *
* @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包 * @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
* @return Java源码编译器 * @return Java源码编译器
*/ */
public JavaSourceCompiler addSource(final File... files) { public JavaSourceCompiler addSource(final File... files) {
if (ArrayUtil.isNotEmpty(files)) { if (ArrayUtil.isNotEmpty(files)) {
this.sourceFileList.addAll(Arrays.asList(files)); this.sourceFileList.addAll(Arrays.asList(files));
} }
return this; return this;
} }
/** /**
* 向编译器中加入待编译的源码Map * 向编译器中加入待编译的源码Map
* *
* @param sourceCodeMap 源码Map key: 类名 value 源码 * @param sourceCodeMap 源码Map key: 类名 value 源码
* @return Java源码编译器 * @return Java源码编译器
*/ */
public JavaSourceCompiler addSource(final Map<String, String> sourceCodeMap) { public JavaSourceCompiler addSource(final Map<String, String> sourceCodeMap) {
if (MapUtil.isNotEmpty(sourceCodeMap)) { if (MapUtil.isNotEmpty(sourceCodeMap)) {
this.sourceCodeMap.putAll(sourceCodeMap); this.sourceCodeMap.putAll(sourceCodeMap);
} }
return this; return this;
} }
/** /**
* 加入编译Java源码时所需要的jar包 * 加入编译Java源码时所需要的jar包
* *
* @param files 编译Java源码时所需要的jar包 * @param files 编译Java源码时所需要的jar包
* @return Java源码编译器 * @return Java源码编译器
*/ */
public JavaSourceCompiler addLibrary(final File... files) { public JavaSourceCompiler addLibrary(final File... files) {
if (ArrayUtil.isNotEmpty(files)) { if (ArrayUtil.isNotEmpty(files)) {
this.libraryFileList.addAll(Arrays.asList(files)); this.libraryFileList.addAll(Arrays.asList(files));
} }
return this; return this;
} }
/** /**
* 向编译器中加入待编译的源码Map * 向编译器中加入待编译的源码Map
* *
* @param className 类名 * @param className 类名
* @param sourceCode 源码 * @param sourceCode 源码
* @return Java文件编译器 * @return Java文件编译器
*/ */
public JavaSourceCompiler addSource(final String className, final String sourceCode) { public JavaSourceCompiler addSource(final String className, final String sourceCode) {
if (className != null && sourceCode != null) { if (className != null && sourceCode != null) {
this.sourceCodeMap.put(className, sourceCode); this.sourceCodeMap.put(className, sourceCode);
} }
return this; return this;
} }
/** /**
* 编译所有文件并返回类加载器 * 编译所有文件并返回类加载器
* *
* @return 类加载器 * @return 类加载器
*/ */
public ClassLoader compile() { public ClassLoader compile() {
final ClassLoader parent; final ClassLoader parent = ObjectUtil.defaultIfNull(this.parentClassLoader, ClassLoaderUtil.getClassLoader());
if (this.parentClassLoader == null) {
parent = Thread.currentThread().getContextClassLoader();
} else {
parent = this.parentClassLoader;
}
// 获得classPath
final List<File> classPath = getClassPath();
final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0]));
final URLClassLoader ucl = URLClassLoader.newInstance(urLs, parent);
if (sourceCodeMap.isEmpty() && sourceFileList.isEmpty()) {
// 没有需要编译的源码
return ucl;
}
// 没有需要编译的源码文件返回加载zip或jar包的类加载器
final Iterable<JavaFileObject> javaFileObjectList = getJavaFileObject();
// 创建编译器
final JavaFileManager standardJavaFileManager = JAVA_COMPILER.getStandardFileManager(null, null, null);
final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, standardJavaFileManager);
final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
final List<String> options = new ArrayList<>();
if (!classPath.isEmpty()) {
final List<String> cp = classPath.stream().map(File::getAbsolutePath).collect(Collectors.toList());
options.add("-cp");
options.addAll(cp);
}
// 编译文件
final CompilationTask task = JAVA_COMPILER.getTask(null, javaFileManager, diagnosticCollector,
options, null, javaFileObjectList);
final Boolean result = task.call();
if (Boolean.TRUE.equals(result)) {
return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
} else {
// 编译失败,收集错误信息
final List<?> diagnostics = diagnosticCollector.getDiagnostics();
final String errorMsg = diagnostics.stream().map(String::valueOf)
.collect(Collectors.joining(System.lineSeparator()));
// CompileException
throw new RuntimeException(errorMsg);
}
}
/** // 获得classPath
* 获得编译源码时需要的classpath final List<File> classPath = getClassPath();
* final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0]));
* @return 编译源码时需要的classpath final URLClassLoader ucl = URLClassLoader.newInstance(urLs, parent);
*/ if (sourceCodeMap.isEmpty() && sourceFileList.isEmpty()) {
private List<File> getClassPath() { // 没有需要编译的源码
List<File> classPathFileList = new ArrayList<>(); return ucl;
for (File file : libraryFileList) { }
List<File> jarOrZipFile = FileUtil.loopFiles(file, this::isJarOrZipFile); // 没有需要编译的源码文件返回加载zip或jar包的类加载器
classPathFileList.addAll(jarOrZipFile); final Iterable<JavaFileObject> javaFileObjectList = getJavaFileObject();
if (file.isDirectory()) {
classPathFileList.add(file);
}
}
return classPathFileList;
}
/** // 创建编译器
* 获得待编译的Java文件对象 final JavaFileManager standardJavaFileManager = JAVA_COMPILER.getStandardFileManager(null, null, null);
* final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, standardJavaFileManager);
* @return 待编译的Java文件对象
*/
private Iterable<JavaFileObject> getJavaFileObject() {
final Collection<JavaFileObject> collection = new ArrayList<>();
for (File file : sourceFileList) {
// .java 文件
final List<File> javaFileList = FileUtil.loopFiles(file, this::isJavaFile);
for (File javaFile : javaFileList) {
collection.add(getJavaFileObjectByJavaFile(javaFile));
}
// 压缩包
final List<File> jarOrZipFileList = FileUtil.loopFiles(file, this::isJarOrZipFile);
for (File jarOrZipFile : jarOrZipFileList) {
collection.addAll(getJavaFileObjectByZipOrJarFile(jarOrZipFile));
}
}
// 源码Map
collection.addAll(getJavaFileObjectByMap(this.sourceCodeMap));
return collection;
}
/** // classpath
* 通过源码Map获得Java文件对象 final List<String> options = new ArrayList<>();
* if (false == classPath.isEmpty()) {
* @param sourceCodeMap 源码Map final List<String> cp = classPath.stream().map(File::getAbsolutePath).collect(Collectors.toList());
* @return Java文件对象集合 options.add("-cp");
*/ options.addAll(cp);
private Collection<JavaFileObject> getJavaFileObjectByMap(final Map<String, String> sourceCodeMap) { }
if (MapUtil.isNotEmpty(sourceCodeMap)) {
return sourceCodeMap.entrySet().stream()
.map(entry -> new JavaSourceFileObject(entry.getKey(), entry.getValue(), Kind.SOURCE))
.collect(Collectors.toList());
}
return Collections.emptySet();
}
/** // 编译文件
* 通过.java文件创建Java文件对象 final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
* final CompilationTask task = JAVA_COMPILER.getTask(null, javaFileManager, diagnosticCollector,
* @param file .java文件 options, null, javaFileObjectList);
* @return Java文件对象 if (task.call()) {
*/ return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
private JavaFileObject getJavaFileObjectByJavaFile(final File file) { } else {
return new JavaSourceFileObject(file.toURI(), Kind.SOURCE); // 编译失败,收集错误信息
} final List<?> diagnostics = diagnosticCollector.getDiagnostics();
final String errorMsg = diagnostics.stream().map(String::valueOf)
.collect(Collectors.joining(System.lineSeparator()));
// CompileException
throw new RuntimeException(errorMsg);
}
}
/** /**
* 通过zip包或jar包创建Java文件对象 * 获得编译源码时需要的classpath
* *
* @param file 压缩文件 * @return 编译源码时需要的classpath
* @return Java文件对象 */
*/ private List<File> getClassPath() {
private Collection<JavaFileObject> getJavaFileObjectByZipOrJarFile(final File file) { List<File> classPathFileList = new ArrayList<>();
final Collection<JavaFileObject> collection = new ArrayList<>(); for (File file : libraryFileList) {
try { List<File> jarOrZipFile = FileUtil.loopFiles(file, this::isJarOrZipFile);
final ZipFile zipFile = new ZipFile(file); classPathFileList.addAll(jarOrZipFile);
final Enumeration<? extends ZipEntry> entries = zipFile.entries(); if (file.isDirectory()) {
while (entries.hasMoreElements()) { classPathFileList.add(file);
final ZipEntry zipEntry = entries.nextElement(); }
final String name = zipEntry.getName(); }
if (name.endsWith(".java")) { return classPathFileList;
final InputStream inputStream = zipFile.getInputStream(zipEntry); }
final JavaSourceFileObject fileObject = new JavaSourceFileObject(name, inputStream, Kind.SOURCE);
collection.add(fileObject); /**
} * 获得待编译的Java文件对象
} *
return collection; * @return 待编译的Java文件对象
} catch (IOException e) { */
e.printStackTrace(); private Iterable<JavaFileObject> getJavaFileObject() {
} final Collection<JavaFileObject> collection = new ArrayList<>();
return Collections.emptyList(); for (File file : sourceFileList) {
} // .java 文件
final List<File> javaFileList = FileUtil.loopFiles(file, this::isJavaFile);
for (File javaFile : javaFileList) {
collection.add(getJavaFileObjectByJavaFile(javaFile));
}
// 压缩包
final List<File> jarOrZipFileList = FileUtil.loopFiles(file, this::isJarOrZipFile);
for (File jarOrZipFile : jarOrZipFileList) {
collection.addAll(getJavaFileObjectByZipOrJarFile(jarOrZipFile));
}
}
// 源码Map
collection.addAll(getJavaFileObjectByMap(this.sourceCodeMap));
return collection;
}
/**
* 通过源码Map获得Java文件对象
*
* @param sourceCodeMap 源码Map
* @return Java文件对象集合
*/
private Collection<JavaFileObject> getJavaFileObjectByMap(final Map<String, String> 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());
}
/**
* 通过zip包或jar包创建Java文件对象
*
* @param file 压缩文件
* @return Java文件对象
*/
private Collection<JavaFileObject> getJavaFileObjectByZipOrJarFile(final File file) {
final Collection<JavaFileObject> collection = new ArrayList<>();
try {
final ZipFile zipFile = new ZipFile(file);
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
final ZipEntry zipEntry = entries.nextElement();
final String name = zipEntry.getName();
if (name.endsWith(".java")) {
final InputStream inputStream = zipFile.getInputStream(zipEntry);
final JavaSourceFileObject fileObject = new JavaSourceFileObject(name, inputStream);
collection.add(fileObject);
}
}
return collection;
} catch (IOException e) {
e.printStackTrace();
}
return Collections.emptyList();
}
/** /**
* 是否是jar 或 zip 文件 * 是否是jar 或 zip 文件
* *
* @param file 文件 * @param file 文件
* @return 是否是jar 或 zip 文件 * @return 是否是jar 或 zip 文件
*/ */
private boolean isJarOrZipFile(final File file) { private boolean isJarOrZipFile(final File file) {
final String fileName = file.getName(); final String fileName = file.getName();
return fileName.endsWith(".jar") || fileName.endsWith(".zip"); return fileName.endsWith(".jar") || fileName.endsWith(".zip");
} }
/** /**
* 是否是.java文件 * 是否是.java文件
* *
* @param file 文件 * @param file 文件
* @return 是否是.java文件 * @return 是否是.java文件
*/ */
private boolean isJavaFile(final File file) { private boolean isJavaFile(final File file) {
final String fileName = file.getName(); final String fileName = file.getName();
return fileName.endsWith(".java"); return fileName.endsWith(".java");
} }
} }

View File

@@ -1,98 +1,85 @@
package cn.hutool.core.compiler; package cn.hutool.core.compiler;
import cn.hutool.core.io.IoUtil;
import javax.tools.SimpleJavaFileObject;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import javax.tools.SimpleJavaFileObject;
import cn.hutool.core.io.IoUtil;
/** /**
* Java 源码文件对象 * Java 源码文件对象
* *
* @author lzpeng * @author lzpeng
* @see JavaSourceCompilerBak#getJavaFileObjectByJavaFile(java.io.File) * @since 5.5.2
* @see JavaSourceCompilerBak#getJavaFileObjectByZipOrJarFile(java.io.File)
* @see JavaSourceCompilerBak#getJavaFileObject(java.util.Map)
* @see com.sun.tools.javac.api.ClientCodeWrapper.WrappedFileObject#getCharContent(boolean)
*/ */
final class JavaSourceFileObject extends SimpleJavaFileObject { class JavaSourceFileObject extends SimpleJavaFileObject {
/** /**
* 输入流 * 输入流
*/ */
private InputStream inputStream; private InputStream inputStream;
/** /**
* 构造 * 构造
* *
* @param uri 需要编译的文件uri * @param uri 需要编译的文件uri
* @param kind 需要编译的文件类型 */
* @see JavaSourceCompilerBak#getJavaFileObjectByJavaFile(java.io.File) protected JavaSourceFileObject(URI uri) {
*/ super(uri, Kind.SOURCE);
protected JavaSourceFileObject(URI uri, Kind kind) { }
super(uri, kind);
}
/** /**
* 构造 * 构造
* *
* @param name 需要编译的文件名 * @param name 需要编译的文件名
* @param inputStream 输入流 * @param inputStream 输入流
* @param kind 需要编译的文件类型 */
* @see JavaSourceCompilerBak#getJavaFileObjectByZipOrJarFile(java.io.File) protected JavaSourceFileObject(String name, InputStream inputStream) {
*/ this(URI.create("string:///" + name));
protected JavaSourceFileObject(final String name, final InputStream inputStream, final Kind kind) { this.inputStream = inputStream;
super(URI.create("string:///" + name), kind); }
this.inputStream = inputStream;
}
/** /**
* 构造 * 构造
* *
* @param className 需要编译的类名 * @param className 需要编译的类名
* @param code 需要编译的类源码 * @param code 需要编译的类源码
* @param kind 需要编译的文件类型 */
* @see JavaSourceCompilerBak#getJavaFileObject(java.util.Map) protected JavaSourceFileObject(String className, String code, Charset charset) {
*/ this(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension));
protected JavaSourceFileObject(final String className, final String code, final Kind kind) { this.inputStream = IoUtil.toStream(code, charset);
super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind); }
this.inputStream = new ByteArrayInputStream(code.getBytes());
}
/** /**
* 获得类源码的输入流 * 获得类源码的输入流
* *
* @return 类源码的输入流 * @return 类源码的输入流
* @throws IOException IO 异常 * @throws IOException IO 异常
*/ */
@Override @Override
public InputStream openInputStream() throws IOException { public InputStream openInputStream() throws IOException {
if (inputStream == null) { if (inputStream == null) {
inputStream = toUri().toURL().openStream(); inputStream = toUri().toURL().openStream();
} }
return new BufferedInputStream(inputStream); return new BufferedInputStream(inputStream);
} }
/** /**
* 获得类源码 * 获得类源码
* 编译器编辑源码前,会通过此方法获取类的源码 * 编译器编辑源码前,会通过此方法获取类的源码
* *
* @param ignoreEncodingErrors 是否忽略编码错误 * @param ignoreEncodingErrors 是否忽略编码错误
* @return 需要编译的类的源码 * @return 需要编译的类的源码
* @throws IOException IO异常 * @throws IOException IO异常
* @see com.sun.tools.javac.api.ClientCodeWrapper.WrappedFileObject#getCharContent(boolean) */
*/ @Override
@Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
public CharSequence getCharContent(final boolean ignoreEncodingErrors) throws IOException { try(final InputStream in = openInputStream()){
final InputStream in = openInputStream(); return IoUtil.readUtf8(in);
final String code = IoUtil.read(in, Charset.defaultCharset()); }
IoUtil.close(in); }
return code;
}
} }

View File

@@ -16,27 +16,27 @@ import java.io.InputStream;
*/ */
public class JavaSourceCompilerTest { public class JavaSourceCompilerTest {
/** /**
* 测试编译Java源码 * 测试编译Java源码
*/ */
@Test @Test
public void testCompile() throws ClassNotFoundException { public void testCompile() throws ClassNotFoundException {
final File libFile = ZipUtil.zip(FileUtil.file("lib.jar"), final File libFile = ZipUtil.zip(FileUtil.file("lib.jar"),
new String[]{"a/A.class", "a/A$1.class", "a/A$InnerClass.class"}, new String[]{"a/A.class", "a/A$1.class", "a/A$InnerClass.class"},
new InputStream[]{ new InputStream[]{
FileUtil.getInputStream("test-compile/a/A.class"), FileUtil.getInputStream("test-compile/a/A.class"),
FileUtil.getInputStream("test-compile/a/A$1.class"), FileUtil.getInputStream("test-compile/a/A$1.class"),
FileUtil.getInputStream("test-compile/a/A$InnerClass.class") FileUtil.getInputStream("test-compile/a/A$InnerClass.class")
}); });
final ClassLoader classLoader = JavaSourceCompiler.create(null) final ClassLoader classLoader = JavaSourceCompiler.create(null)
.addSource(FileUtil.file("test-compile/b/B.java")) .addSource(FileUtil.file("test-compile/b/B.java"))
.addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java")) .addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java"))
.addLibrary(libFile) .addLibrary(libFile)
.compile(); .compile();
final Class<?> clazz = classLoader.loadClass("c.C"); final Class<?> clazz = classLoader.loadClass("c.C");
Object obj = ReflectUtil.newInstance(clazz); Object obj = ReflectUtil.newInstance(clazz);
Assert.assertTrue(String.valueOf(obj).startsWith("c.C@")); Assert.assertTrue(String.valueOf(obj).startsWith("c.C@"));
FileUtil.del(libFile); FileUtil.del(libFile);
} }
} }