From db8ff64bf939ea9eb51ce740ff6157d55c3523a5 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 29 Nov 2020 00:32:04 +0800 Subject: [PATCH] add compile --- CHANGELOG.md | 1 + .../core/compiler/JavaClassFileManager.java | 146 +++-- .../core/compiler/JavaClassFileObject.java | 76 ++- .../core/compiler/JavaSourceCompiler.java | 503 +++++++++--------- .../core/compiler/JavaSourceFileObject.java | 141 +++-- .../core/compiler/JavaSourceCompilerTest.java | 44 +- 6 files changed, 452 insertions(+), 459 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 413de071b..43c6b360e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ * 【crypto 】 opt改为otp包(issue#1257@Github) * 【cache 】 增加CacheListener(issue#1257@Github) * 【core 】 TimeInterval支持分组(issue#1238@Github) +* 【core 】 增加compile包(pr#1243@Github) ### Bug修复 * 【cron 】 修复CronTimer可能死循环的问题(issue#1224@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java index ce2d4af49..0de53681c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java @@ -1,12 +1,16 @@ package cn.hutool.core.compiler; +import cn.hutool.core.io.IORuntimeException; 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.ForwardingJavaFileManager; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.JavaFileObject.Kind; +import java.io.IOException; import java.io.InputStream; import java.security.SecureClassLoader; import java.util.HashMap; @@ -18,88 +22,78 @@ import java.util.Map; * 我们采取此对象来管理运行时动态编译类生成的字节码 * * @author lzpeng - * @see JavaSourceCompilerBak#compile() - * @see com.sun.tools.javac.api.ClientCodeWrapper.WrappedJavaFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject) + * @since 5.5.2 */ -final class JavaClassFileManager extends ForwardingJavaFileManager { +class JavaClassFileManager extends ForwardingJavaFileManager { - /** - * 存储java字节码文件对象映射 - */ - private final Map javaFileObjectMap = new HashMap<>(); + /** + * 存储java字节码文件对象映射 + */ + private final Map javaFileObjectMap = new HashMap<>(); - /** - * 加载动态编译生成类的父类加载器 - */ - private final ClassLoader parent; + /** + * 加载动态编译生成类的父类加载器 + */ + private final ClassLoader parent; - /** - * 构造 - * - * @param parent 父类加载器 - * @param fileManager 字节码文件管理器 - * @see JavaSourceCompilerBak#compile() - */ - protected JavaClassFileManager(final ClassLoader parent, final JavaFileManager fileManager) { - super(fileManager); - if (parent == null) { - this.parent = Thread.currentThread().getContextClassLoader(); - } else { - this.parent = parent; - } - } + /** + * 构造 + * + * @param parent 父类加载器 + * @param fileManager 字节码文件管理器 + */ + protected JavaClassFileManager(ClassLoader parent, JavaFileManager fileManager) { + super(fileManager); + this.parent = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil.getClassLoader()); + } - /** - * 获得动态编译生成的类的类加载器 - * - * @param location 源码位置 - * @return 动态编译生成的类的类加载器 - * @see JavaSourceCompilerBak#compile() - */ - @Override - public ClassLoader getClassLoader(final Location location) { - return new SecureClassLoader(parent) { + /** + * 获得动态编译生成的类的类加载器 + * + * @param location 源码位置 + * @return 动态编译生成的类的类加载器 + */ + @Override + public ClassLoader getClassLoader(final Location location) { + return new SecureClassLoader(parent) { - /** - * 查找类 - * @param name 类名 - * @return 类的class对象 - * @throws ClassNotFoundException 未找到类异常 - */ - @Override - protected Class findClass(final String name) throws ClassNotFoundException { - final JavaFileObject javaFileObject = javaFileObjectMap.get(name); - if (javaFileObject != null) { - try { - final InputStream inputStream = javaFileObject.openInputStream(); - final byte[] bytes = IoUtil.readBytes(inputStream); - final Class c = defineClass(name, bytes, 0, bytes.length); - return c; - } catch (Exception e) { - e.printStackTrace(); - } - } - throw new ClassNotFoundException(name); - } - }; - } + /** + * 查找类 + * @param name 类名 + * @return 类的class对象 + * @throws ClassNotFoundException 未找到类异常 + */ + @Override + protected Class findClass(final String name) throws ClassNotFoundException { + final JavaFileObject javaFileObject = javaFileObjectMap.get(name); + if (null != javaFileObject) { + try(final InputStream inputStream = javaFileObject.openInputStream()){ + final byte[] bytes = IoUtil.readBytes(inputStream); + return defineClass(name, bytes, 0, bytes.length); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + throw new ClassNotFoundException(name); + } + }; + } - /** - * 获得Java字节码文件对象 - * 编译器编译源码时会将Java源码对象编译转为Java字节码对象 - * - * @param location 源码位置 - * @param className 类名 - * @param kind 文件类型 - * @param sibling 将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 - public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) { - final JavaFileObject javaFileObject = new JavaClassFileObject(className, kind); - javaFileObjectMap.put(className, javaFileObject); - return javaFileObject; - } + /** + * 获得Java字节码文件对象 + * 编译器编译源码时会将Java源码对象编译转为Java字节码对象 + * + * @param location 源码位置 + * @param className 类名 + * @param kind 文件类型 + * @param sibling 将Java源码对象 + * @return Java字节码文件对象 + */ + @Override + public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) { + final JavaFileObject javaFileObject = new JavaClassFileObject(className, kind); + javaFileObjectMap.put(className, javaFileObject); + return javaFileObject; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileObject.java b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileObject.java index c9f92bc70..d707a1c18 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileObject.java +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileObject.java @@ -14,50 +14,48 @@ import java.net.URI; * @author lzpeng * @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 com.sun.tools.javac.jvm.ClassWriter.ClassWriter#writeClass(com.sun.tools.javac.code.Symbol.ClassSymbol) + * @since 5.5.2 */ final class JavaClassFileObject extends SimpleJavaFileObject { - /** - * 字节码输出流 - */ - private final ByteArrayOutputStream byteArrayOutputStream; + /** + * 字节码输出流 + */ + private final ByteArrayOutputStream byteArrayOutputStream; - /** - * 构造 - * - * @param className 需要编译的类名 - * @param kind 需要编译的文件类型 - * @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) { - super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind); - this.byteArrayOutputStream = new ByteArrayOutputStream(); - } + /** + * 构造 + * + * @param className 需要编译的类名 + * @param kind 需要编译的文件类型 + * @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) { + super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind); + this.byteArrayOutputStream = new ByteArrayOutputStream(); + } - /** - * 获得字节码输入流 - * 编译器编辑源码后,我们将通过此输出流获得编译后的字节码,以便运行时加载类 - * - * @return 字节码输入流 - * @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location) - */ - @Override - public InputStream openInputStream() { - final byte[] bytes = byteArrayOutputStream.toByteArray(); - return new ByteArrayInputStream(bytes); - } + /** + * 获得字节码输入流 + * 编译器编辑源码后,我们将通过此输出流获得编译后的字节码,以便运行时加载类 + * + * @return 字节码输入流 + * @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location) + */ + @Override + public InputStream openInputStream() { + return new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); + } - /** - * 获得字节码输出流 - * 编译器编辑源码时,会将编译结果输出到本输出流中 - * - * @return 字节码输出流 - * @see com.sun.tools.javac.jvm.ClassWriter.ClassWriter#writeClass(com.sun.tools.javac.code.Symbol.ClassSymbol) - */ - @Override - public OutputStream openOutputStream() { - return this.byteArrayOutputStream; - } + /** + * 获得字节码输出流 + * 编译器编辑源码时,会将编译结果输出到本输出流中 + * + * @return 字节码输出流 + */ + @Override + public OutputStream openOutputStream() { + return this.byteArrayOutputStream; + } } \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java index c88e579fa..9f2650c8e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java @@ -3,17 +3,31 @@ package cn.hutool.core.compiler; import cn.hutool.core.io.FileUtil; 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.*; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; 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.IOException; import java.io.InputStream; import java.net.URL; 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.zip.ZipEntry; import java.util.zip.ZipFile; @@ -23,270 +37,269 @@ import java.util.zip.ZipFile; * * @author lzpeng */ -public final class JavaSourceCompiler { +public class JavaSourceCompiler { - /** - * java 编译器 - */ - private static final JavaCompiler JAVA_COMPILER = ToolProvider.getSystemJavaCompiler(); + /** + * java 编译器 + */ + private static final JavaCompiler JAVA_COMPILER = ToolProvider.getSystemJavaCompiler(); - /** - * 待编译的文件 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包 - */ - private final List sourceFileList = new ArrayList<>(); + /** + * 待编译的文件 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包 + */ + private final List sourceFileList = new ArrayList<>(); - /** - * 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包 - */ - private final List libraryFileList = new ArrayList<>(); + /** + * 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包 + */ + private final List libraryFileList = new ArrayList<>(); - /** - * 源码映射 key: 类名 value: 类源码 - */ - private final Map sourceCodeMap = new LinkedHashMap<>(); + /** + * 源码映射 key: 类名 value: 类源码 + */ + private final Map sourceCodeMap = new LinkedHashMap<>(); - /** - * 编译类时使用的父类加载器 - */ - private final ClassLoader parentClassLoader; + /** + * 编译类时使用的父类加载器 + */ + private final ClassLoader parentClassLoader; - /** - * 构造 - * - * @param parent 父类加载器 - */ - private JavaSourceCompiler(ClassLoader parent) { - this.parentClassLoader = parent; - } + /** + * 构造 + * + * @param parent 父类加载器 + */ + private JavaSourceCompiler(ClassLoader parent) { + this.parentClassLoader = parent; + } - /** - * 创建Java源码编译器 - * - * @param parent 父类加载器 - * @return Java源码编译器 - */ - public static JavaSourceCompiler create(ClassLoader parent) { - return new JavaSourceCompiler(parent); - } + /** + * 创建Java源码编译器 + * + * @param parent 父类加载器 + * @return Java源码编译器 + */ + public static JavaSourceCompiler create(ClassLoader parent) { + return new JavaSourceCompiler(parent); + } - /** - * 向编译器中加入待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包 - * - * @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包 - * @return Java源码编译器 - */ - public JavaSourceCompiler addSource(final File... files) { - if (ArrayUtil.isNotEmpty(files)) { - this.sourceFileList.addAll(Arrays.asList(files)); - } - return this; - } + /** + * 向编译器中加入待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包 + * + * @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包 + * @return Java源码编译器 + */ + public JavaSourceCompiler addSource(final File... files) { + if (ArrayUtil.isNotEmpty(files)) { + this.sourceFileList.addAll(Arrays.asList(files)); + } + return this; + } - /** - * 向编译器中加入待编译的源码Map - * - * @param sourceCodeMap 源码Map key: 类名 value 源码 - * @return Java源码编译器 - */ - public JavaSourceCompiler addSource(final Map sourceCodeMap) { - if (MapUtil.isNotEmpty(sourceCodeMap)) { - this.sourceCodeMap.putAll(sourceCodeMap); - } - return this; - } + /** + * 向编译器中加入待编译的源码Map + * + * @param sourceCodeMap 源码Map key: 类名 value 源码 + * @return Java源码编译器 + */ + public JavaSourceCompiler addSource(final Map sourceCodeMap) { + if (MapUtil.isNotEmpty(sourceCodeMap)) { + this.sourceCodeMap.putAll(sourceCodeMap); + } + return this; + } - /** - * 加入编译Java源码时所需要的jar包 - * - * @param files 编译Java源码时所需要的jar包 - * @return Java源码编译器 - */ - public JavaSourceCompiler addLibrary(final File... files) { - if (ArrayUtil.isNotEmpty(files)) { - this.libraryFileList.addAll(Arrays.asList(files)); - } - return this; - } + /** + * 加入编译Java源码时所需要的jar包 + * + * @param files 编译Java源码时所需要的jar包 + * @return Java源码编译器 + */ + public JavaSourceCompiler addLibrary(final File... files) { + if (ArrayUtil.isNotEmpty(files)) { + this.libraryFileList.addAll(Arrays.asList(files)); + } + return this; + } - /** - * 向编译器中加入待编译的源码Map - * - * @param className 类名 - * @param sourceCode 源码 - * @return Java文件编译器 - */ - public JavaSourceCompiler addSource(final String className, final String sourceCode) { - if (className != null && sourceCode != null) { - this.sourceCodeMap.put(className, sourceCode); - } - return this; - } + /** + * 向编译器中加入待编译的源码Map + * + * @param className 类名 + * @param sourceCode 源码 + * @return Java文件编译器 + */ + public JavaSourceCompiler addSource(final String className, final String sourceCode) { + if (className != null && sourceCode != null) { + this.sourceCodeMap.put(className, sourceCode); + } + return this; + } - /** - * 编译所有文件并返回类加载器 - * - * @return 类加载器 - */ - public ClassLoader compile() { - final ClassLoader parent; - if (this.parentClassLoader == null) { - parent = Thread.currentThread().getContextClassLoader(); - } else { - parent = this.parentClassLoader; - } - // 获得classPath - final List 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 javaFileObjectList = getJavaFileObject(); - // 创建编译器 - final JavaFileManager standardJavaFileManager = JAVA_COMPILER.getStandardFileManager(null, null, null); - final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, standardJavaFileManager); - final DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>(); - final List options = new ArrayList<>(); - if (!classPath.isEmpty()) { - final List 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); - } - } + /** + * 编译所有文件并返回类加载器 + * + * @return 类加载器 + */ + public ClassLoader compile() { + final ClassLoader parent = ObjectUtil.defaultIfNull(this.parentClassLoader, ClassLoaderUtil.getClassLoader()); - /** - * 获得编译源码时需要的classpath - * - * @return 编译源码时需要的classpath - */ - private List getClassPath() { - List classPathFileList = new ArrayList<>(); - for (File file : libraryFileList) { - List jarOrZipFile = FileUtil.loopFiles(file, this::isJarOrZipFile); - classPathFileList.addAll(jarOrZipFile); - if (file.isDirectory()) { - classPathFileList.add(file); - } - } - return classPathFileList; - } + // 获得classPath + final List 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 javaFileObjectList = getJavaFileObject(); - /** - * 获得待编译的Java文件对象 - * - * @return 待编译的Java文件对象 - */ - private Iterable getJavaFileObject() { - final Collection collection = new ArrayList<>(); - for (File file : sourceFileList) { - // .java 文件 - final List javaFileList = FileUtil.loopFiles(file, this::isJavaFile); - for (File javaFile : javaFileList) { - collection.add(getJavaFileObjectByJavaFile(javaFile)); - } - // 压缩包 - final List jarOrZipFileList = FileUtil.loopFiles(file, this::isJarOrZipFile); - for (File jarOrZipFile : jarOrZipFileList) { - collection.addAll(getJavaFileObjectByZipOrJarFile(jarOrZipFile)); - } - } - // 源码Map - collection.addAll(getJavaFileObjectByMap(this.sourceCodeMap)); - return collection; - } + // 创建编译器 + final JavaFileManager standardJavaFileManager = JAVA_COMPILER.getStandardFileManager(null, null, null); + final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, standardJavaFileManager); - /** - * 通过源码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(), Kind.SOURCE)) - .collect(Collectors.toList()); - } - return Collections.emptySet(); - } + // classpath + final List options = new ArrayList<>(); + if (false == classPath.isEmpty()) { + final List cp = classPath.stream().map(File::getAbsolutePath).collect(Collectors.toList()); + options.add("-cp"); + options.addAll(cp); + } - /** - * 通过.java文件创建Java文件对象 - * - * @param file .java文件 - * @return Java文件对象 - */ - private JavaFileObject getJavaFileObjectByJavaFile(final File file) { - return new JavaSourceFileObject(file.toURI(), Kind.SOURCE); - } + // 编译文件 + final DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>(); + final CompilationTask task = JAVA_COMPILER.getTask(null, javaFileManager, diagnosticCollector, + options, null, javaFileObjectList); + if (task.call()) { + 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); + } + } - /** - * 通过zip包或jar包创建Java文件对象 - * - * @param file 压缩文件 - * @return Java文件对象 - */ - private Collection getJavaFileObjectByZipOrJarFile(final File file) { - final Collection collection = new ArrayList<>(); - try { - final ZipFile zipFile = new ZipFile(file); - final Enumeration 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, Kind.SOURCE); - collection.add(fileObject); - } - } - return collection; - } catch (IOException e) { - e.printStackTrace(); - } - return Collections.emptyList(); - } + /** + * 获得编译源码时需要的classpath + * + * @return 编译源码时需要的classpath + */ + private List getClassPath() { + List classPathFileList = new ArrayList<>(); + for (File file : libraryFileList) { + List jarOrZipFile = FileUtil.loopFiles(file, this::isJarOrZipFile); + classPathFileList.addAll(jarOrZipFile); + if (file.isDirectory()) { + classPathFileList.add(file); + } + } + return classPathFileList; + } + + /** + * 获得待编译的Java文件对象 + * + * @return 待编译的Java文件对象 + */ + private Iterable getJavaFileObject() { + final Collection collection = new ArrayList<>(); + for (File file : sourceFileList) { + // .java 文件 + final List javaFileList = FileUtil.loopFiles(file, this::isJavaFile); + for (File javaFile : javaFileList) { + collection.add(getJavaFileObjectByJavaFile(javaFile)); + } + // 压缩包 + final List 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 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()); + } + + /** + * 通过zip包或jar包创建Java文件对象 + * + * @param file 压缩文件 + * @return Java文件对象 + */ + private Collection getJavaFileObjectByZipOrJarFile(final File file) { + final Collection collection = new ArrayList<>(); + try { + final ZipFile zipFile = new ZipFile(file); + final Enumeration 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 文件 - * - * @param file 文件 - * @return 是否是jar 或 zip 文件 - */ - private boolean isJarOrZipFile(final File file) { - final String fileName = file.getName(); - return fileName.endsWith(".jar") || fileName.endsWith(".zip"); - } + /** + * 是否是jar 或 zip 文件 + * + * @param file 文件 + * @return 是否是jar 或 zip 文件 + */ + private boolean isJarOrZipFile(final File file) { + final String fileName = file.getName(); + return fileName.endsWith(".jar") || fileName.endsWith(".zip"); + } - /** - * 是否是.java文件 - * - * @param file 文件 - * @return 是否是.java文件 - */ - private boolean isJavaFile(final File file) { - final String fileName = file.getName(); - return fileName.endsWith(".java"); - } + /** + * 是否是.java文件 + * + * @param file 文件 + * @return 是否是.java文件 + */ + private boolean isJavaFile(final File file) { + final String fileName = file.getName(); + return fileName.endsWith(".java"); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java index a13cc4841..e8cae9da8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java @@ -1,98 +1,85 @@ package cn.hutool.core.compiler; +import cn.hutool.core.io.IoUtil; + +import javax.tools.SimpleJavaFileObject; import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.nio.charset.Charset; -import javax.tools.SimpleJavaFileObject; - -import cn.hutool.core.io.IoUtil; - /** * Java 源码文件对象 * * @author lzpeng - * @see JavaSourceCompilerBak#getJavaFileObjectByJavaFile(java.io.File) - * @see JavaSourceCompilerBak#getJavaFileObjectByZipOrJarFile(java.io.File) - * @see JavaSourceCompilerBak#getJavaFileObject(java.util.Map) - * @see com.sun.tools.javac.api.ClientCodeWrapper.WrappedFileObject#getCharContent(boolean) + * @since 5.5.2 */ -final class JavaSourceFileObject extends SimpleJavaFileObject { +class JavaSourceFileObject extends SimpleJavaFileObject { - /** - * 输入流 - */ - private InputStream inputStream; + /** + * 输入流 + */ + private InputStream inputStream; - /** - * 构造 - * - * @param uri 需要编译的文件uri - * @param kind 需要编译的文件类型 - * @see JavaSourceCompilerBak#getJavaFileObjectByJavaFile(java.io.File) - */ - protected JavaSourceFileObject(URI uri, Kind kind) { - super(uri, kind); - } + /** + * 构造 + * + * @param uri 需要编译的文件uri + */ + protected JavaSourceFileObject(URI uri) { + super(uri, Kind.SOURCE); + } - /** - * 构造 - * - * @param name 需要编译的文件名 - * @param inputStream 输入流 - * @param kind 需要编译的文件类型 - * @see JavaSourceCompilerBak#getJavaFileObjectByZipOrJarFile(java.io.File) - */ - protected JavaSourceFileObject(final String name, final InputStream inputStream, final Kind kind) { - super(URI.create("string:///" + name), kind); - this.inputStream = inputStream; - } + /** + * 构造 + * + * @param name 需要编译的文件名 + * @param inputStream 输入流 + */ + protected JavaSourceFileObject(String name, InputStream inputStream) { + this(URI.create("string:///" + name)); + this.inputStream = inputStream; + } - /** - * 构造 - * - * @param className 需要编译的类名 - * @param code 需要编译的类源码 - * @param kind 需要编译的文件类型 - * @see JavaSourceCompilerBak#getJavaFileObject(java.util.Map) - */ - protected JavaSourceFileObject(final String className, final String code, final Kind kind) { - super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind); - this.inputStream = new ByteArrayInputStream(code.getBytes()); - } + /** + * 构造 + * + * @param className 需要编译的类名 + * @param code 需要编译的类源码 + */ + protected JavaSourceFileObject(String className, String code, Charset charset) { + this(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension)); + this.inputStream = IoUtil.toStream(code, charset); + } - /** - * 获得类源码的输入流 - * - * @return 类源码的输入流 - * @throws IOException IO 异常 - */ - @Override - public InputStream openInputStream() throws IOException { - if (inputStream == null) { - inputStream = toUri().toURL().openStream(); - } - return new BufferedInputStream(inputStream); - } + /** + * 获得类源码的输入流 + * + * @return 类源码的输入流 + * @throws IOException IO 异常 + */ + @Override + public InputStream openInputStream() throws IOException { + if (inputStream == null) { + inputStream = toUri().toURL().openStream(); + } + return new BufferedInputStream(inputStream); + } - /** - * 获得类源码 - * 编译器编辑源码前,会通过此方法获取类的源码 - * - * @param ignoreEncodingErrors 是否忽略编码错误 - * @return 需要编译的类的源码 - * @throws IOException IO异常 - * @see com.sun.tools.javac.api.ClientCodeWrapper.WrappedFileObject#getCharContent(boolean) - */ - @Override - public CharSequence getCharContent(final boolean ignoreEncodingErrors) throws IOException { - final InputStream in = openInputStream(); - final String code = IoUtil.read(in, Charset.defaultCharset()); - IoUtil.close(in); - return code; - } + /** + * 获得类源码 + * 编译器编辑源码前,会通过此方法获取类的源码 + * + * @param ignoreEncodingErrors 是否忽略编码错误 + * @return 需要编译的类的源码 + * @throws IOException IO异常 + */ + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + try(final InputStream in = openInputStream()){ + return IoUtil.readUtf8(in); + } + } } \ No newline at end of file diff --git a/hutool-core/src/test/java/cn/hutool/core/compiler/JavaSourceCompilerTest.java b/hutool-core/src/test/java/cn/hutool/core/compiler/JavaSourceCompilerTest.java index b8ce0a6c7..3e24514d7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/compiler/JavaSourceCompilerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/compiler/JavaSourceCompilerTest.java @@ -16,27 +16,27 @@ import java.io.InputStream; */ public class JavaSourceCompilerTest { - /** - * 测试编译Java源码 - */ - @Test - public void testCompile() throws ClassNotFoundException { - final File libFile = ZipUtil.zip(FileUtil.file("lib.jar"), - new String[]{"a/A.class", "a/A$1.class", "a/A$InnerClass.class"}, - new InputStream[]{ - FileUtil.getInputStream("test-compile/a/A.class"), - FileUtil.getInputStream("test-compile/a/A$1.class"), - FileUtil.getInputStream("test-compile/a/A$InnerClass.class") - }); - final 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(); - final Class clazz = classLoader.loadClass("c.C"); - Object obj = ReflectUtil.newInstance(clazz); - Assert.assertTrue(String.valueOf(obj).startsWith("c.C@")); - FileUtil.del(libFile); - } + /** + * 测试编译Java源码 + */ + @Test + public void testCompile() throws ClassNotFoundException { + final File libFile = ZipUtil.zip(FileUtil.file("lib.jar"), + new String[]{"a/A.class", "a/A$1.class", "a/A$InnerClass.class"}, + new InputStream[]{ + FileUtil.getInputStream("test-compile/a/A.class"), + FileUtil.getInputStream("test-compile/a/A$1.class"), + FileUtil.getInputStream("test-compile/a/A$InnerClass.class") + }); + final 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(); + final Class clazz = classLoader.loadClass("c.C"); + Object obj = ReflectUtil.newInstance(clazz); + Assert.assertTrue(String.valueOf(obj).startsWith("c.C@")); + FileUtil.del(libFile); + } } \ No newline at end of file