This commit is contained in:
Looly 2024-07-14 18:18:10 +08:00
parent 7b10238225
commit 4451cebe20
14 changed files with 286 additions and 92 deletions

View File

@ -12,7 +12,11 @@
package org.dromara.hutool.core.bean;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.map.reference.WeakConcurrentMap;
import org.dromara.hutool.core.reflect.FieldUtil;
import java.lang.reflect.Proxy;
/**
* Bean描述信息工厂类<br>
@ -48,6 +52,9 @@ public class BeanDescFactory {
public static BeanDesc getBeanDescWithoutCache(final Class<?> clazz) {
if (RecordUtil.isRecord(clazz)) {
return new RecordBeanDesc(clazz);
}else if(Proxy.isProxyClass(clazz) || ArrayUtil.isEmpty(FieldUtil.getFields(clazz))){
// 代理类和空字段的Bean不支持属性获取直接使用方法方式
return new SimpleBeanDesc(clazz);
} else {
return new StrictBeanDesc(clazz);
}

View File

@ -326,6 +326,7 @@ public class PropDesc {
public String toString() {
return "PropDesc{" +
"field=" + field +
", fieldName=" + fieldName +
", getter=" + getter +
", setter=" + setter +
'}';

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.bean;
import org.dromara.hutool.core.bean.path.AbstractBeanDesc;
import org.dromara.hutool.core.reflect.method.MethodNameUtil;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import java.lang.reflect.Method;
import java.util.Map;
/**
* 简单的Bean描述只查找getter和setter方法
*
* @author Looly
* @since 6.0.0
*/
public class SimpleBeanDesc extends AbstractBeanDesc {
private static final long serialVersionUID = 1L;
/**
* 构造
*
* @param beanClass Bean类
*/
public SimpleBeanDesc(final Class<?> beanClass) {
super(beanClass);
init();
}
/**
* 普通Bean初始化查找和加载getter和setter<br>
*/
private void init() {
final Map<String, PropDesc> propMap = this.propMap;
final Method[] gettersAndSetters = MethodUtil.getPublicMethods(this.beanClass, MethodUtil::isGetterOrSetterIgnoreCase);
boolean isSetter;
int nameIndex;
String methodName;
String fieldName;
for (final Method method : gettersAndSetters) {
methodName = method.getName();
switch (methodName.charAt(0)){
case 's':
isSetter = true;
nameIndex = 3;
break;
case 'g':
isSetter = false;
nameIndex = 3;
break;
case 'i':
isSetter = false;
nameIndex = 2;
break;
default:
continue;
}
fieldName = MethodNameUtil.decapitalize(methodName.substring(nameIndex));
PropDesc propDesc = propMap.get(fieldName);
if(null == propDesc){
propDesc = new PropDesc(fieldName, isSetter ? null : method, isSetter ? method : null);
propMap.put(fieldName, propDesc);
} else{
if(isSetter){
propDesc.setter = method;
}else{
propDesc.getter = method;
}
}
}
}
}

View File

@ -15,6 +15,7 @@ package org.dromara.hutool.core.bean;
import org.dromara.hutool.core.bean.path.AbstractBeanDesc;
import org.dromara.hutool.core.reflect.FieldUtil;
import org.dromara.hutool.core.reflect.ModifierUtil;
import org.dromara.hutool.core.reflect.method.MethodNameUtil;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.BooleanUtil;
@ -158,12 +159,12 @@ public class StrictBeanDesc extends AbstractBeanDesc {
methodName = method.getName();
if (0 == method.getParameterCount()) {
// 无参数可能为Getter方法
if (StrUtil.equals(methodName, StrUtil.genGetter(fieldName), ignoreCase) &&
if (StrUtil.equals(methodName, MethodNameUtil.genGetter(fieldName), ignoreCase) &&
method.getReturnType().isAssignableFrom(fieldType)) {
// getter的返回类型必须为字段类型或字段的父类
getter = method;
}
} else if (StrUtil.equals(methodName, StrUtil.genSetter(fieldName), ignoreCase) &&
} else if (StrUtil.equals(methodName, MethodNameUtil.genSetter(fieldName), ignoreCase) &&
fieldType.isAssignableFrom(method.getParameterTypes()[0])) {
// setter方法的参数必须为字段类型或字段的子类
setter = method;
@ -198,7 +199,7 @@ public class StrictBeanDesc extends AbstractBeanDesc {
return false;
}
if (StrUtil.startWith(fieldName, "is", ignoreCase)) {
if (StrUtil.startWith(fieldName, MethodNameUtil.IS_PREFIX, ignoreCase)) {
// isName - isName
if (StrUtil.equals(fieldName, m.getName(), ignoreCase)) {
return true;
@ -207,7 +208,7 @@ public class StrictBeanDesc extends AbstractBeanDesc {
// name - isName
// isName - isIsName
return StrUtil.equals(StrUtil.upperFirstAndAddPre(fieldName, "is"), m.getName(), ignoreCase);
return StrUtil.equals(StrUtil.upperFirstAndAddPre(fieldName, MethodNameUtil.IS_PREFIX), m.getName(), ignoreCase);
});
}
@ -231,10 +232,10 @@ public class StrictBeanDesc extends AbstractBeanDesc {
return false;
}
if (StrUtil.startWith(fieldName, "is", ignoreCase)) {
if (StrUtil.startWith(fieldName, MethodNameUtil.IS_PREFIX, ignoreCase)) {
// isName - setName
return StrUtil.equals(
"set" + StrUtil.removePrefix(fieldName, "is", ignoreCase),
MethodNameUtil.SET_PREFIX + StrUtil.removePrefix(fieldName, MethodNameUtil.IS_PREFIX, ignoreCase),
m.getName(), ignoreCase);
}

View File

@ -13,6 +13,7 @@
package org.dromara.hutool.core.net.url;
import org.dromara.hutool.core.convert.Convert;
import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.CharsetUtil;
@ -211,12 +212,14 @@ public class UrlQueryUtil {
return MapUtil.empty();
}
Console.log(queryMap);
final Map<String, List<String>> params = new LinkedHashMap<>();
queryMap.forEach((key, value) -> {
if(null != key && null != value){
if(null != key){
final List<String> values = params.computeIfAbsent(key.toString(), k -> new ArrayList<>(1));
// 一般是一个参数
values.add(key.toString());
values.add(StrUtil.toStringOrNull(value));
}
});
return params;

View File

@ -197,12 +197,12 @@ public class MethodMatcherUtil {
Objects.requireNonNull(fieldName);
Objects.requireNonNull(fieldType);
// 匹配方法名为 get + 首字母大写的属性名的无参数方法
Predicate<Method> nameMatcher = forName(CharSequenceUtil.upperFirstAndAddPre(fieldName, "get"));
Predicate<Method> nameMatcher = forName(CharSequenceUtil.upperFirstAndAddPre(fieldName, MethodNameUtil.GET_PREFIX));
// 查找方法名为属性名的无参数方法
nameMatcher = nameMatcher.or(forName(fieldName));
if (Objects.equals(boolean.class, fieldType) || Objects.equals(Boolean.class, fieldType)) {
// 匹配方法名为 get + 首字母大写的属性名的无参数方法
nameMatcher = nameMatcher.or(forName(CharSequenceUtil.upperFirstAndAddPre(fieldName, "is")));
// 匹配方法名为 is + 首字母大写的属性名的无参数方法
nameMatcher = nameMatcher.or(forName(CharSequenceUtil.upperFirstAndAddPre(fieldName, MethodNameUtil.IS_PREFIX)));
}
return allMatch(nameMatcher, forReturnType(fieldType), forNoneParameter());
}
@ -237,7 +237,7 @@ public class MethodMatcherUtil {
public static Predicate<Method> forSetterMethod(final String fieldName, final Class<?> fieldType) {
Objects.requireNonNull(fieldName);
Objects.requireNonNull(fieldType);
final Predicate<Method> nameMatcher = forName(CharSequenceUtil.upperFirstAndAddPre(fieldName, "set"))
final Predicate<Method> nameMatcher = forName(CharSequenceUtil.upperFirstAndAddPre(fieldName, MethodNameUtil.SET_PREFIX))
.or(forName(fieldName));
return allMatch(nameMatcher, forParameterTypes(fieldType));
}

View File

@ -0,0 +1,109 @@
/*
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.reflect.method;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.text.StrUtil;
import java.beans.Introspector;
/**
* 方法名相关工具如生成Getter和Setter方法获取其字段名等
*
* @author Looly
* @since 6.0.0
*/
public class MethodNameUtil {
/**
* getter方法前缀
*/
public static final String GET_PREFIX = "get";
/**
* setter方法前缀
*/
public static final String SET_PREFIX = "set";
/**
* is方法前缀
*/
public static final String IS_PREFIX = "is";
/**
* 转换名称为标准的字段名称规则为
* <ul>
* <li>首字母小写</li>
* <li>转悠名字保持大写第一个和第二个字母均为大写</li>
* </ul>
*
* <pre>
* Name =name
* name =name
* CPU =CPU
* </pre>
*
* @param name 字段名称
* @return 转换后的名称
* @see java.beans.Introspector#decapitalize(String)
*/
public static String decapitalize(final CharSequence name) {
return Introspector.decapitalize(StrUtil.toStringOrNull(name));
}
/**
* 获得set或get或is方法对应的标准属性名<br>
* 例如setName 返回 name
*
* <pre>
* getName =name
* setName =name
* isName =name
* </pre>
* <p>
* 需要注意的是相比{@link java.beans.Introspector#decapitalize(String)}此方法始终小写第一个字符
*
* @param getOrSetMethodName Get或Set方法名
* @return 如果是set或get方法名返回field 否则null
* @see java.beans.Introspector#decapitalize(String)
*/
public static String getGeneralField(final CharSequence getOrSetMethodName) {
Assert.notBlank(getOrSetMethodName);
final String getOrSetMethodNameStr = getOrSetMethodName.toString();
if (getOrSetMethodNameStr.startsWith(GET_PREFIX) || getOrSetMethodNameStr.startsWith(SET_PREFIX)) {
return StrUtil.removePreAndLowerFirst(getOrSetMethodName, 3);
} else if (getOrSetMethodNameStr.startsWith(IS_PREFIX)) {
return StrUtil.removePreAndLowerFirst(getOrSetMethodName, 2);
}
return null;
}
/**
* 生成set方法名<br>
* 例如name 返回 setName
*
* @param fieldName 属性名
* @return setXxx
*/
public static String genSetter(final CharSequence fieldName) {
return StrUtil.upperFirstAndAddPre(fieldName, SET_PREFIX);
}
/**
* 生成get方法名
*
* @param fieldName 属性名
* @return getXxx
*/
public static String genGetter(final CharSequence fieldName) {
return StrUtil.upperFirstAndAddPre(fieldName, GET_PREFIX);
}
}

View File

@ -482,14 +482,14 @@ public class MethodUtil {
String name = method.getName();
// 跳过set这类特殊方法
if ("set".equals(name)) {
if (name.length() < 4) {
return false;
}
if (ignoreCase) {
name = name.toLowerCase();
}
return name.startsWith("set");
return name.startsWith(MethodNameUtil.SET_PREFIX);
}
/**
@ -511,14 +511,18 @@ public class MethodUtil {
}
// 参数个数必须为0或1
final int parameterCount = method.getParameterCount();
if (0 != parameterCount) {
if (0 != method.getParameterCount()) {
return false;
}
// 必须有返回值
if(Void.class == method.getReturnType()){
return false;
}
String name = method.getName();
// 跳过getClassgetis这类特殊方法
if ("getClass".equals(name) || "get".equals(name) || "is".equals(name)) {
if (name.length() < 3 || "getClass".equals(name) || MethodNameUtil.GET_PREFIX.equals(name)) {
return false;
}
@ -526,11 +530,11 @@ public class MethodUtil {
name = name.toLowerCase();
}
if (name.startsWith("is")) {
if (name.startsWith(MethodNameUtil.IS_PREFIX)) {
// 判断返回值是否为Boolean
return BooleanUtil.isBoolean(method.getReturnType());
}
return name.startsWith("get");
return name.startsWith(MethodNameUtil.GET_PREFIX);
}
// region ----- invoke

View File

@ -12,7 +12,6 @@
package org.dromara.hutool.core.regex;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.collection.set.SetUtil;
import org.dromara.hutool.core.comparator.CompareUtil;
import org.dromara.hutool.core.comparator.StrLengthComparator;
@ -867,33 +866,42 @@ public class ReUtil {
*
* @param content 文本
* @param pattern {@link Pattern}
* @param replacementTemplate 替换的文本模板可以使用$1类似的变量提取正则匹配出的内容
* @param replacementTemplate 替换的文本模板可以使用$1类似的变量提取正则匹配出的内容null表示去除匹配
* @return 处理后的文本
* @since 3.0.4
*/
public static String replaceAll(final CharSequence content, final Pattern pattern, final String replacementTemplate) {
if(null != pattern && StrUtil.isNotEmpty(content) && StrUtil.isNotEmpty(replacementTemplate)){
final Matcher matcher = pattern.matcher(content);
boolean result = matcher.find();
if (result) {
final Set<String> varNums = findAll(PatternPool.GROUP_VAR, replacementTemplate, 1,
new TreeSet<>(StrLengthComparator.INSTANCE.reversed()));
final StringBuffer sb = new StringBuffer();
do {
String replacement = replacementTemplate;
for (final String var : varNums) {
final int group = Integer.parseInt(var);
replacement = replacement.replace("$" + var, matcher.group(group));
}
matcher.appendReplacement(sb, escape(replacement));
result = matcher.find();
} while (result);
matcher.appendTail(sb);
return sb.toString();
}
public static String replaceAll(final CharSequence content, final Pattern pattern, String replacementTemplate) {
if (StrUtil.isEmpty(content)) {
return StrUtil.toStringOrNull(content);
}
return StrUtil.toStringOrNull(replacementTemplate);
// replacementTemplate字段为null时按照去除匹配对待
if(null == replacementTemplate){
replacementTemplate = StrUtil.EMPTY;
}
Assert.notNull(replacementTemplate, "ReplacementTemplate must be not null !");
final Matcher matcher = pattern.matcher(content);
boolean result = matcher.find();
if (result) {
final Set<String> varNums = findAll(PatternPool.GROUP_VAR, replacementTemplate, 1,
new TreeSet<>(StrLengthComparator.INSTANCE.reversed()));
final StringBuffer sb = new StringBuffer();
do {
String replacement = replacementTemplate;
for (final String var : varNums) {
final int group = Integer.parseInt(var);
replacement = replacement.replace("$" + var, matcher.group(group));
}
matcher.appendReplacement(sb, escape(replacement));
result = matcher.find();
} while (result);
matcher.appendTail(sb);
return sb.toString();
}
// 无匹配结果返回原字符串
return StrUtil.toStringOrNull(content);
}
/**

View File

@ -3678,52 +3678,6 @@ public class CharSequenceUtil extends StrValidator {
return sb;
}
// ------------------------------------------------------------------------ getter and setter
/**
* 获得set或get或is方法对应的标准属性名<br>
* 例如setName 返回 name
*
* <pre>
* getName =name
* setName =name
* isName =name
* </pre>
*
* @param getOrSetMethodName Get或Set方法名
* @return 如果是set或get方法名返回field 否则null
*/
public static String getGeneralField(final CharSequence getOrSetMethodName) {
final String getOrSetMethodNameStr = getOrSetMethodName.toString();
if (getOrSetMethodNameStr.startsWith("get") || getOrSetMethodNameStr.startsWith("set")) {
return removePreAndLowerFirst(getOrSetMethodName, 3);
} else if (getOrSetMethodNameStr.startsWith("is")) {
return removePreAndLowerFirst(getOrSetMethodName, 2);
}
return null;
}
/**
* 生成set方法名<br>
* 例如name 返回 setName
*
* @param fieldName 属性名
* @return setXxx
*/
public static String genSetter(final CharSequence fieldName) {
return upperFirstAndAddPre(fieldName, "set");
}
/**
* 生成get方法名
*
* @param fieldName 属性名
* @return getXxx
*/
public static String genGetter(final CharSequence fieldName) {
return upperFirstAndAddPre(fieldName, "get");
}
// ------------------------------------------------------------------------ other
/**

View File

@ -135,8 +135,9 @@ public class XmlUtil extends XmlConstants {
*/
public static Document parseXml(final String xmlStr) {
if (StrUtil.isBlank(xmlStr)) {
throw new IllegalArgumentException("XML content string is empty !");
throw new IllegalArgumentException("XML content string is blank !");
}
return readXml(StrUtil.getReader(cleanInvalid(xmlStr)));
}
// endregion

View File

@ -89,6 +89,22 @@ public class BeanDescTest {
Assertions.assertEquals("张三", value);
}
@Test
void simpleBeanDescTest() {
final SimpleBeanDesc desc = new SimpleBeanDesc(User.class);
final User user = new User();
desc.getProp("name").setValue(user, "张三");
Assertions.assertEquals("张三", user.getName());
Object value = desc.getProp("name").getValue(user);
Assertions.assertEquals("张三", value);
desc.getProp("admin").setValue(user, true);
Assertions.assertTrue(user.isAdmin());
value = desc.getProp("admin").getValue(user);
Assertions.assertEquals(true, value);
}
public static class User {
private String name;
private int age;

View File

@ -16,6 +16,7 @@ import lombok.Data;
import org.dromara.hutool.core.xml.XmlUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Document;
import java.util.ArrayList;
import java.util.List;
@ -31,7 +32,8 @@ public class Issue3136Test {
@Test
void xmlToBeanTest() {
final String xmlStr = "<?xml version=\"1.0\" encoding=\"gbk\" ?><response><code>02</code><message></message></response>";
final SmsRes smsRes = XmlUtil.xmlToBean(XmlUtil.parseXml(xmlStr).getDocumentElement(), SmsRes.class);
final Document doc = XmlUtil.parseXml(xmlStr);
final SmsRes smsRes = XmlUtil.xmlToBean(doc.getDocumentElement(), SmsRes.class);
Assertions.assertEquals("02", smsRes.getCode());
Assertions.assertEquals(new Message(), smsRes.getMessage());

View File

@ -146,8 +146,11 @@ public class ReUtilTest {
final String replaceAll = ReUtil.replaceAll(content, pattern, parameters -> "->" + parameters.group(1) + "<-");
assertEquals("ZZZaaabbbccc中文->1234<-", replaceAll);
// 修改后判断ReUtil.replaceAll()方法当replacementTemplate为null对象时提示为非法的参数异常ReplacementTemplate must be not null !
Assertions.assertThrows(IllegalArgumentException.class, () -> ReUtil.replaceAll(content, pattern, str));
// 修改后判断ReUtil.replaceAll()方法当replacementTemplate为null对象时按照""处理表示去除匹配
String s = ReUtil.replaceAll(content, pattern, str);
assertEquals("ZZZaaabbbccc中文", s);
s = ReUtil.replaceAll(content, pattern, "");
assertEquals("ZZZaaabbbccc中文", s);
}
@Test