mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
fix code
This commit is contained in:
parent
3b327ec7c3
commit
7cb48f9cd2
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.dromara.hutool.core.bean.copier.ValueProvider;
|
||||
import org.dromara.hutool.core.classloader.ClassLoaderUtil;
|
||||
import org.dromara.hutool.core.reflect.ConstructorUtil;
|
||||
import org.dromara.hutool.core.reflect.MethodUtil;
|
||||
import org.dromara.hutool.core.util.JdkUtil;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* java.lang.Record 相关工具类封装<br>
|
||||
* 来自于FastJSON2的BeanUtils
|
||||
*
|
||||
* @author fastjson2, looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class RecordUtil {
|
||||
|
||||
private static volatile Class<?> RECORD_CLASS;
|
||||
|
||||
private static volatile Method METHOD_GET_RECORD_COMPONENTS;
|
||||
private static volatile Method METHOD_COMPONENT_GET_NAME;
|
||||
private static volatile Method METHOD_COMPONENT_GET_GENERIC_TYPE;
|
||||
|
||||
/**
|
||||
* 判断给定类是否为Record类
|
||||
*
|
||||
* @param clazz 类
|
||||
* @return 是否为Record类
|
||||
*/
|
||||
public static boolean isRecord(final Class<?> clazz) {
|
||||
if (JdkUtil.JVM_VERSION < 14) {
|
||||
// JDK14+支持Record类
|
||||
return false;
|
||||
}
|
||||
final Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (RECORD_CLASS == null) {
|
||||
// 此处不使用同步代码,重复赋值并不影响判断
|
||||
final String superclassName = superClass.getName();
|
||||
if ("java.lang.Record".equals(superclassName)) {
|
||||
RECORD_CLASS = superClass;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return superClass == RECORD_CLASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Record类中所有字段名称,getter方法名与字段同名
|
||||
*
|
||||
* @param recordClass Record类
|
||||
* @return 字段数组
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Map.Entry<String, Type>[] getRecordComponents(final Class<?> recordClass) {
|
||||
if (JdkUtil.JVM_VERSION < 14) {
|
||||
// JDK14+支持Record类
|
||||
return new Map.Entry[0];
|
||||
}
|
||||
if (null == METHOD_GET_RECORD_COMPONENTS) {
|
||||
METHOD_GET_RECORD_COMPONENTS = MethodUtil.getMethod(Class.class, "getRecordComponents");
|
||||
}
|
||||
|
||||
final Class<Object> recordComponentClass = ClassLoaderUtil.loadClass("java.lang.reflect.RecordComponent");
|
||||
if (METHOD_COMPONENT_GET_NAME == null) {
|
||||
METHOD_COMPONENT_GET_NAME = MethodUtil.getMethod(recordComponentClass, "getName");
|
||||
}
|
||||
if (METHOD_COMPONENT_GET_GENERIC_TYPE == null) {
|
||||
METHOD_COMPONENT_GET_GENERIC_TYPE = MethodUtil.getMethod(recordComponentClass, "getGenericType");
|
||||
}
|
||||
|
||||
final Object[] components = MethodUtil.invoke(recordClass, METHOD_GET_RECORD_COMPONENTS);
|
||||
final Map.Entry<String, Type>[] entries = new Map.Entry[components.length];
|
||||
for (int i = 0; i < components.length; i++) {
|
||||
entries[i] = new AbstractMap.SimpleEntry<>(
|
||||
MethodUtil.invoke(components[i], METHOD_COMPONENT_GET_NAME),
|
||||
MethodUtil.invoke(components[i], METHOD_COMPONENT_GET_GENERIC_TYPE)
|
||||
);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化Record类
|
||||
*
|
||||
* @param recordClass 类
|
||||
* @param valueProvider 参数值提供器
|
||||
* @return Record类
|
||||
*/
|
||||
public static Object newInstance(final Class<?> recordClass, final ValueProvider<String> valueProvider) {
|
||||
final Map.Entry<String, Type>[] recordComponents = getRecordComponents(recordClass);
|
||||
final Object[] args = new Object[recordComponents.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
args[i] = valueProvider.value(recordComponents[i].getKey(), recordComponents[i].getValue());
|
||||
}
|
||||
|
||||
return ConstructorUtil.newInstance(recordClass, args);
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@
|
||||
package org.dromara.hutool.core.convert;
|
||||
|
||||
import org.dromara.hutool.core.bean.BeanUtil;
|
||||
import org.dromara.hutool.core.bean.RecordUtil;
|
||||
import org.dromara.hutool.core.convert.impl.*;
|
||||
import org.dromara.hutool.core.reflect.TypeReference;
|
||||
import org.dromara.hutool.core.reflect.TypeUtil;
|
||||
@ -146,11 +147,6 @@ 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);
|
||||
@ -225,6 +221,16 @@ public class CompositeConverter extends RegisterConverter {
|
||||
return ArrayConverter.INSTANCE.convert(type, value, defaultValue);
|
||||
}
|
||||
|
||||
// Record
|
||||
if(RecordUtil.isRecord(rowType)){
|
||||
return (T) RecordConverter.INSTANCE.convert(type, value);
|
||||
}
|
||||
|
||||
// Kotlin Bean
|
||||
if(KClassUtil.isKotlinClass(rowType)){
|
||||
return (T) KBeanConverter.INSTANCE.convert(type, value);
|
||||
}
|
||||
|
||||
// 表示非需要特殊转换的对象
|
||||
return null;
|
||||
}
|
||||
|
@ -37,6 +37,9 @@ import java.util.stream.Collectors;
|
||||
public class EnumConverter extends AbstractConverter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 单例
|
||||
*/
|
||||
public static final EnumConverter INSTANCE = new EnumConverter();
|
||||
|
||||
private static final WeakConcurrentMap<Class<?>, Map<Class<?>, Method>> VALUE_OF_METHOD_CACHE = new WeakConcurrentMap<>();
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.RecordUtil;
|
||||
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.AbstractConverter;
|
||||
import org.dromara.hutool.core.convert.ConvertException;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Record类的转换器,支持:
|
||||
* <pre>
|
||||
* Map =》 Record
|
||||
* Bean =》 Record
|
||||
* ValueProvider =》 Record
|
||||
* </pre>
|
||||
*/
|
||||
public class RecordConverter extends AbstractConverter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 单例对象
|
||||
*/
|
||||
public static RecordConverter INSTANCE = new RecordConverter();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected Object convertInternal(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 RecordUtil.newInstance(targetClass, valueProvider);
|
||||
}
|
||||
|
||||
throw new ConvertException("Unsupported source type: [{}] to [{}]", value.getClass(), targetClass);
|
||||
}
|
||||
}
|
@ -12,11 +12,8 @@
|
||||
|
||||
package org.dromara.hutool.core.reflect;
|
||||
|
||||
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;
|
||||
@ -82,7 +79,7 @@ public class MethodHandleUtil {
|
||||
*/
|
||||
public static <T> T invoke(final Object obj, final Method method, final Object... args) throws HutoolException{
|
||||
Assert.notNull(method, "Method must be not null!");
|
||||
return invokeExact(obj, method, actualArgs(method, args));
|
||||
return invokeExact(obj, method, MethodUtil.actualArgs(method, args));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -123,48 +120,4 @@ public class MethodHandleUtil {
|
||||
throw new HutoolException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户传入参数:
|
||||
* <ul>
|
||||
* <li>1、忽略多余的参数</li>
|
||||
* <li>2、参数不够补齐默认值</li>
|
||||
* <li>3、通过NullWrapperBean传递的参数,会直接赋值null</li>
|
||||
* <li>4、传入参数为null,但是目标参数类型为原始类型,做转换</li>
|
||||
* <li>5、传入参数类型不对应,尝试转换类型</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param method 方法
|
||||
* @param args 参数
|
||||
* @return 实际的参数数组
|
||||
*/
|
||||
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++) {
|
||||
if (i >= args.length || null == args[i]) {
|
||||
// 越界或者空值
|
||||
actualArgs[i] = ClassUtil.getDefaultValue(parameterTypes[i]);
|
||||
} else if (args[i] instanceof NullWrapperBean) {
|
||||
//如果是通过NullWrapperBean传递的null参数,直接赋值null
|
||||
actualArgs[i] = null;
|
||||
} else if (!parameterTypes[i].isAssignableFrom(args[i].getClass())) {
|
||||
//对于类型不同的字段,尝试转换,转换失败则使用原对象类型
|
||||
final Object targetValue = Convert.convert(parameterTypes[i], args[i], args[i]);
|
||||
if (null != targetValue) {
|
||||
actualArgs[i] = targetValue;
|
||||
}
|
||||
} else {
|
||||
actualArgs[i] = args[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actualArgs;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import org.dromara.hutool.core.bean.NullWrapperBean;
|
||||
import org.dromara.hutool.core.classloader.ClassLoaderUtil;
|
||||
import org.dromara.hutool.core.collection.set.SetUtil;
|
||||
import org.dromara.hutool.core.collection.set.UniqueKeySet;
|
||||
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.Singleton;
|
||||
@ -24,6 +25,7 @@ import org.dromara.hutool.core.map.WeakConcurrentMap;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.util.BooleanUtil;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
@ -624,8 +626,18 @@ public class MethodUtil {
|
||||
* @throws HutoolException 一些列异常的包装
|
||||
* @see MethodHandleUtil#invoke(Object, Method, Object...)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T invoke(final Object obj, final Method method, final Object... args) throws HutoolException {
|
||||
return MethodHandleUtil.invoke(obj, method, args);
|
||||
try{
|
||||
return MethodHandleUtil.invoke(obj, method, args);
|
||||
} catch (final Exception e){
|
||||
// 传统反射方式执行方法
|
||||
try {
|
||||
return (T) method.invoke(ModifierUtil.isStatic(method) ? null : obj, actualArgs(method, args));
|
||||
} catch (final IllegalAccessException | InvocationTargetException ex) {
|
||||
throw new HutoolException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -742,6 +754,50 @@ public class MethodUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户传入参数:
|
||||
* <ul>
|
||||
* <li>1、忽略多余的参数</li>
|
||||
* <li>2、参数不够补齐默认值</li>
|
||||
* <li>3、通过NullWrapperBean传递的参数,会直接赋值null</li>
|
||||
* <li>4、传入参数为null,但是目标参数类型为原始类型,做转换</li>
|
||||
* <li>5、传入参数类型不对应,尝试转换类型</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param method 方法
|
||||
* @param args 参数
|
||||
* @return 实际的参数数组
|
||||
*/
|
||||
public 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++) {
|
||||
if (i >= args.length || null == args[i]) {
|
||||
// 越界或者空值
|
||||
actualArgs[i] = ClassUtil.getDefaultValue(parameterTypes[i]);
|
||||
} else if (args[i] instanceof NullWrapperBean) {
|
||||
//如果是通过NullWrapperBean传递的null参数,直接赋值null
|
||||
actualArgs[i] = null;
|
||||
} else if (!parameterTypes[i].isAssignableFrom(args[i].getClass())) {
|
||||
//对于类型不同的字段,尝试转换,转换失败则使用原对象类型
|
||||
final Object targetValue = Convert.convert(parameterTypes[i], args[i], args[i]);
|
||||
if (null != targetValue) {
|
||||
actualArgs[i] = targetValue;
|
||||
}
|
||||
} else {
|
||||
actualArgs[i] = args[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actualArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取方法的唯一键,结构为:
|
||||
* <pre>
|
||||
|
@ -14,13 +14,13 @@ package org.dromara.hutool.json.convert;
|
||||
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.core.bean.BeanUtil;
|
||||
import org.dromara.hutool.core.bean.RecordUtil;
|
||||
import org.dromara.hutool.core.bean.copier.BeanCopier;
|
||||
import org.dromara.hutool.core.convert.Convert;
|
||||
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;
|
||||
@ -228,7 +228,7 @@ public class JSONConverter implements Converter {
|
||||
}
|
||||
|
||||
// 无法转换
|
||||
throw new JSONException("Can not convert from {}: [{}] to [{}]",
|
||||
throw new JSONException("Can not convert from '{}': {} to '{}'",
|
||||
json.getClass().getName(), json, targetType.getTypeName());
|
||||
}
|
||||
|
||||
@ -280,6 +280,11 @@ public class JSONConverter implements Converter {
|
||||
return (T) ArrayConverter.INSTANCE.convert(type, value);
|
||||
}
|
||||
|
||||
// Record
|
||||
if(RecordUtil.isRecord(rowType)){
|
||||
return (T) RecordConverter.INSTANCE.convert(type, value);
|
||||
}
|
||||
|
||||
// 表示非需要特殊转换的对象
|
||||
return null;
|
||||
}
|
||||
|
@ -13,10 +13,12 @@
|
||||
package org.dromara.hutool.json.mapper;
|
||||
|
||||
import org.dromara.hutool.core.bean.BeanUtil;
|
||||
import org.dromara.hutool.core.bean.RecordUtil;
|
||||
import org.dromara.hutool.core.bean.copier.CopyOptions;
|
||||
import org.dromara.hutool.core.convert.Convert;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.lang.mutable.MutableEntry;
|
||||
import org.dromara.hutool.core.reflect.MethodUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.json.InternalJSONUtil;
|
||||
import org.dromara.hutool.json.JSONArray;
|
||||
@ -30,6 +32,7 @@ import org.dromara.hutool.json.serialize.JSONSerializer;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
@ -105,7 +108,7 @@ public class JSONObjectMapper {
|
||||
if (source instanceof JSONTokener) {
|
||||
// JSONTokener
|
||||
mapFromTokener((JSONTokener) source, jsonObject);
|
||||
}else if (source instanceof Map) {
|
||||
} else if (source instanceof Map) {
|
||||
// Map
|
||||
for (final Map.Entry<?, ?> e : ((Map<?, ?>) source).entrySet()) {
|
||||
jsonObject.set(Convert.toStr(e.getKey()), e.getValue(), predicate, false);
|
||||
@ -125,11 +128,14 @@ public class JSONObjectMapper {
|
||||
} else if (source instanceof ResourceBundle) {
|
||||
// ResourceBundle
|
||||
mapFromResourceBundle((ResourceBundle) source, jsonObject);
|
||||
} else if (RecordUtil.isRecord(source.getClass())) {
|
||||
// since 6.0.0
|
||||
mapFromRecord(source, jsonObject);
|
||||
} else if (BeanUtil.isReadableBean(source.getClass())) {
|
||||
// 普通Bean
|
||||
mapFromBean(source, jsonObject);
|
||||
} else {
|
||||
if(!jsonObject.config().isIgnoreError()){
|
||||
if (!jsonObject.config().isIgnoreError()) {
|
||||
// 不支持对象类型转换为JSONObject
|
||||
throw new JSONException("Unsupported type [{}] to JSONObject!", source.getClass());
|
||||
}
|
||||
@ -180,6 +186,22 @@ public class JSONObjectMapper {
|
||||
JSONParser.of(x).parseTo(jsonObject, this.predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Record转换
|
||||
*
|
||||
* @param record Record对象
|
||||
* @param jsonObject {@link JSONObject}
|
||||
*/
|
||||
private void mapFromRecord(final Object record, final JSONObject jsonObject) {
|
||||
final Map.Entry<String, Type>[] components = RecordUtil.getRecordComponents(record.getClass());
|
||||
|
||||
String key;
|
||||
for (final Map.Entry<String, Type> entry : components) {
|
||||
key = entry.getKey();
|
||||
jsonObject.set(key, MethodUtil.invoke(record, key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Bean转换
|
||||
*
|
||||
|
3
pom.xml
3
pom.xml
@ -179,6 +179,9 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<configuration>
|
||||
<argLine>--add-opens java.base/java.lang=ALL-UNNAMED</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
Loading…
x
Reference in New Issue
Block a user