From ec71c6cd521131c5dc2ed4d2fea3eb78166b793b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=84=9F=E5=86=92=E7=81=B5?= Date: Fri, 19 Nov 2021 17:16:09 +0800 Subject: [PATCH] =?UTF-8?q?:=20=E5=B1=9E=E6=80=A7=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../support/ConversionServiceFactoryBean.java | 71 ++++ .../factory/convert/ConversionService.java | 16 + .../factory/convert/converter/Converter.java | 12 + .../convert/converter/ConverterFactory.java | 12 + .../convert/converter/ConverterRegistry.java | 16 + .../convert/converter/GenericConverter.java | 58 ++++ .../support/DefaultConversionService.java | 20 ++ .../support/GenericConversionService.java | 137 ++++++++ .../StringToNumberConverterFactory.java | 35 ++ .../AbstractAutowiredCapableBeanFactory.java | 12 + .../factory/support/AbstractBeanFactory.java | 11 + .../container/factory/utils/NumberUtils.java | 328 ++++++++++++++++++ 12 files changed, 728 insertions(+) create mode 100644 src/main/java/cn/noexception/container/context/support/ConversionServiceFactoryBean.java create mode 100644 src/main/java/cn/noexception/container/factory/convert/ConversionService.java create mode 100644 src/main/java/cn/noexception/container/factory/convert/converter/Converter.java create mode 100644 src/main/java/cn/noexception/container/factory/convert/converter/ConverterFactory.java create mode 100644 src/main/java/cn/noexception/container/factory/convert/converter/ConverterRegistry.java create mode 100644 src/main/java/cn/noexception/container/factory/convert/converter/GenericConverter.java create mode 100644 src/main/java/cn/noexception/container/factory/convert/support/DefaultConversionService.java create mode 100644 src/main/java/cn/noexception/container/factory/convert/support/GenericConversionService.java create mode 100644 src/main/java/cn/noexception/container/factory/convert/support/StringToNumberConverterFactory.java create mode 100644 src/main/java/cn/noexception/container/factory/utils/NumberUtils.java diff --git a/src/main/java/cn/noexception/container/context/support/ConversionServiceFactoryBean.java b/src/main/java/cn/noexception/container/context/support/ConversionServiceFactoryBean.java new file mode 100644 index 0000000..6281660 --- /dev/null +++ b/src/main/java/cn/noexception/container/context/support/ConversionServiceFactoryBean.java @@ -0,0 +1,71 @@ +package cn.noexception.container.context.support; + +import cn.noexception.container.factory.FactoryBean; +import cn.noexception.container.factory.InitializingBean; +import cn.noexception.container.factory.convert.ConversionService; +import cn.noexception.container.factory.convert.converter.Converter; +import cn.noexception.container.factory.convert.converter.ConverterFactory; +import cn.noexception.container.factory.convert.converter.ConverterRegistry; +import cn.noexception.container.factory.convert.converter.GenericConverter; +import cn.noexception.container.factory.convert.support.DefaultConversionService; +import cn.noexception.container.factory.convert.support.GenericConversionService; +import jdk.internal.jline.internal.Nullable; + +import java.util.Set; + +/** + * ConversionServiceFactoryBean + * + * @author 吕滔 + * @Date 2021/11/19 16:21 + */ +public class ConversionServiceFactoryBean implements FactoryBean, InitializingBean { + + @Nullable + private Set converters; + + @Nullable + private GenericConversionService conversionService; + + @Override + public ConversionService getObject() throws Exception { + return conversionService; + } + + @Override + public Class getObjectType() { + return conversionService.getClass(); + } + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public void afterPropertiesSet() throws Exception { + this.conversionService = new DefaultConversionService(); + registerConversions(converters, conversionService); + } + + private void registerConversions(Set converters, ConverterRegistry registry) { + if (converters != null) { + for (Object converter : converters) { + if (converter instanceof GenericConverter) { + registry.addConverter((GenericConverter) converter); + } else if (converter instanceof Converter) { + registry.addConverterFactory((ConverterFactory) converter); + } else if (converter instanceof ConverterFactory) { + registry.addConverterFactory((ConverterFactory) converter); + } else { + throw new IllegalArgumentException("Each converter object must implememnt one of the " + + "Converter, ConverterFactory, or GenericConverter interfaces"); + } + } + } + } + + public void setConverters(Set converters) { + this.converters = converters; + } +} diff --git a/src/main/java/cn/noexception/container/factory/convert/ConversionService.java b/src/main/java/cn/noexception/container/factory/convert/ConversionService.java new file mode 100644 index 0000000..04c7329 --- /dev/null +++ b/src/main/java/cn/noexception/container/factory/convert/ConversionService.java @@ -0,0 +1,16 @@ +package cn.noexception.container.factory.convert; + +import jdk.internal.jline.internal.Nullable; + +/** + * ConversionService + * + * @author 吕滔 + * @Date 2021/11/19 12:00 + */ +public interface ConversionService { + boolean canConvert(@Nullable Class sourceType, Class targetType); + + + T convert(Object source, Class targetType); +} diff --git a/src/main/java/cn/noexception/container/factory/convert/converter/Converter.java b/src/main/java/cn/noexception/container/factory/convert/converter/Converter.java new file mode 100644 index 0000000..b87f9b6 --- /dev/null +++ b/src/main/java/cn/noexception/container/factory/convert/converter/Converter.java @@ -0,0 +1,12 @@ +package cn.noexception.container.factory.convert.converter; + +/** + * Converter + * 定义类型转换接口 + * + * @author 吕滔 + * @Date 2021/11/18 16:42 + */ +public interface Converter { + T convert(S source); +} diff --git a/src/main/java/cn/noexception/container/factory/convert/converter/ConverterFactory.java b/src/main/java/cn/noexception/container/factory/convert/converter/ConverterFactory.java new file mode 100644 index 0000000..9065a42 --- /dev/null +++ b/src/main/java/cn/noexception/container/factory/convert/converter/ConverterFactory.java @@ -0,0 +1,12 @@ +package cn.noexception.container.factory.convert.converter; + +/** + * ConverterFactory + * 类型转换工厂 + * + * @author 吕滔 + * @Date 2021/11/18 16:43 + */ +public interface ConverterFactory { + Converter getConverter(Class targetType); +} diff --git a/src/main/java/cn/noexception/container/factory/convert/converter/ConverterRegistry.java b/src/main/java/cn/noexception/container/factory/convert/converter/ConverterRegistry.java new file mode 100644 index 0000000..1a50b7b --- /dev/null +++ b/src/main/java/cn/noexception/container/factory/convert/converter/ConverterRegistry.java @@ -0,0 +1,16 @@ +package cn.noexception.container.factory.convert.converter; + +/** + * ConverterRegistry + * 类型转换注册接口 + * + * @author 吕滔 + * @Date 2021/11/18 16:45 + */ +public interface ConverterRegistry { + void addConverter(Converter converter); + + void addConverter(GenericConverter converter); + + void addConverterFactory(ConverterFactory converterFactory); +} diff --git a/src/main/java/cn/noexception/container/factory/convert/converter/GenericConverter.java b/src/main/java/cn/noexception/container/factory/convert/converter/GenericConverter.java new file mode 100644 index 0000000..1d739d2 --- /dev/null +++ b/src/main/java/cn/noexception/container/factory/convert/converter/GenericConverter.java @@ -0,0 +1,58 @@ +package cn.noexception.container.factory.convert.converter; + +import cn.hutool.core.lang.Assert; + +import java.util.Set; + +/** + * GenericConverter + * + * @author 吕滔 + * @Date 2021/11/18 16:56 + */ +public interface GenericConverter { + + Set getConvertibleTypes(); + + Object convert(Object source, Class sourceType, Class targetType); + + + final class ConvertiblePair { + private final Class sourceType; + private final Class targetType; + + + public ConvertiblePair(Class sourceType, Class targetType) { + Assert.notNull(sourceType, "来源类型不能为空。"); + Assert.notNull(targetType, "目标类型不能为空。"); + this.sourceType = sourceType; + this.targetType = targetType; + } + + public Class getSourceType() { + return sourceType; + } + + public Class getTargetType() { + return targetType; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != ConvertiblePair.class) { + return false; + } + ConvertiblePair other = (ConvertiblePair) obj; + return this.sourceType.equals(other.sourceType) && this.targetType.equals(other.targetType); + } + + @Override + public int hashCode() { + return this.sourceType.hashCode() * 31 + this.targetType.hashCode(); + } + } + +} diff --git a/src/main/java/cn/noexception/container/factory/convert/support/DefaultConversionService.java b/src/main/java/cn/noexception/container/factory/convert/support/DefaultConversionService.java new file mode 100644 index 0000000..c92fd0d --- /dev/null +++ b/src/main/java/cn/noexception/container/factory/convert/support/DefaultConversionService.java @@ -0,0 +1,20 @@ +package cn.noexception.container.factory.convert.support; + +import cn.noexception.container.factory.convert.converter.ConverterRegistry; + +/** + * DefaultConversionService + * + * @author 吕滔 + * @Date 2021/11/18 16:50 + */ +public class DefaultConversionService extends GenericConversionService { + public DefaultConversionService(){ + addDefaultConverters(this); + } + + public static void addDefaultConverters(ConverterRegistry converterRegistry) { + // 添加各类类型转换工厂 + converterRegistry.addConverterFactory(new StringToNumberConverterFactory()); + } +} diff --git a/src/main/java/cn/noexception/container/factory/convert/support/GenericConversionService.java b/src/main/java/cn/noexception/container/factory/convert/support/GenericConversionService.java new file mode 100644 index 0000000..edc25a3 --- /dev/null +++ b/src/main/java/cn/noexception/container/factory/convert/support/GenericConversionService.java @@ -0,0 +1,137 @@ +package cn.noexception.container.factory.convert.support; + + +import cn.noexception.container.factory.convert.ConversionService; +import cn.noexception.container.factory.convert.converter.Converter; +import cn.noexception.container.factory.convert.converter.ConverterFactory; +import cn.noexception.container.factory.convert.converter.ConverterRegistry; +import cn.noexception.container.factory.convert.converter.GenericConverter; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.*; + +/** + * GenericConversionService + * + * @author 吕滔 + * @Date 2021/11/19 10:35 + */ +public class GenericConversionService implements ConversionService, ConverterRegistry { + + private Map converters = new HashMap(); + + @Override + public void addConverter(Converter converter) { + GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converter); + ConverterAdapter converterAdapter = new ConverterAdapter(typeInfo, converter); + for (GenericConverter.ConvertiblePair convertibleType : converterAdapter.getConvertibleTypes()) { + converters.put(convertibleType, converterAdapter); + } + } + + private final class ConverterAdapter implements GenericConverter{ + private final ConvertiblePair typeInfo; + private final Converter converter; + + public ConverterAdapter(ConvertiblePair typeInfo, Converter converter) { + this.typeInfo = typeInfo; + this.converter = (Converter) converter; + } + + @Override + public Set getConvertibleTypes() { + return Collections.singleton(typeInfo); + } + + @Override + public Object convert(Object source, Class sourceType, Class targetType) { + return converter.convert(source); + } + } + + private GenericConverter.ConvertiblePair getRequiredTypeInfo(Object object) { + Type[] types = object.getClass().getGenericInterfaces(); + ParameterizedType parameterizedType = (ParameterizedType) types[0]; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + Class sourceType = (Class) actualTypeArguments[0]; + Class targetType = (Class) actualTypeArguments[1]; + return new GenericConverter.ConvertiblePair(sourceType, targetType); + } + + @Override + public void addConverter(GenericConverter converter) { + for (GenericConverter.ConvertiblePair convertibleType : converter.getConvertibleTypes()) { + converters.put(convertibleType, converter); + } + } + + @Override + public void addConverterFactory(ConverterFactory converterFactory) { + GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converterFactory); + ConverterFactoryAdapter converterFactoryAdapter = new ConverterFactoryAdapter(typeInfo, converterFactory); + for (GenericConverter.ConvertiblePair convertibleType : converterFactoryAdapter.getConvertibleTypes()) { + converters.put(convertibleType, converterFactoryAdapter); + } + } + + private final class ConverterFactoryAdapter implements GenericConverter{ + private final ConvertiblePair typeInfo; + private final ConverterFactory converterFactory; + + private ConverterFactoryAdapter(ConvertiblePair typeInfo, ConverterFactory converterFactory) { + this.typeInfo = typeInfo; + this.converterFactory = (ConverterFactory) converterFactory; + } + + @Override + public Set getConvertibleTypes() { + return Collections.singleton(typeInfo); + } + + @Override + public Object convert(Object source, Class sourceType, Class targetType) { + return converterFactory.getConverter(targetType).convert(source); + } + } + + + + @Override + public boolean canConvert(Class sourceType, Class targetType) { + GenericConverter converter = getConverter(sourceType, targetType); + return converter != null; + } + + protected GenericConverter getConverter(Class sourceType, Class targetType) { + List> sourceCandidates = getClassHierarchy(sourceType); + List> targetCandidates = getClassHierarchy(targetType); + for (Class sourceCandidate : sourceCandidates) { + for (Class targetCandidate : targetCandidates) { + GenericConverter.ConvertiblePair convertiblePair = new GenericConverter.ConvertiblePair(sourceCandidate, targetCandidate); + GenericConverter converter = converters.get(convertiblePair); + if (converter != null) { + return converter; + } + } + } + return null; + } + + private List> getClassHierarchy(Class clazz) { + List> hierarchy = new ArrayList<>(); + while (clazz != null) { + hierarchy.add(clazz); + clazz = clazz.getSuperclass(); + } + return hierarchy; + } + + @Override + public T convert(Object source, Class targetType) { + Class sourceType = source.getClass(); + GenericConverter converter = getConverter(sourceType, targetType); + + return (T) converter.convert(source, sourceType, targetType); + } +} diff --git a/src/main/java/cn/noexception/container/factory/convert/support/StringToNumberConverterFactory.java b/src/main/java/cn/noexception/container/factory/convert/support/StringToNumberConverterFactory.java new file mode 100644 index 0000000..b7f3701 --- /dev/null +++ b/src/main/java/cn/noexception/container/factory/convert/support/StringToNumberConverterFactory.java @@ -0,0 +1,35 @@ +package cn.noexception.container.factory.convert.support; + +import cn.noexception.container.factory.convert.converter.Converter; +import cn.noexception.container.factory.convert.converter.ConverterFactory; +import cn.noexception.container.factory.utils.NumberUtils; +import jdk.internal.jline.internal.Nullable; + +/** + * StringToNumberConverterFactory + * + * @author 吕滔 + * @Date 2021/11/19 15:39 + */ +public class StringToNumberConverterFactory implements ConverterFactory { + @Override + public Converter getConverter(Class targetType) { + return new StringToNumber<>(targetType); + } + private static final class StringToNumber implements Converter{ + private final Class targetType; + + public StringToNumber(Class targetType) { + this.targetType = targetType; + } + + @Override + @Nullable + public T convert(String source) { + if (source.isEmpty()) { + return null; + } + return NumberUtils.parseNumber(source, this.targetType); + } + } +} diff --git a/src/main/java/cn/noexception/container/factory/support/AbstractAutowiredCapableBeanFactory.java b/src/main/java/cn/noexception/container/factory/support/AbstractAutowiredCapableBeanFactory.java index cbd3705..1dc27cb 100644 --- a/src/main/java/cn/noexception/container/factory/support/AbstractAutowiredCapableBeanFactory.java +++ b/src/main/java/cn/noexception/container/factory/support/AbstractAutowiredCapableBeanFactory.java @@ -2,11 +2,13 @@ package cn.noexception.container.factory.support; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.TypeUtil; import cn.noexception.container.BeansException; import cn.noexception.container.PropertyValue; import cn.noexception.container.PropertyValues; import cn.noexception.container.factory.*; import cn.noexception.container.factory.config.*; +import cn.noexception.container.factory.convert.ConversionService; import java.lang.reflect.Constructor; import java.lang.reflect.Method; @@ -178,6 +180,16 @@ public abstract class AbstractAutowiredCapableBeanFactory extends AbstractBeanFa // A 依赖 B,获取 B 的实例化 BeanReference beanReference = (BeanReference) value; value = getBean(beanReference.getBeanName()); + }else { + // 类型转换处理 + Class sourceType = value.getClass(); + Class targetType = (Class) TypeUtil.getFieldType(bean.getClass(), name); + ConversionService conversionService = getConversionService(); + if (conversionService != null) { + if (conversionService.canConvert(sourceType, targetType)) { + value = conversionService.convert(value, targetType); + } + } } // 属性填充 BeanUtil.setFieldValue(bean, name, value); diff --git a/src/main/java/cn/noexception/container/factory/support/AbstractBeanFactory.java b/src/main/java/cn/noexception/container/factory/support/AbstractBeanFactory.java index f09851e..bb52066 100644 --- a/src/main/java/cn/noexception/container/factory/support/AbstractBeanFactory.java +++ b/src/main/java/cn/noexception/container/factory/support/AbstractBeanFactory.java @@ -6,6 +6,7 @@ import cn.noexception.container.factory.FactoryBean; import cn.noexception.container.factory.config.BeanDefinition; import cn.noexception.container.factory.config.BeanPostProcessor; import cn.noexception.container.factory.config.ConfigurableBeanFactory; +import cn.noexception.container.factory.convert.ConversionService; import cn.noexception.container.factory.utils.ClassUtils; import cn.noexception.container.factory.utils.StringValueResolver; @@ -28,6 +29,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp */ private final List embeddedValueResolvers = new ArrayList<>(); + private ConversionService conversionService; + @Override public Object getBean(String name) throws BeansException { return doGetBean(name, null); @@ -97,4 +100,12 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp } return result; } + + public ConversionService getConversionService() { + return conversionService; + } + + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } } diff --git a/src/main/java/cn/noexception/container/factory/utils/NumberUtils.java b/src/main/java/cn/noexception/container/factory/utils/NumberUtils.java new file mode 100644 index 0000000..4ea52f5 --- /dev/null +++ b/src/main/java/cn/noexception/container/factory/utils/NumberUtils.java @@ -0,0 +1,328 @@ +package cn.noexception.container.factory.utils; + +import cn.hutool.core.lang.Assert; +import jdk.internal.jline.internal.Nullable; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * NumberUtils + * + * @author 吕滔 + * @Date 2021/11/19 15:44 + */ +public class NumberUtils { + + private static final BigInteger LONG_MIN = BigInteger.valueOf(Long.MIN_VALUE); + + private static final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE); + + /** + * Standard number types (all immutable): + * Byte, Short, Integer, Long, BigInteger, Float, Double, BigDecimal. + */ + public static final Set> STANDARD_NUMBER_TYPES; + + static { + Set> numberTypes = new HashSet<>(8); + numberTypes.add(Byte.class); + numberTypes.add(Short.class); + numberTypes.add(Integer.class); + numberTypes.add(Long.class); + numberTypes.add(BigInteger.class); + numberTypes.add(Float.class); + numberTypes.add(Double.class); + numberTypes.add(BigDecimal.class); + STANDARD_NUMBER_TYPES = Collections.unmodifiableSet(numberTypes); + } + + + /** + * Convert the given number into an instance of the given target class. + * @param number the number to convert + * @param targetClass the target class to convert to + * @return the converted number + * @throws IllegalArgumentException if the target class is not supported + * (i.e. not a standard Number subclass as included in the JDK) + * @see java.lang.Byte + * @see java.lang.Short + * @see java.lang.Integer + * @see java.lang.Long + * @see java.math.BigInteger + * @see java.lang.Float + * @see java.lang.Double + * @see java.math.BigDecimal + */ + @SuppressWarnings("unchecked") + public static T convertNumberToTargetClass(Number number, Class targetClass) + throws IllegalArgumentException { + + Assert.notNull(number, "Number must not be null"); + Assert.notNull(targetClass, "Target class must not be null"); + + if (targetClass.isInstance(number)) { + return (T) number; + } + else if (Byte.class == targetClass) { + long value = checkedLongValue(number, targetClass); + if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) { + raiseOverflowException(number, targetClass); + } + return (T) Byte.valueOf(number.byteValue()); + } + else if (Short.class == targetClass) { + long value = checkedLongValue(number, targetClass); + if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) { + raiseOverflowException(number, targetClass); + } + return (T) Short.valueOf(number.shortValue()); + } + else if (Integer.class == targetClass) { + long value = checkedLongValue(number, targetClass); + if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) { + raiseOverflowException(number, targetClass); + } + return (T) Integer.valueOf(number.intValue()); + } + else if (Long.class == targetClass) { + long value = checkedLongValue(number, targetClass); + return (T) Long.valueOf(value); + } + else if (BigInteger.class == targetClass) { + if (number instanceof BigDecimal) { + // do not lose precision - use BigDecimal's own conversion + return (T) ((BigDecimal) number).toBigInteger(); + } + else { + // original value is not a Big* number - use standard long conversion + return (T) BigInteger.valueOf(number.longValue()); + } + } + else if (Float.class == targetClass) { + return (T) Float.valueOf(number.floatValue()); + } + else if (Double.class == targetClass) { + return (T) Double.valueOf(number.doubleValue()); + } + else if (BigDecimal.class == targetClass) { + // always use BigDecimal(String) here to avoid unpredictability of BigDecimal(double) + // (see BigDecimal javadoc for details) + return (T) new BigDecimal(number.toString()); + } + else { + throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" + + number.getClass().getName() + "] to unsupported target class [" + targetClass.getName() + "]"); + } + } + + /** + * Check for a {@code BigInteger}/{@code BigDecimal} long overflow + * before returning the given number as a long value. + * @param number the number to convert + * @param targetClass the target class to convert to + * @return the long value, if convertible without overflow + * @throws IllegalArgumentException if there is an overflow + * @see #raiseOverflowException + */ + private static long checkedLongValue(Number number, Class targetClass) { + BigInteger bigInt = null; + if (number instanceof BigInteger) { + bigInt = (BigInteger) number; + } + else if (number instanceof BigDecimal) { + bigInt = ((BigDecimal) number).toBigInteger(); + } + // Effectively analogous to JDK 8's BigInteger.longValueExact() + if (bigInt != null && (bigInt.compareTo(LONG_MIN) < 0 || bigInt.compareTo(LONG_MAX) > 0)) { + raiseOverflowException(number, targetClass); + } + return number.longValue(); + } + + /** + * Raise an overflow exception for the given number and target class. + * @param number the number we tried to convert + * @param targetClass the target class we tried to convert to + * @throws IllegalArgumentException if there is an overflow + */ + private static void raiseOverflowException(Number number, Class targetClass) { + throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" + + number.getClass().getName() + "] to target class [" + targetClass.getName() + "]: overflow"); + } + + /** + * Parse the given {@code text} into a {@link Number} instance of the given + * target class, using the corresponding {@code decode} / {@code valueOf} method. + *

Trims all whitespace (leading, trailing, and in between characters) from + * the input {@code String} before attempting to parse the number. + *

Supports numbers in hex format (with leading "0x", "0X", or "#") as well. + * @param text the text to convert + * @param targetClass the target class to parse into + * @return the parsed number + * @throws IllegalArgumentException if the target class is not supported + * (i.e. not a standard Number subclass as included in the JDK) + * @see Byte#decode + * @see Short#decode + * @see Integer#decode + * @see Long#decode + * @see #decodeBigInteger(String) + * @see Float#valueOf + * @see Double#valueOf + * @see java.math.BigDecimal#BigDecimal(String) + */ + @SuppressWarnings("unchecked") + public static T parseNumber(String text, Class targetClass) { + Assert.notNull(text, "Text must not be null"); + Assert.notNull(targetClass, "Target class must not be null"); + String trimmed = trimAllWhitespace(text); + + if (Byte.class == targetClass) { + return (T) (isHexNumber(trimmed) ? Byte.decode(trimmed) : Byte.valueOf(trimmed)); + } + else if (Short.class == targetClass) { + return (T) (isHexNumber(trimmed) ? Short.decode(trimmed) : Short.valueOf(trimmed)); + } + else if (Integer.class == targetClass) { + return (T) (isHexNumber(trimmed) ? Integer.decode(trimmed) : Integer.valueOf(trimmed)); + } + else if (Long.class == targetClass) { + return (T) (isHexNumber(trimmed) ? Long.decode(trimmed) : Long.valueOf(trimmed)); + } + else if (BigInteger.class == targetClass) { + return (T) (isHexNumber(trimmed) ? decodeBigInteger(trimmed) : new BigInteger(trimmed)); + } + else if (Float.class == targetClass) { + return (T) Float.valueOf(trimmed); + } + else if (Double.class == targetClass) { + return (T) Double.valueOf(trimmed); + } + else if (BigDecimal.class == targetClass || Number.class == targetClass) { + return (T) new BigDecimal(trimmed); + } + else { + throw new IllegalArgumentException( + "Cannot convert String [" + text + "] to target class [" + targetClass.getName() + "]"); + } + } + + /** + * Parse the given {@code text} into a {@link Number} instance of the + * given target class, using the supplied {@link NumberFormat}. + *

Trims the input {@code String} before attempting to parse the number. + * @param text the text to convert + * @param targetClass the target class to parse into + * @param numberFormat the {@code NumberFormat} to use for parsing (if + * {@code null}, this method falls back to {@link #parseNumber(String, Class)}) + * @return the parsed number + * @throws IllegalArgumentException if the target class is not supported + * (i.e. not a standard Number subclass as included in the JDK) + * @see java.text.NumberFormat#parse + * @see #convertNumberToTargetClass + * @see #parseNumber(String, Class) + */ + public static T parseNumber( + String text, Class targetClass, @Nullable NumberFormat numberFormat) { + + if (numberFormat != null) { + Assert.notNull(text, "Text must not be null"); + Assert.notNull(targetClass, "Target class must not be null"); + DecimalFormat decimalFormat = null; + boolean resetBigDecimal = false; + if (numberFormat instanceof DecimalFormat) { + decimalFormat = (DecimalFormat) numberFormat; + if (BigDecimal.class == targetClass && !decimalFormat.isParseBigDecimal()) { + decimalFormat.setParseBigDecimal(true); + resetBigDecimal = true; + } + } + try { + Number number = numberFormat.parse(trimAllWhitespace(text)); + return convertNumberToTargetClass(number, targetClass); + } + catch (ParseException ex) { + throw new IllegalArgumentException("Could not parse number: " + ex.getMessage()); + } + finally { + if (resetBigDecimal) { + decimalFormat.setParseBigDecimal(false); + } + } + } + else { + return parseNumber(text, targetClass); + } + } + + public static String trimAllWhitespace(String str) { + if (!hasLength(str)) { + return str; + } + + int len = str.length(); + StringBuilder sb = new StringBuilder(str.length()); + for (int i = 0; i < len; i++) { + char c = str.charAt(i); + if (!Character.isWhitespace(c)) { + sb.append(c); + } + } + return sb.toString(); + } + + public static boolean hasLength(@Nullable String str) { + return (str != null && !str.isEmpty()); + } + + /** + * Determine whether the given {@code value} String indicates a hex number, + * i.e. needs to be passed into {@code Integer.decode} instead of + * {@code Integer.valueOf}, etc. + */ + private static boolean isHexNumber(String value) { + int index = (value.startsWith("-") ? 1 : 0); + return (value.startsWith("0x", index) || value.startsWith("0X", index) || value.startsWith("#", index)); + } + + /** + * Decode a {@link java.math.BigInteger} from the supplied {@link String} value. + *

Supports decimal, hex, and octal notation. + * @see BigInteger#BigInteger(String, int) + */ + private static BigInteger decodeBigInteger(String value) { + int radix = 10; + int index = 0; + boolean negative = false; + + // Handle minus sign, if present. + if (value.startsWith("-")) { + negative = true; + index++; + } + + // Handle radix specifier, if present. + if (value.startsWith("0x", index) || value.startsWith("0X", index)) { + index += 2; + radix = 16; + } + else if (value.startsWith("#", index)) { + index++; + radix = 16; + } + else if (value.startsWith("0", index) && value.length() > 1 + index) { + index++; + radix = 8; + } + + BigInteger result = new BigInteger(value.substring(index), radix); + return (negative ? result.negate() : result); + } + +} -- GitLab