Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ma yanling
hutool-5-master
Commits
45cda665
Commit
45cda665
authored
Sep 25, 2024
by
ma yanling
Browse files
project commit
parent
ad2fb30a
Pipeline
#2354
failed with stages
in 0 seconds
Changes
369
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
1316 additions
and
0 deletions
+1316
-0
hutool-core/src/main/java/cn/hutool/core/comparator/FieldsComparator.java
...main/java/cn/hutool/core/comparator/FieldsComparator.java
+50
-0
hutool-core/src/main/java/cn/hutool/core/comparator/FuncComparator.java
...c/main/java/cn/hutool/core/comparator/FuncComparator.java
+63
-0
hutool-core/src/main/java/cn/hutool/core/comparator/IndexedComparator.java
...ain/java/cn/hutool/core/comparator/IndexedComparator.java
+75
-0
hutool-core/src/main/java/cn/hutool/core/comparator/InstanceComparator.java
...in/java/cn/hutool/core/comparator/InstanceComparator.java
+83
-0
hutool-core/src/main/java/cn/hutool/core/comparator/LengthComparator.java
...main/java/cn/hutool/core/comparator/LengthComparator.java
+25
-0
hutool-core/src/main/java/cn/hutool/core/comparator/NullComparator.java
...c/main/java/cn/hutool/core/comparator/NullComparator.java
+73
-0
hutool-core/src/main/java/cn/hutool/core/comparator/PinyinComparator.java
...main/java/cn/hutool/core/comparator/PinyinComparator.java
+31
-0
hutool-core/src/main/java/cn/hutool/core/comparator/PropertyComparator.java
...in/java/cn/hutool/core/comparator/PropertyComparator.java
+34
-0
hutool-core/src/main/java/cn/hutool/core/comparator/ReverseComparator.java
...ain/java/cn/hutool/core/comparator/ReverseComparator.java
+49
-0
hutool-core/src/main/java/cn/hutool/core/comparator/VersionComparator.java
...ain/java/cn/hutool/core/comparator/VersionComparator.java
+88
-0
hutool-core/src/main/java/cn/hutool/core/comparator/package-info.java
...src/main/java/cn/hutool/core/comparator/package-info.java
+7
-0
hutool-core/src/main/java/cn/hutool/core/compiler/CompilerException.java
.../main/java/cn/hutool/core/compiler/CompilerException.java
+34
-0
hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java
...e/src/main/java/cn/hutool/core/compiler/CompilerUtil.java
+80
-0
hutool-core/src/main/java/cn/hutool/core/compiler/DiagnosticUtil.java
...src/main/java/cn/hutool/core/compiler/DiagnosticUtil.java
+26
-0
hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java
...in/java/cn/hutool/core/compiler/JavaClassFileManager.java
+78
-0
hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileObject.java
...ain/java/cn/hutool/core/compiler/JavaClassFileObject.java
+60
-0
hutool-core/src/main/java/cn/hutool/core/compiler/JavaFileObjectUtil.java
...main/java/cn/hutool/core/compiler/JavaFileObjectUtil.java
+75
-0
hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java
...main/java/cn/hutool/core/compiler/JavaSourceCompiler.java
+281
-0
hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java
...in/java/cn/hutool/core/compiler/JavaSourceFileObject.java
+98
-0
hutool-core/src/main/java/cn/hutool/core/compiler/package-info.java
...e/src/main/java/cn/hutool/core/compiler/package-info.java
+6
-0
No files found.
Too many changes to show.
To preserve performance only
369 of 369+
files are displayed.
Plain diff
Email patch
hutool-core/src/main/java/cn/hutool/core/comparator/FieldsComparator.java
0 → 100644
View file @
45cda665
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
;
});
}
}
hutool-core/src/main/java/cn/hutool/core/comparator/FuncComparator.java
0 → 100644
View file @
45cda665
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
;
}
}
hutool-core/src/main/java/cn/hutool/core/comparator/IndexedComparator.java
0 → 100644
View file @
45cda665
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
;
}
}
hutool-core/src/main/java/cn/hutool/core/comparator/InstanceComparator.java
0 → 100644
View file @
45cda665
/*
* 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
;
}
}
hutool-core/src/main/java/cn/hutool/core/comparator/LengthComparator.java
0 → 100644
View file @
45cda665
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
;
}
}
hutool-core/src/main/java/cn/hutool/core/comparator/NullComparator.java
0 → 100644
View file @
45cda665
package
cn.hutool.core.comparator
;
import
java.io.Serializable
;
import
java.util.Comparator
;
import
java.util.Objects
;
/**
* {@code null}友好的比较器包装,如果nullGreater,则{@code null} > 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
);
}
}
hutool-core/src/main/java/cn/hutool/core/comparator/PinyinComparator.java
0 → 100644
View file @
45cda665
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
);
}
}
hutool-core/src/main/java/cn/hutool/core/comparator/PropertyComparator.java
0 → 100644
View file @
45cda665
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
));
}
}
hutool-core/src/main/java/cn/hutool/core/comparator/ReverseComparator.java
0 → 100644
View file @
45cda665
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
;
}
}
hutool-core/src/main/java/cn/hutool/core/comparator/VersionComparator.java
0 → 100644
View file @
45cda665
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") < 0
* compare("v1", "v1") = 0
* compare(null, null) = 0
* compare("v1", null) > 0
* compare("1.0.0", "1.0.2") < 0
* compare("1.0.2", "1.0.2a") < 0
* compare("1.13.0", "1.12.1c") > 0
* compare("V0.0.20170102", "V0.0.20170101") > 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
();
}
}
hutool-core/src/main/java/cn/hutool/core/comparator/package-info.java
0 → 100644
View file @
45cda665
/**
* 各种比较器(Comparator)实现和封装
*
* @author looly
*
*/
package
cn.hutool.core.comparator
;
\ No newline at end of file
hutool-core/src/main/java/cn/hutool/core/compiler/CompilerException.java
0 → 100644
View file @
45cda665
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
);
}
}
hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java
0 → 100644
View file @
45cda665
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
);
}
}
hutool-core/src/main/java/cn/hutool/core/compiler/DiagnosticUtil.java
0 → 100644
View file @
45cda665
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
()));
}
}
hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileManager.java
0 → 100644
View file @
45cda665
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
;
}
}
hutool-core/src/main/java/cn/hutool/core/compiler/JavaClassFileObject.java
0 → 100644
View file @
45cda665
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
hutool-core/src/main/java/cn/hutool/core/compiler/JavaFileObjectUtil.java
0 → 100644
View file @
45cda665
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
;
}
}
hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceCompiler.java
0 → 100644
View file @
45cda665
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<?> 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
());
}
}
hutool-core/src/main/java/cn/hutool/core/compiler/JavaSourceFileObject.java
0 → 100644
View file @
45cda665
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
;
}
}
hutool-core/src/main/java/cn/hutool/core/compiler/package-info.java
0 → 100644
View file @
45cda665
/**
* 运行时编译java源码,动态从字符串或外部文件加载类
*
* @author : Lzpeng
*/
package
cn.hutool.core.compiler
;
\ No newline at end of file
Prev
1
…
10
11
12
13
14
15
16
17
18
19
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment