Commit 45cda665 authored by ma yanling's avatar ma yanling
Browse files

project commit

parent ad2fb30a
Pipeline #2354 failed with stages
in 0 seconds
package cn.hutool.core.comparator;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ClassUtil;
import java.lang.reflect.Field;
/**
* Bean字段排序器<br>
* 参阅feilong-core中的PropertyComparator
*
* @param <T> 被比较的Bean
* @author Looly
*/
public class FieldsComparator<T> extends NullComparator<T> {
private static final long serialVersionUID = 8649196282886500803L;
/**
* 构造
*
* @param beanClass Bean类
* @param fieldNames 多个字段名
*/
public FieldsComparator(Class<T> beanClass, String... fieldNames) {
this(true, beanClass, fieldNames);
}
/**
* 构造
*
* @param nullGreater 是否{@code null}在后
* @param beanClass Bean类
* @param fieldNames 多个字段名
*/
public FieldsComparator(boolean nullGreater, Class<T> beanClass, String... fieldNames) {
super(nullGreater, (a, b) -> {
Field field;
for (String fieldName : fieldNames) {
field = ClassUtil.getDeclaredField(beanClass, fieldName);
Assert.notNull(field, "Field [{}] not found in Class [{}]", fieldName, beanClass.getName());
final int compare = new FieldComparator<>(field).compare(a, b);
if (0 != compare) {
return compare;
}
}
return 0;
});
}
}
package cn.hutool.core.comparator;
import cn.hutool.core.util.ObjectUtil;
import java.util.function.Function;
/**
* 指定函数排序器
*
* @param <T> 被比较的对象
* @author looly
*/
public class FuncComparator<T> extends NullComparator<T> {
private static final long serialVersionUID = 1L;
private final Function<T, Comparable<?>> func;
/**
* 构造
*
* @param nullGreater 是否{@code null}在后
* @param func 比较项获取函数
*/
public FuncComparator(boolean nullGreater, Function<T, Comparable<?>> func) {
super(nullGreater, null);
this.func = func;
}
@Override
protected int doCompare(T a, T b) {
Comparable<?> v1;
Comparable<?> v2;
try {
v1 = func.apply(a);
v2 = func.apply(b);
} catch (Exception e) {
throw new ComparatorException(e);
}
return compare(a, b, v1, v2);
}
/**
* 对象及对应比较的值的综合比较<br>
* 考虑到如果对象对应的比较值相同,如对象的字段值相同,则返回相同结果,此时在TreeMap等容器比较去重时会去重。<br>
* 因此需要比较下对象本身以避免去重
*
* @param o1 对象1
* @param o2 对象2
* @param v1 被比较的值1
* @param v2 被比较的值2
* @return 比较结果
*/
@SuppressWarnings({"rawtypes", "unchecked"})
private int compare(T o1, T o2, Comparable v1, Comparable v2) {
int result = ObjectUtil.compare(v1, v2, this.nullGreater);
if (0 == result) {
//避免TreeSet / TreeMap 过滤掉排序字段相同但是对象不相同的情况
result = CompareUtil.compare(o1, o2, this.nullGreater);
}
return result;
}
}
package cn.hutool.core.comparator;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import java.util.Comparator;
/**
* 按照数组的顺序正序排列,数组的元素位置决定了对象的排序先后<br>
* 默认的,如果参与排序的元素并不在数组中,则排序在前(可以通过atEndIfMiss设置)
*
* @param <T> 被排序元素类型
* @author looly
* @since 4.1.5
*/
public class IndexedComparator<T> implements Comparator<T> {
private final boolean atEndIfMiss;
private final T[] array;
/**
* 构造
*
* @param objs 参与排序的数组,数组的元素位置决定了对象的排序先后
*/
@SuppressWarnings("unchecked")
public IndexedComparator(T... objs) {
this(false, objs);
}
/**
* 构造
*
* @param atEndIfMiss 如果不在列表中是否排在后边
* @param objs 参与排序的数组,数组的元素位置决定了对象的排序先后
*/
@SuppressWarnings("unchecked")
public IndexedComparator(boolean atEndIfMiss, T... objs) {
Assert.notNull(objs, "'objs' array must not be null");
this.atEndIfMiss = atEndIfMiss;
this.array = objs;
}
@Override
public int compare(T o1, T o2) {
final int index1 = getOrder(o1);
final int index2 = getOrder(o2);
if (index1 == index2) {
if (index1 < 0 || index1 == this.array.length) {
// 任意一个元素不在列表中, 返回原顺序
return 1;
}
// 位置一样,认为是同一个元素
return 0;
}
return Integer.compare(index1, index2);
}
/**
* 查找对象类型所在列表的位置
*
* @param object 对象
* @return 位置,未找到位置根据{@link #atEndIfMiss}取不同值,false返回-1,否则返回列表长度
*/
private int getOrder(T object) {
int order = ArrayUtil.indexOf(array, object);
if (order < 0) {
order = this.atEndIfMiss ? this.array.length : -1;
}
return order;
}
}
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hutool.core.comparator;
import cn.hutool.core.lang.Assert;
import java.util.Comparator;
/**
* 按照指定类型顺序排序,对象顺序取决于对象对应的类在数组中的位置。
*
* <p>如果对比的两个对象类型相同,返回{@code 0},默认如果对象类型不在列表中,则排序在前</p>
* <p>此类来自Spring,有所改造</p>
*
* @param <T> 用于比较的对象类型
* @author Phillip Webb
* @since 5.4.1
*/
public class InstanceComparator<T> implements Comparator<T> {
private final boolean atEndIfMiss;
private final Class<?>[] instanceOrder;
/**
* 构造
*
* @param instanceOrder 用于比较排序的对象类型数组,排序按照数组位置排序
*/
public InstanceComparator(Class<?>... instanceOrder) {
this(false, instanceOrder);
}
/**
* 构造
*
* @param atEndIfMiss 如果不在列表中是否排在后边
* @param instanceOrder 用于比较排序的对象类型数组,排序按照数组位置排序
*/
public InstanceComparator(boolean atEndIfMiss, Class<?>... instanceOrder) {
Assert.notNull(instanceOrder, "'instanceOrder' array must not be null");
this.atEndIfMiss = atEndIfMiss;
this.instanceOrder = instanceOrder;
}
@Override
public int compare(T o1, T o2) {
int i1 = getOrder(o1);
int i2 = getOrder(o2);
return Integer.compare(i1, i2);
}
/**
* 查找对象类型所在列表的位置
*
* @param object 对象
* @return 位置,未找到位置根据{@link #atEndIfMiss}取不同值,false返回-1,否则返回列表长度
*/
private int getOrder(T object) {
if (object != null) {
for (int i = 0; i < this.instanceOrder.length; i++) {
if (this.instanceOrder[i].isInstance(object)) {
return i;
}
}
}
return this.atEndIfMiss ? this.instanceOrder.length : -1;
}
}
package cn.hutool.core.comparator;
import java.util.Comparator;
/**
* 字符串长度比较器,短在前
*
* @author looly
* @since 5.8.9
*/
public class LengthComparator implements Comparator<CharSequence> {
/**
* 单例的字符串长度比较器,短在前
*/
public static final LengthComparator INSTANCE = new LengthComparator();
@Override
public int compare(CharSequence o1, CharSequence o2) {
int result = Integer.compare(o1.length(), o2.length());
if (0 == result) {
result = CompareUtil.compare(o1.toString(), o2.toString());
}
return result;
}
}
package cn.hutool.core.comparator;
import java.io.Serializable;
import java.util.Comparator;
import java.util.Objects;
/**
* {@code null}友好的比较器包装,如果nullGreater,则{@code null} &gt; non-null,否则反之。<br>
* 如果二者皆为{@code null},则为相等,返回0。<br>
* 如果二者都非{@code null},则使用传入的比较器排序。<br>
* 传入比较器为{@code null},则看被比较的两个对象是否都实现了{@link Comparable}实现则调用{@link Comparable#compareTo(Object)}。
* 如果两者至少一个未实现,则视为所有元素相等。
*
* @param <T> 被比较的对象
* @author looly
* @since 5.7.10
*/
public class NullComparator<T> implements Comparator<T>, Serializable {
private static final long serialVersionUID = 1L;
protected final boolean nullGreater;
protected final Comparator<T> comparator;
/**
* 构造
* @param nullGreater 是否{@code null}最大,排在最后
* @param comparator 实际比较器
*/
@SuppressWarnings("unchecked")
public NullComparator(boolean nullGreater, Comparator<? super T> comparator) {
this.nullGreater = nullGreater;
this.comparator = (Comparator<T>) comparator;
}
@Override
public int compare(T a, T b) {
if (a == b) {
return 0;
}if (a == null) {
return nullGreater ? 1 : -1;
} else if (b == null) {
return nullGreater ? -1 : 1;
} else {
return doCompare(a, b);
}
}
@Override
public Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return new NullComparator<>(nullGreater, comparator == null ? other : comparator.thenComparing(other));
}
/**
* 不检查{@code null}的比较方法<br>
* 用户可自行重写此方法自定义比较方式
*
* @param a A值
* @param b B值
* @return 比较结果,-1:a小于b,0:相等,1:a大于b
*/
@SuppressWarnings({"rawtypes", "unchecked"})
protected int doCompare(T a, T b) {
if (null == comparator) {
if (a instanceof Comparable && b instanceof Comparable) {
return ((Comparable) a).compareTo(b);
}
return 0;
}
return comparator.compare(a, b);
}
}
package cn.hutool.core.comparator;
import java.io.Serializable;
import java.text.Collator;
import java.util.Comparator;
import java.util.Locale;
/**
* 按照GBK拼音顺序对给定的汉字字符串排序
*
* @author looly
* @since 4.0.8
*/
public class PinyinComparator implements Comparator<String>, Serializable {
private static final long serialVersionUID = 1L;
final Collator collator;
/**
* 构造
*/
public PinyinComparator() {
collator = Collator.getInstance(Locale.CHINESE);
}
@Override
public int compare(String o1, String o2) {
return collator.compare(o1, o2);
}
}
package cn.hutool.core.comparator;
import cn.hutool.core.bean.BeanUtil;
/**
* Bean属性排序器<br>
* 支持读取Bean多层次下的属性
*
* @author Looly
*
* @param <T> 被比较的Bean
*/
public class PropertyComparator<T> extends FuncComparator<T> {
private static final long serialVersionUID = 9157326766723846313L;
/**
* 构造
*
* @param property 属性名
*/
public PropertyComparator(String property) {
this(property, true);
}
/**
* 构造
*
* @param property 属性名
* @param isNullGreater null值是否排在后(从小到大排序)
*/
public PropertyComparator(String property, boolean isNullGreater) {
super(isNullGreater, (bean)-> BeanUtil.getProperty(bean, property));
}
}
package cn.hutool.core.comparator;
import java.io.Serializable;
import java.util.Comparator;
/**
* 反转比较器
*
* @author Looly
*
* @param <E> 被比较对象类型
*/
public class ReverseComparator<E> implements Comparator<E>, Serializable {
private static final long serialVersionUID = 8083701245147495562L;
/** 原始比较器 */
private final Comparator<? super E> comparator;
@SuppressWarnings("unchecked")
public ReverseComparator(Comparator<? super E> comparator) {
this.comparator = (null == comparator) ? ComparableComparator.INSTANCE : comparator;
}
//-----------------------------------------------------------------------------------------------------
@Override
public int compare(E o1, E o2) {
return comparator.compare(o2, o1);
}
@Override
public int hashCode() {
return "ReverseComparator".hashCode() ^ comparator.hashCode();
}
@Override
public boolean equals(final Object object) {
if (this == object) {
return true;
}
if (null == object) {
return false;
}
if (object.getClass().equals(this.getClass())) {
final ReverseComparator<?> thatrc = (ReverseComparator<?>) object;
return comparator.equals(thatrc.comparator);
}
return false;
}
}
package cn.hutool.core.comparator;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import java.io.Serializable;
import java.util.Comparator;
import java.util.List;
/**
* 版本比较器<br>
* 比较两个版本的大小<br>
* 排序时版本从小到大排序,即比较时小版本在前,大版本在后<br>
* 支持如:1.3.20.8,6.82.20160101,8.5a/8.5c等版本形式<br>
* 参考:https://www.cnblogs.com/shihaiming/p/6286575.html
*
* @author Looly
* @since 4.0.2
*/
public class VersionComparator implements Comparator<String>, Serializable {
private static final long serialVersionUID = 8083701245147495562L;
/** 单例 */
public static final VersionComparator INSTANCE = new VersionComparator();
/**
* 默认构造
*/
public VersionComparator() {
}
// -----------------------------------------------------------------------------------------------------
/**
* 比较两个版本<br>
* null版本排在最小:即:
* <pre>
* compare(null, "v1") &lt; 0
* compare("v1", "v1") = 0
* compare(null, null) = 0
* compare("v1", null) &gt; 0
* compare("1.0.0", "1.0.2") &lt; 0
* compare("1.0.2", "1.0.2a") &lt; 0
* compare("1.13.0", "1.12.1c") &gt; 0
* compare("V0.0.20170102", "V0.0.20170101") &gt; 0
* </pre>
*
* @param version1 版本1
* @param version2 版本2
*/
@Override
public int compare(String version1, String version2) {
if(ObjectUtil.equal(version1, version2)) {
return 0;
}
if (version1 == null && version2 == null) {
return 0;
} else if (version1 == null) {// null视为最小版本,排在前
return -1;
} else if (version2 == null) {
return 1;
}
final List<String> v1s = StrUtil.split(version1, CharUtil.DOT);
final List<String> v2s = StrUtil.split(version2, CharUtil.DOT);
int diff = 0;
int minLength = Math.min(v1s.size(), v2s.size());// 取最小长度值
String v1;
String v2;
for (int i = 0; i < minLength; i++) {
v1 = v1s.get(i);
v2 = v2s.get(i);
// 先比较长度
diff = v1.length() - v2.length();
if (0 == diff) {
diff = v1.compareTo(v2);
}
if(diff != 0) {
//已有结果,结束
break;
}
}
// 如果已经分出大小,则直接返回,如果未分出大小,则再比较位数,有子版本的为大;
return (diff != 0) ? diff : v1s.size() - v2s.size();
}
}
/**
* 各种比较器(Comparator)实现和封装
*
* @author looly
*
*/
package cn.hutool.core.comparator;
\ No newline at end of file
package cn.hutool.core.compiler;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
/**
* 编译异常
*
* @author looly
* @since 5.5.2
*/
public class CompilerException extends RuntimeException {
private static final long serialVersionUID = 1L;
public CompilerException(Throwable e) {
super(ExceptionUtil.getMessage(e), e);
}
public CompilerException(String message) {
super(message);
}
public CompilerException(String messageTemplate, Object... params) {
super(StrUtil.format(messageTemplate, params));
}
public CompilerException(String message, Throwable throwable) {
super(message, throwable);
}
public CompilerException(Throwable throwable, String messageTemplate, Object... params) {
super(StrUtil.format(messageTemplate, params), throwable);
}
}
package cn.hutool.core.compiler;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
/**
* 源码编译工具类,主要封装{@link JavaCompiler} 相关功能
*
* @author looly
* @since 5.5.2
*/
public class CompilerUtil {
/**
* java 编译器
*/
public static final JavaCompiler SYSTEM_COMPILER = ToolProvider.getSystemJavaCompiler();
/**
* 编译指定的源码文件
*
* @param sourceFiles 源码文件路径
* @return 0表示成功,否则其他
*/
public static boolean compile(String... sourceFiles) {
return 0 == SYSTEM_COMPILER.run(null, null, null, sourceFiles);
}
/**
* 获取{@link StandardJavaFileManager}
*
* @return {@link StandardJavaFileManager}
*/
public static StandardJavaFileManager getFileManager() {
return getFileManager(null);
}
/**
* 获取{@link StandardJavaFileManager}
*
* @param diagnosticListener 异常收集器
* @return {@link StandardJavaFileManager}
* @since 5.5.8
*/
public static StandardJavaFileManager getFileManager(DiagnosticListener<? super JavaFileObject> diagnosticListener) {
return SYSTEM_COMPILER.getStandardFileManager(diagnosticListener, null, null);
}
/**
* 新建编译任务
*
* @param fileManager {@link JavaFileManager},用于管理已经编译好的文件
* @param diagnosticListener 诊断监听
* @param options 选项,例如 -cpXXX等
* @param compilationUnits 编译单元,即需要编译的对象
* @return {@link JavaCompiler.CompilationTask}
*/
public static JavaCompiler.CompilationTask getTask(
JavaFileManager fileManager,
DiagnosticListener<? super JavaFileObject> diagnosticListener,
Iterable<String> options,
Iterable<? extends JavaFileObject> compilationUnits) {
return SYSTEM_COMPILER.getTask(null, fileManager, diagnosticListener, options, null, compilationUnits);
}
/**
* 获取{@link JavaSourceCompiler}
*
* @param parent 父{@link ClassLoader}
* @return {@link JavaSourceCompiler}
* @see JavaSourceCompiler#create(ClassLoader)
*/
public static JavaSourceCompiler getCompiler(ClassLoader parent) {
return JavaSourceCompiler.create(parent);
}
}
package cn.hutool.core.compiler;
import javax.tools.DiagnosticCollector;
import java.util.List;
import java.util.stream.Collectors;
/**
* 诊断工具类
*
* @author looly
* @since 5.5.2
*/
public class DiagnosticUtil {
/**
* 获取{@link DiagnosticCollector}收集到的诊断信息,以文本返回
*
* @param collector {@link DiagnosticCollector}
* @return 诊断消息
*/
public static String getMessages(DiagnosticCollector<?> collector) {
final List<?> diagnostics = collector.getDiagnostics();
return diagnostics.stream().map(String::valueOf)
.collect(Collectors.joining(System.lineSeparator()));
}
}
package cn.hutool.core.compiler;
import cn.hutool.core.io.resource.FileObjectResource;
import cn.hutool.core.lang.ResourceClassLoader;
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.util.HashMap;
import java.util.Map;
/**
* Java 字节码文件对象管理器
*
* <p>
* 正常我们使用javac命令编译源码时会将class文件写入到磁盘中,但在运行时动态编译类不适合保存在磁盘中
* 我们采取此对象来管理运行时动态编译类生成的字节码。
* </p>
*
* @author lzpeng
* @since 5.5.2
*/
class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
/**
* 存储java字节码文件对象映射
*/
private final Map<String, FileObjectResource> classFileObjectMap = new HashMap<>();
/**
* 加载动态编译生成类的父类加载器
*/
private final ClassLoader parent;
/**
* 构造
*
* @param parent 父类加载器
* @param fileManager 字节码文件管理器
*/
protected JavaClassFileManager(ClassLoader parent, JavaFileManager fileManager) {
super(fileManager);
this.parent = ObjectUtil.defaultIfNull(parent, ClassLoaderUtil::getClassLoader);
}
/**
* 获得动态编译生成的类的类加载器
*
* @param location 源码位置
* @return 动态编译生成的类的类加载器
*/
@Override
public ClassLoader getClassLoader(final Location location) {
return new ResourceClassLoader<>(this.parent, this.classFileObjectMap);
}
/**
* 获得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);
this.classFileObjectMap.put(className, new FileObjectResource(javaFileObject));
return javaFileObject;
}
}
package cn.hutool.core.compiler;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.URLUtil;
import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Java 字节码文件对象,用于在内存中暂存class字节码,从而可以在ClassLoader中动态加载。
*
* @author lzpeng
* @since 5.5.2
*/
class JavaClassFileObject extends SimpleJavaFileObject {
/**
* 字节码输出流
*/
private final ByteArrayOutputStream byteArrayOutputStream;
/**
* 构造
*
* @param className 编译后的class文件的类名
* @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
*/
protected JavaClassFileObject(String className) {
super(URLUtil.getStringURI(className.replace(CharUtil.DOT, CharUtil.SLASH) + Kind.CLASS.extension), Kind.CLASS);
this.byteArrayOutputStream = new ByteArrayOutputStream();
}
/**
* 获得字节码输入流
* 编译器编辑源码后,我们将通过此输出流获得编译后的字节码,以便运行时加载类
*
* @return 字节码输入流
* @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location)
*/
@Override
public InputStream openInputStream() {
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
}
/**
* 获得字节码输出流
* 编译器编辑源码时,会将编译结果输出到本输出流中
*
* @return 字节码输出流
*/
@Override
public OutputStream openOutputStream() {
return this.byteArrayOutputStream;
}
}
\ No newline at end of file
package cn.hutool.core.compiler;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.ZipUtil;
import javax.tools.JavaFileObject;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipFile;
/**
* {@link JavaFileObject} 相关工具类封装
*
* @author lzpeng, looly
* @since 5.5.2
*/
public class JavaFileObjectUtil {
/**
* 获取指定文件下的所有待编译的java文件,并以{@link JavaFileObject}形式返回
*
* @param file 文件或目录,文件支持.java、.jar和.zip文件
* @return 所有待编译的 {@link JavaFileObject}
*/
public static List<JavaFileObject> getJavaFileObjects(File file) {
final List<JavaFileObject> result = new ArrayList<>();
final String fileName = file.getName();
if (isJavaFile(fileName)) {
result.add(new JavaSourceFileObject(file.toURI()));
} else if (isJarOrZipFile(fileName)) {
result.addAll(getJavaFileObjectByZipOrJarFile(file));
}
return result;
}
/**
* 是否是jar 或 zip 文件
*
* @param fileName 文件名
* @return 是否是jar 或 zip 文件
*/
public static boolean isJarOrZipFile(String fileName) {
return FileNameUtil.isType(fileName, "jar", "zip");
}
/**
* 是否是java文件
*
* @param fileName 文件名
* @return 是否是.java文件
*/
public static boolean isJavaFile(String fileName) {
return FileNameUtil.isType(fileName, "java");
}
/**
* 通过zip包或jar包创建Java文件对象
*
* @param file 压缩文件
* @return Java文件对象
*/
private static List<JavaFileObject> getJavaFileObjectByZipOrJarFile(File file) {
final List<JavaFileObject> collection = new ArrayList<>();
final ZipFile zipFile = ZipUtil.toZipFile(file, null);
ZipUtil.read(zipFile, (zipEntry) -> {
final String name = zipEntry.getName();
if (isJavaFile(name)) {
collection.add(new JavaSourceFileObject(name, ZipUtil.getStream(zipFile, zipEntry)));
}
});
return collection;
}
}
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 源码编译器
* <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&lt;?&gt; clazz = classLoader.loadClass("c.C");
* </pre>
*
* @author lzpeng
*/
public class JavaSourceCompiler {
/**
* 待编译的资源,支持:
*
* <ul>
* <li>源码字符串,使用{@link StringResource}</li>
* <li>源码文件、源码jar包或源码zip包,亦或者文件夹,使用{@link FileResource}</li>
* </ul>
* 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
*/
private final List<Resource> sourceList = new ArrayList<>();
/**
* 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包
*/
private final List<File> 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);
}
/**
* 向编译器中加入待编译的资源<br>
* 支持 .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;
}
/**
* 向编译器中加入待编译的文件<br>
* 支持 .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<String, String> 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<String> options) {
// 获得classPath
final List<File> 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<String> 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<JavaFileObject> 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<File> getClassPath() {
List<File> classPathFileList = new ArrayList<>();
for (File file : libraryFileList) {
List<File> 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<JavaFileObject> getJavaFileObject() {
final List<JavaFileObject> 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(file)));
} else {
list.add(new JavaSourceFileObject(resource.getName(), resource.getStream()));
}
}
return list;
}
/**
* 通过源码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());
}
}
package cn.hutool.core.compiler;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.URLUtil;
import javax.tools.SimpleJavaFileObject;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.Charset;
/**
* Java 源码文件对象,支持:<br>
* <ol>
* <li>源文件,通过文件的uri传入</li>
* <li>代码内容,通过流传入</li>
* </ol>
*
* @author lzpeng
* @since 5.5.2
*/
class JavaSourceFileObject extends SimpleJavaFileObject {
/**
* 输入流
*/
private InputStream inputStream;
/**
* Source code.
*/
private String sourceCode;
/**
* 构造,支持File等路径类型的源码
*
* @param uri 需要编译的文件uri
*/
protected JavaSourceFileObject(URI uri) {
super(uri, Kind.SOURCE);
}
/**
* 构造,支持String类型的源码
*
* @param className 需要编译的类名
* @param code 需要编译的类源码
*/
protected JavaSourceFileObject(String className, String code, Charset charset) {
this(className, IoUtil.toStream(code, charset));
}
/**
* 构造,支持流中读取源码(例如zip或网络等)
*
* @param name 需要编译的文件名
* @param inputStream 输入流
*/
protected JavaSourceFileObject(String name, InputStream inputStream) {
this(URLUtil.getStringURI(name.replace(CharUtil.DOT, CharUtil.SLASH) + Kind.SOURCE.extension));
this.inputStream = 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异常
*/
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
if (sourceCode == null) {
try(final InputStream in = openInputStream()){
sourceCode = IoUtil.readUtf8(in);
}
}
return sourceCode;
}
}
/**
* 运行时编译java源码,动态从字符串或外部文件加载类
*
* @author : Lzpeng
*/
package cn.hutool.core.compiler;
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment