package org.osgl.util; import org.osgl.$; import org.osgl.exception.NotAppliedException; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; import java.util.HashMap; import java.util.List; import java.util.Map; /** * A String value resolver resolves a {@link String string value} into * a certain type of object instance. */ public abstract class StringValueResolver<T> extends $.F1<String, T> { private final Type targetType; protected Map<String, Object> attributes = new HashMap<String, Object>(); public StringValueResolver() { targetType = findTargetType(); } protected StringValueResolver(Class<T> targetType) { this.targetType = $.notNull(targetType); } public abstract T resolve(String value); public Class<T> targetType() { return Generics.classOf(targetType); } public Type genericTargetType() { return targetType; } private Type findTargetType() { return findTargetType(getClass()); } private static Type findTargetType(Class<?> clazz) { List<Type> typeParams = Generics.typeParamImplementations(clazz, StringValueResolver.class); if (typeParams.size() > 0) { return typeParams.get(0); } throw E.unsupport("Cannot identify the target type from %s", clazz); } @Override public final T apply(String s) throws NotAppliedException, $.Break { return resolve(s); } /** * Set attribute of this resolver. * * Note use this method only on new resolver instance instead of shared instance * * @param key the attribute key * @param value the attribute value * @return this resolver instance */ public StringValueResolver<T> attribute(String key, Object value) { if (null == value) { attributes.remove(value); } else { attributes.put(key, value); } return this; } /** * Set attributes to this resolver * * Note use this method only on new resolver instance instead of shared instance * * @param attributes the attributes map * @return this resolver instance */ public StringValueResolver<T> attributes(Map<String, Object> attributes) { this.attributes.putAll(attributes); return this; } /** * Clear all attributes on this resolver * @return this resolver instance */ public StringValueResolver<T> clearAttributes() { attributes.clear(); return this; } /** * Get attribute of this resolver by key specified * @param key the attribute key * @param <V> the generic type variable of attribute value * @return the attribute value */ protected <V> V attribute(String key) { return (V) attributes.get(key); } /** * Returns an amended copy of this resolver based on the {@link AnnotationAware} * provided. This method allows resolver implementation to tune the * resolving logic when a certain annotation is provided * * By default return `this` resolver instance * * @param beanSpec the bean spec with annotation information * @return the amended copy of this resolver */ public StringValueResolver<T> amended(AnnotationAware beanSpec) { return this; } public static <T> StringValueResolver<T> wrap(final $.Function<String, T> func, final Class<T> targetType) { if (func instanceof StringValueResolver) { return (StringValueResolver) func; } else { return new StringValueResolver<T>(targetType) { @Override public T resolve(String value) { return func.apply(value); } }; } } // For primary types private static final StringValueResolver<Boolean> _boolean = new StringValueResolver<Boolean>() { @Override public Boolean resolve(String value) { if (S.empty(value)) { return Boolean.FALSE; } return Boolean.parseBoolean(value); } }; private static final StringValueResolver<Boolean> _Boolean = new StringValueResolver<Boolean>() { @Override public Boolean resolve(String value) { if (S.empty(value)) { return null; } return Boolean.parseBoolean(value); } }; private static final Map<String, Character> PREDEFINED_CHARS = new HashMap<String, Character>(); static { PREDEFINED_CHARS.put("\\b", '\b'); PREDEFINED_CHARS.put("\\f", '\f'); PREDEFINED_CHARS.put("\\n", '\n'); PREDEFINED_CHARS.put("\\r", '\r'); PREDEFINED_CHARS.put("\\t", '\t'); PREDEFINED_CHARS.put("\\", '\"'); PREDEFINED_CHARS.put("\\'", '\''); PREDEFINED_CHARS.put("\\\\", '\\'); } /** * Parsing String into char. The rules are: * * 1. if there value is null or empty length String then return `defval` specified * 2. if the length of the String is `1`, then return that one char in the string * 3. if the value not starts with '\', then throw `IllegalArgumentException` * 4. if the value starts with `\\u` then parse the integer using `16` radix. The check * the range, if it fall into Character range, then return that number, otherwise raise * `IllegalArgumentException` * 5. if the value length is 2 then check if it one of {@link #PREDEFINED_CHARS}, if found * then return * 6. check if it valid OctalEscape defined in the <a href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6">spec</a> * if pass the check then return that char * 7. all other cases throw `IllegalArgumentException` * * @param value the string value to be resolved * @param defVal the default value when string value is `null` or empty * @return the char resolved from the string */ private static Character resolveChar(String value, Character defVal) { if (null == value) { return defVal; } switch (value.length()) { case 0: return defVal; case 1: return value.charAt(0); default: if (value.startsWith("\\")) { if (value.length() == 2) { Character c = PREDEFINED_CHARS.get(value); if (null != c) { return c; } } try { String s = value.substring(1); if (s.startsWith("u")) { int i = Integer.parseInt(s.substring(1), 16); if (i > Character.MAX_VALUE || i < Character.MIN_VALUE) { throw new IllegalArgumentException("Invalid character: " + value); } return (char) i; } else if (s.length() > 3) { throw new IllegalArgumentException("Invalid character: " + value); } else { if (s.length() == 3) { int i = Integer.parseInt(s.substring(0, 1)); if (i > 3) { throw new IllegalArgumentException("Invalid character: " + value); } } int i = Integer.parseInt(s, 8); return (char) i; } } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid character: " + value); } } else { throw new IllegalArgumentException("Invalid character: " + value); } } } /** * Returns the char resolver based on {@link #resolveChar(String, Character)} with `\0` as * default value */ private static final StringValueResolver<Character> _char = new StringValueResolver<Character>() { @Override public Character resolve(String value) { return resolveChar(value, '\0'); } }; /** * Returns the char resolver based on {@link #resolveChar(String, Character)} with `null` as * default value */ private static final StringValueResolver<Character> _Char = new StringValueResolver<Character>() { @Override public Character resolve(String value) { return resolveChar(value, null); } }; private static final StringValueResolver<Byte> _byte = new StringValueResolver<Byte>() { @Override public Byte resolve(String value) { if (S.blank(value)) { return (byte) 0; } return Byte.parseByte(value); } }; private static final StringValueResolver<Byte> _Byte = new StringValueResolver<Byte>() { @Override public Byte resolve(String value) { if (S.blank(value)) { return null; } return Byte.parseByte(value); } }; private static final StringValueResolver<Short> _short = new StringValueResolver<Short>() { @Override public Short resolve(String value) { if (S.blank(value)) { return (short) 0; } return Short.valueOf(value); } }; private static final StringValueResolver<Short> _Short = new StringValueResolver<Short>() { @Override public Short resolve(String value) { if (S.blank(value)) { return null; } return Short.valueOf(value); } }; private static int _int(String s) { if (s.contains(".")) { float f = Float.valueOf(s); return Math.round(f); } else { return Integer.valueOf(s); } } private static final StringValueResolver<Integer> _int = new StringValueResolver<Integer>() { @Override public Integer resolve(String value) { if (S.blank(value)) { return 0; } return _int(value); } }; private static final StringValueResolver<Integer> _Integer = new StringValueResolver<Integer>() { @Override public Integer resolve(String value) { if (S.blank(value)) { return null; } return _int(value); } }; private static final StringValueResolver<Long> _long = new StringValueResolver<Long>() { @Override public Long resolve(String value) { if (S.blank(value)) { return 0l; } return Long.valueOf(value); } }; private static final StringValueResolver<Long> _Long = new StringValueResolver<Long>() { @Override public Long resolve(String value) { if (S.blank(value)) { return null; } return Long.valueOf(value); } }; private static final StringValueResolver<Float> _float = new StringValueResolver<Float>() { @Override public Float resolve(String value) { if (S.blank(value)) { return 0f; } float n = Float.valueOf(value); if (Float.isInfinite(n) || Float.isNaN(n)) { throw new IllegalArgumentException("float value out of scope: " + value); } return n; } }; private static final StringValueResolver<Float> _Float = new StringValueResolver<Float>() { @Override public Float resolve(String value) { if (S.blank(value)) { return null; } float n = Float.valueOf(value); if (Float.isInfinite(n) || Float.isNaN(n)) { throw new IllegalArgumentException("float value out of scope: " + value); } return n; } }; private static final StringValueResolver<Double> _double = new StringValueResolver<Double>() { @Override public Double resolve(String value) { if (S.blank(value)) { return 0d; } double n = Double.valueOf(value); if (Double.isInfinite(n) || Double.isNaN(n)) { throw new IllegalArgumentException("double value out of scope: " + value); } return n; } }; private static final StringValueResolver<Double> _Double = new StringValueResolver<Double>() { @Override public Double resolve(String value) { if (S.blank(value)) { return null; } double n = Double.valueOf(value); if (Double.isInfinite(n) || Double.isNaN(n)) { throw new IllegalArgumentException("double value out of scope: " + value); } return n; } }; private static final StringValueResolver<String> _String = wrap($.F.<String>identity(), String.class); private static final StringValueResolver<Str> _Str = new StringValueResolver<Str>() { @Override public Str resolve(String value) { if (null == value) { return null; } return S.str(value); } }; private static final StringValueResolver<FastStr> _FastStr = new StringValueResolver<FastStr>() { @Override public FastStr resolve(String value) { if (null == value) { return null; } return FastStr.of(value); } }; private static Map<Class, StringValueResolver> predefined = C.newMap( boolean.class, _boolean, Boolean.class, _Boolean, char.class, _char, Character.class, _Char, byte.class, _byte, Byte.class, _Byte, short.class, _short, Short.class, _Short, int.class, _int, Integer.class, _Integer, long.class, _long, Long.class, _Long, float.class, _float, Float.class, _Float, double.class, _double, Double.class, _Double, String.class, _String, Str.class, _Str, FastStr.class, _FastStr, BigInteger.class, new BigIntegerValueObjectCodec(), BigDecimal.class, new BigDecimalValueObjectCodec(), Keyword.class, new KeywordValueObjectCodec() ); public static <T> void addPredefinedResolver(Class<T> type, StringValueResolver<T> resolver) { predefined.put(type, resolver); } public static Map<Class, StringValueResolver> predefined() { return C.map(predefined); } @SuppressWarnings("unchecked") public static <T> StringValueResolver<T> predefined(Class<T> type) { return predefined.get(type); } public static void main(String[] args) { System.out.println(_float.targetType()); System.out.println(_String.targetType()); } }