This commit is contained in:
Looly 2023-05-04 13:54:30 +08:00
parent 47ab0d02c3
commit 6d8d51ceca
18 changed files with 496 additions and 20 deletions

View File

@ -30,22 +30,6 @@
<properties>
<Automatic-Module-Name>org.dromara.hutool.core</Automatic-Module-Name>
<kotlin-version>1.8.21</kotlin-version>
</properties>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin-version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2023 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:
* http://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.copier.provider;
import org.dromara.hutool.core.bean.BeanDesc;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.bean.PropDesc;
import org.dromara.hutool.core.bean.copier.ValueProvider;
import org.dromara.hutool.core.convert.Convert;
import java.lang.reflect.Type;
/**
* Bean值提供器
*
* @author looly
*/
public class BeanValueProvider implements ValueProvider<String> {
private final Object bean;
private final BeanDesc beanDesc;
/**
* 构造
*
* @param bean Bean
*/
public BeanValueProvider(final Object bean) {
this.bean = bean;
this.beanDesc = BeanUtil.getBeanDesc(bean.getClass());
}
@Override
public Object value(final String key, final Type valueType) {
final PropDesc prop = beanDesc.getProp(key);
if (null != prop) {
return Convert.convert(valueType, prop.getValue(bean));
}
return null;
}
@Override
public boolean containsKey(final String key) {
return null != beanDesc.getProp(key);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2023 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:
* http://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.copier.provider;
import org.dromara.hutool.core.bean.copier.ValueProvider;
import org.dromara.hutool.core.convert.Convert;
import java.lang.reflect.Type;
import java.util.Map;
/**
* Map值提供者
*
* @author looly
*/
@SuppressWarnings("rawtypes")
public class MapValueProvider implements ValueProvider<String> {
private final Map map;
/**
* 构造
*
* @param map map
*/
public MapValueProvider(final Map map) {
this.map = map;
}
@Override
public Object value(final String key, final Type valueType) {
return Convert.convert(valueType, map.get(key));
}
@Override
public boolean containsKey(final String key) {
return map.containsKey(key);
}
}

View File

@ -16,6 +16,7 @@ import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.convert.impl.*;
import org.dromara.hutool.core.reflect.TypeReference;
import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.reflect.kotlin.KClassUtil;
import org.dromara.hutool.core.util.ObjUtil;
import java.lang.reflect.Type;
@ -145,6 +146,11 @@ public class CompositeConverter extends RegisterConverter {
return result;
}
// Kotlin Bean
if(KClassUtil.isKotlinClass(rowType)){
return (T) KBeanConverter.INSTANCE.convert(type, value);
}
// 尝试转Bean
if (BeanUtil.isBean(rowType)) {
return (T) BeanConverter.INSTANCE.convert(type, value);

View File

@ -23,6 +23,7 @@ import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.map.MapProxy;
import org.dromara.hutool.core.reflect.ConstructorUtil;
import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.reflect.kotlin.KClassUtil;
import java.io.Serializable;
import java.lang.reflect.Type;

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2023 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:
* http://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.convert.impl;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.bean.copier.ValueProvider;
import org.dromara.hutool.core.bean.copier.provider.BeanValueProvider;
import org.dromara.hutool.core.bean.copier.provider.MapValueProvider;
import org.dromara.hutool.core.convert.ConvertException;
import org.dromara.hutool.core.convert.Converter;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.reflect.kotlin.KClassUtil;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.Map;
/**
* Kotlin Bean转换器支持
* <pre>
* Map = Bean
* Bean = Bean
* ValueProvider = Bean
* </pre>
*
* @author Looly
*/
public class KBeanConverter implements Converter, Serializable {
private static final long serialVersionUID = 1L;
/**
* 单例对象
*/
public static KBeanConverter INSTANCE = new KBeanConverter();
@Override
public Object convert(final Type targetType, final Object value) throws ConvertException {
Assert.notNull(targetType);
if (null == value) {
return null;
}
// value本身实现了Converter接口直接调用
if(value instanceof Converter){
return ((Converter) value).convert(targetType, value);
}
final Class<?> targetClass = TypeUtil.getClass(targetType);
Assert.notNull(targetClass, "Target type is not a class!");
return convertInternal(targetType, targetClass, value);
}
@SuppressWarnings("unchecked")
private Object convertInternal(final Type targetType, final Class<?> targetClass, final Object value) {
ValueProvider<String> valueProvider = null;
if(value instanceof ValueProvider){
valueProvider = (ValueProvider<String>) value;
} else if(value instanceof Map){
valueProvider = new MapValueProvider((Map<String, ?>) value);
} else if(BeanUtil.isBean(value.getClass())){
valueProvider = new BeanValueProvider(value);
}
if(null != valueProvider){
return KClassUtil.newInstance(targetClass, valueProvider);
}
throw new ConvertException("Unsupported source type: [{}] to [{}]", value.getClass(), targetType);
}
}

View File

@ -16,6 +16,7 @@ import org.dromara.hutool.core.bean.NullWrapperBean;
import org.dromara.hutool.core.convert.Convert;
import org.dromara.hutool.core.exception.HutoolException;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.reflect.lookup.LookupUtil;
import java.lang.invoke.MethodHandle;
@ -139,6 +140,10 @@ public class MethodHandleUtil {
*/
private static Object[] actualArgs(final Method method, final Object[] args) {
final Class<?>[] parameterTypes = method.getParameterTypes();
if(1 == parameterTypes.length && parameterTypes[0].isArray()){
// 可变长参数不做转换
return args;
}
final Object[] actualArgs = new Object[parameterTypes.length];
if (null != args) {
for (int i = 0; i < actualArgs.length; i++) {

View File

@ -25,11 +25,14 @@ import java.util.List;
* @author VampireAchao, Looly
*/
public class KCallable {
private static final Method METHOD_GET_PARAMETERS;
private static final Method METHOD_CALL;
static {
final Class<?> kFunctionClass = ClassLoaderUtil.loadClass("kotlin.reflect.KCallable");
METHOD_GET_PARAMETERS = MethodUtil.getMethod(kFunctionClass, "getParameters");
METHOD_CALL = MethodUtil.getMethodByName(kFunctionClass, "call");
}
/**
@ -46,4 +49,15 @@ public class KCallable {
}
return result;
}
/**
* 实例化对象本质上调用KCallable.call方法
*
* @param kCallable kotlin的类方法或构造
* @param args 参数列表
* @return 参数列表
*/
public static Object call(final Object kCallable, final Object... args) {
return MethodUtil.invoke(kCallable, METHOD_CALL, new Object[]{args});
}
}

View File

@ -12,9 +12,13 @@
package org.dromara.hutool.core.reflect.kotlin;
import org.dromara.hutool.core.bean.copier.ValueProvider;
import org.dromara.hutool.core.bean.copier.provider.MapValueProvider;
import org.dromara.hutool.core.lang.Opt;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
/**
* Kotlin反射包装相关工具类
@ -23,11 +27,25 @@ import java.util.List;
*/
public class KClassUtil {
@SuppressWarnings("unchecked")
private static final Class<? extends Annotation> META_DATA_CLASS =
(Class<? extends Annotation>) Opt.ofTry(() -> Class.forName("kotlin.Metadata")).get();
/**
* 是否提供或处于Kotlin环境中
*/
public static final boolean IS_KOTLIN_ENABLE =
Opt.ofTry(() -> Class.forName("kotlin.Metadata")).isPresent();
public static final boolean IS_KOTLIN_ENABLE = null != META_DATA_CLASS;
/**
* 检查给定的类是否为Kotlin类<br>
* Kotlin类带有@kotlin.Metadata注解
*
* @param clazz
* @return 是否Kotlin类
*/
public static boolean isKotlinClass(final Class<?> clazz) {
return IS_KOTLIN_ENABLE && clazz.isAnnotationPresent(META_DATA_CLASS);
}
/**
* 获取Kotlin类的所有构造方法
@ -48,4 +66,52 @@ public class KClassUtil {
public static List<KParameter> getParameters(final Object kCallable) {
return KCallable.getParameters(kCallable);
}
/**
* {@link ValueProvider}中提取对应name的参数列表
*
* @param kCallable kotlin的类方法或构造
* @param valueProvider {@link ValueProvider}
* @return 参数数组
*/
public static Object[] getParameterValues(final Object kCallable, final ValueProvider<String> valueProvider) {
final List<KParameter> parameters = getParameters(kCallable);
final Object[] args = new Object[parameters.size()];
KParameter kParameter;
for (int i = 0; i < parameters.size(); i++) {
kParameter = parameters.get(i);
args[i] = valueProvider.value(kParameter.getName(), kParameter.getType());
}
return args;
}
/**
* 实例化Kotlin对象
*
* @param <T> 对象类型
* @param targetType 对象类型
* @param map 参数名和参数值的Map
* @return 对象
*/
public static <T> T newInstance(final Class<T> targetType, final Map<String, ?> map) {
return newInstance(targetType, new MapValueProvider(map));
}
/**
* 实例化Kotlin对象
*
* @param <T> 对象类型
* @param targetType 对象类型
* @param valueProvider 值提供器用于提供构造所需参数值
* @return 对象
*/
@SuppressWarnings("unchecked")
public static <T> T newInstance(final Class<T> targetType, final ValueProvider<String> valueProvider) {
final List<?> constructors = getConstructors(targetType);
for (final Object constructor : constructors) {
final Object[] parameterValues = getParameterValues(constructor, valueProvider);
return (T) KCallable.call(constructor, parameterValues);
}
return null;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2023 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:
* http://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.convert;
import org.dromara.hutool.core.reflect.kotlin.TestKBean;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
public class ConvertKBeanTest {
@Test
void mapToBeanTest(){
final HashMap<String, Object> map = new HashMap<>();
map.put("country", "中国");
map.put("age", 18);
map.put("id", "VampireAchao");
final TestKBean testKBean = Convert.convert(TestKBean.class, map);
Assertions.assertEquals("VampireAchao", testKBean.getId());
Assertions.assertEquals("中国", testKBean.getCountry());
Assertions.assertNull(testKBean.getName());
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2023 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:
* http://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.net.url;
import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.map.MapBuilder;
import org.dromara.hutool.core.util.CharsetUtil;
import org.junit.jupiter.api.Test;
import java.util.Map;
public class IssueI6ZF6KTest {
@Test
void buildQueryTest() {
final String json = "{\"keyword\":\"国际 英语 c&b\",\"anyKeyword\":\"1\"}";
final Map<String, Object> form = MapBuilder.<String, Object>of()
.put("condition", json)
.build();
final String requestBody = URLUtil.buildQuery(form, CharsetUtil.UTF_8);
Console.log(requestBody);
}
}

View File

@ -12,13 +12,28 @@
package org.dromara.hutool.core.reflect.kotlin;
import org.dromara.hutool.core.bean.copier.ValueProvider;
import org.dromara.hutool.core.bean.copier.provider.MapValueProvider;
import org.dromara.hutool.core.convert.Convert;
import org.dromara.hutool.core.map.MapUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
public class KClassUtilTest {
@Test
void isKotlinClassTest() {
boolean kotlinClass = KClassUtil.isKotlinClass(TestKBean.class);
Assertions.assertTrue(kotlinClass);
kotlinClass = KClassUtil.isKotlinClass(KClassUtilTest.class);
Assertions.assertFalse(kotlinClass);
}
@Test
void getConstructorTest() {
final List<?> constructors = KClassUtil.getConstructors(TestKBean.class);
@ -39,4 +54,18 @@ public class KClassUtilTest {
Assertions.assertEquals("name", parameters.get(1).getName());
Assertions.assertEquals("country", parameters.get(2).getName());
}
@Test
void newInstanceTest() {
final HashMap<String, Object> argsMap = new HashMap<>();
argsMap.put("country", "中国");
argsMap.put("age", 18);
argsMap.put("id", "VampireAchao");
final TestKBean testKBean = KClassUtil.newInstance(TestKBean.class, argsMap);
Assertions.assertEquals("VampireAchao", testKBean.getId());
Assertions.assertEquals("中国", testKBean.getCountry());
Assertions.assertNull(testKBean.getName());
}
}

View File

@ -85,6 +85,7 @@
<scope>provided</scope>
</dependency>
<!-- 仅用于测试 -->
<dependency>
<groupId>org.dromara.hutool</groupId>
<artifactId>hutool-json</artifactId>
@ -97,7 +98,6 @@
<version>0.1.2</version>
<scope>test</scope>
</dependency>
<!-- 仅用于测试 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
@ -107,7 +107,7 @@
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.10.0</version>
<version>4.11.0</version>
<scope>test</scope>
<exclusions>
<exclusion>

View File

@ -20,10 +20,12 @@ import org.dromara.hutool.core.convert.ConvertException;
import org.dromara.hutool.core.convert.Converter;
import org.dromara.hutool.core.convert.RegisterConverter;
import org.dromara.hutool.core.convert.impl.*;
import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.map.MapWrapper;
import org.dromara.hutool.core.reflect.ConstructorUtil;
import org.dromara.hutool.core.reflect.TypeReference;
import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.reflect.kotlin.KClassUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.json.*;
@ -210,6 +212,11 @@ public class JSONConverter implements Converter {
// 尝试转Bean
if (BeanUtil.isBean(rawType)) {
// issue#I5WDP0 对于Kotlin对象由于参数可能非空限制导致无法创建一个默认的对象再赋值
if(KClassUtil.isKotlinClass(rawType) && json instanceof JSONGetter){
return KClassUtil.newInstance(rawType, new JSONGetterValueProvider<>((JSONGetter<String>)json));
}
return BeanCopier.of(json,
ConstructorUtil.newInstanceIfPossible(rawType), targetType,
InternalJSONUtil.toCopyOptions(json.config())).copy();

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2023 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:
* http://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.json.convert;
import org.dromara.hutool.core.bean.copier.ValueProvider;
import org.dromara.hutool.json.JSONGetter;
import java.lang.reflect.Type;
/**
* JSONGetter的ValueProvider
*
* @param <K> 键类型
* @author looly
*/
public class JSONGetterValueProvider<K> implements ValueProvider<K> {
private final JSONGetter<K> jsonGetter;
/**
* 构造
*
* @param jsonGetter {@link JSONGetter}
*/
public JSONGetterValueProvider(final JSONGetter<K> jsonGetter) {
this.jsonGetter = jsonGetter;
}
@Override
public Object value(final K key, final Type valueType) {
return jsonGetter.get(key, valueType);
}
@Override
public boolean containsKey(final K key) {
return !jsonGetter.isNull(key);
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright (c) 2023 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:
* http://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.json.issueI5WDP0
data class ERPProduct(
var code:String,
var status:String
)

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 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:
* http://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.json.issueI5WDP0;
import org.dromara.hutool.json.JSONUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class JsonToBeanTest {
@Test
void toBeanTest() {
final String jsonStr = "{\"code\": \"201\", \"status\": \"ok\"}";
final ERPProduct bean = JSONUtil.toBean(jsonStr, ERPProduct.class);
Assertions.assertNotNull(bean);
Assertions.assertEquals("201", bean.getCode());
Assertions.assertEquals("ok", bean.getStatus());
}
}

14
pom.xml
View File

@ -51,6 +51,7 @@
<compile.version>8</compile.version>
<junit.version>5.9.2</junit.version>
<lombok.version>1.18.26</lombok.version>
<kotlin-version>1.8.21</kotlin-version>
</properties>
<dependencies>
@ -67,6 +68,19 @@
<version>${lombok.version}</version>
<scope>test</scope>
</dependency>
<!-- Kotlin适配 -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin-version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<issueManagement>