All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lodsve.core.properties.env.PropertyConverter Maven / Gradle / Ivy

/*
 * Copyright (C) 2018  Sun.Hao
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */

package lodsve.core.properties.env;

import lodsve.core.utils.StringUtils;
import org.apache.commons.lang3.BooleanUtils;

import javax.mail.internet.InternetAddress;
import java.awt.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.*;

/**
 * A utility class to convert the configuration properties into any type.
 *
 * @author Emmanuel Bourg
 * @version $Id: PropertyConverter.java 1534376 2013-10-21 21:14:18Z henning $
 * @since 1.1
 */
final class PropertyConverter {
    /**
     * Constant for the list delimiter as char.
     */
    static final char LIST_ESC_CHAR = '\\';

    /**
     * Constant for the list delimiter escaping character as string.
     */
    static final String LIST_ESCAPE = String.valueOf(LIST_ESC_CHAR);

    /**
     * Constant for the prefix of hex numbers.
     */
    private static final String HEX_PREFIX = "0x";

    /**
     * Constant for the radix of hex numbers.
     */
    private static final int HEX_RADIX = 16;

    /**
     * Constant for the prefix of binary numbers.
     */
    private static final String BIN_PREFIX = "0b";

    /**
     * Constant for the radix of binary numbers.
     */
    private static final int BIN_RADIX = 2;

    /**
     * Constant for the argument classes of the Number constructor that takes a
     * String.
     */
    private static final Class[] CONSTR_ARGS = {String.class};

    /**
     * The fully qualified name of {@link InternetAddress}
     */
    private static final String INTERNET_ADDRESS_CLASSNAME = "javax.mail.internet.InternetAddress";

    /**
     * Private constructor prevents instances from being created.
     */
    private PropertyConverter() {
        // to prevent instantiation...
    }

    /**
     * Converts the specified value to the target class. If the class is a
     * primitive type (Integer.TYPE, Boolean.TYPE, etc) the value returned will
     * use the wrapper type (Integer.class, Boolean.class, etc).
     *
     * @param cls    the target class of the converted value
     * @param value  the value to convert
     * @param params optional parameters used for the conversion
     * @return the converted value
     * @throws ConversionException if the value is not compatible with the requested type
     * @since 1.5
     */
    static Object to(Class cls, Object value, Object[] params)
            throws ConversionException {
        if (cls.isInstance(value)) {
            return value;
        }

        if (Boolean.class.equals(cls) || Boolean.TYPE.equals(cls)) {
            return toBoolean(value);
        } else if (Character.class.equals(cls) || Character.TYPE.equals(cls)) {
            return toCharacter(value);
        } else if (Number.class.isAssignableFrom(cls) || cls.isPrimitive()) {
            if (Integer.class.equals(cls) || Integer.TYPE.equals(cls)) {
                return toInteger(value);
            } else if (Long.class.equals(cls) || Long.TYPE.equals(cls)) {
                return toLong(value);
            } else if (Byte.class.equals(cls) || Byte.TYPE.equals(cls)) {
                return toByte(value);
            } else if (Short.class.equals(cls) || Short.TYPE.equals(cls)) {
                return toShort(value);
            } else if (Float.class.equals(cls) || Float.TYPE.equals(cls)) {
                return toFloat(value);
            } else if (Double.class.equals(cls) || Double.TYPE.equals(cls)) {
                return toDouble(value);
            } else if (BigInteger.class.equals(cls)) {
                return toBigInteger(value);
            } else if (BigDecimal.class.equals(cls)) {
                return toBigDecimal(value);
            }
        } else if (Date.class.equals(cls)) {
            return toDate(value, (String) params[0]);
        } else if (Calendar.class.equals(cls)) {
            return toCalendar(value, (String) params[0]);
        } else if (URL.class.equals(cls)) {
            return toURL(value);
        } else if (Locale.class.equals(cls)) {
            return toLocale(value);
        } else if (isEnum(cls)) {
            return convertToEnum(cls, value);
        } else if (Color.class.equals(cls)) {
            return toColor(value);
        } else if (cls.getName().equals(INTERNET_ADDRESS_CLASSNAME)) {
            return toInternetAddress(value);
        } else if (InetAddress.class.isAssignableFrom(cls)) {
            return toInetAddress(value);
        }

        throw new ConversionException("The value '" + value + "' ("
                + value.getClass() + ")" + " can't be converted to a "
                + cls.getName() + " object");
    }

    /**
     * Convert the specified object into a Boolean. Internally the
     * {@code org.apache.commons.lang.BooleanUtils} class from the Commons Lang project is used
     * to perform this conversion. This class accepts some more tokens for the
     * boolean value of true, e.g. {@code yes} and {@code on}. Please
     * refer to the documentation of this class for more details.
     *
     * @param value the value to convert
     * @return the converted value
     * @throws ConversionException thrown if the value cannot be converted to a boolean
     */
    public static Boolean toBoolean(Object value) throws ConversionException {
        if (value instanceof Boolean) {
            return (Boolean) value;
        } else if (value instanceof String) {
            Boolean b = BooleanUtils.toBooleanObject((String) value);
            if (b == null) {
                throw new ConversionException("The value " + value
                        + " can't be converted to a Boolean object");
            }
            return b;
        } else {
            throw new ConversionException("The value " + value
                    + " can't be converted to a Boolean object");
        }
    }

    /**
     * Converts the specified value object to a {@code Character}. This method
     * converts the passed in object to a string. If the string has exactly one
     * character, this character is returned as result. Otherwise, conversion
     * fails.
     *
     * @param value the value to be converted
     * @return the resulting {@code Character} object
     * @throws ConversionException if the conversion is not possible
     */
    public static Character toCharacter(Object value)
            throws ConversionException {
        String strValue = String.valueOf(value);
        if (strValue.length() == 1) {
            return Character.valueOf(strValue.charAt(0));
        } else {
            throw new ConversionException(
                    String.format(
                            "The value '%s' cannot be converted to a Character object!",
                            strValue));
        }
    }

    /**
     * Convert the specified object into a Byte.
     *
     * @param value the value to convert
     * @return the converted value
     * @throws ConversionException thrown if the value cannot be converted to a byte
     */
    public static Byte toByte(Object value) throws ConversionException {
        Number n = toNumber(value, Byte.class);
        if (n instanceof Byte) {
            return (Byte) n;
        } else {
            return new Byte(n.byteValue());
        }
    }

    /**
     * Convert the specified object into a Short.
     *
     * @param value the value to convert
     * @return the converted value
     * @throws ConversionException thrown if the value cannot be converted to a short
     */
    public static Short toShort(Object value) throws ConversionException {
        Number n = toNumber(value, Short.class);
        if (n instanceof Short) {
            return (Short) n;
        } else {
            return new Short(n.shortValue());
        }
    }

    /**
     * Convert the specified object into an Integer.
     *
     * @param value the value to convert
     * @return the converted value
     * @throws ConversionException thrown if the value cannot be converted to an integer
     */
    public static Integer toInteger(Object value) throws ConversionException {
        Number n = toNumber(value, Integer.class);
        if (n instanceof Integer) {
            return (Integer) n;
        } else {
            return new Integer(n.intValue());
        }
    }

    /**
     * Convert the specified object into a Long.
     *
     * @param value the value to convert
     * @return the converted value
     * @throws ConversionException thrown if the value cannot be converted to a Long
     */
    public static Long toLong(Object value) throws ConversionException {
        Number n = toNumber(value, Long.class);
        if (n instanceof Long) {
            return (Long) n;
        } else {
            return new Long(n.longValue());
        }
    }

    /**
     * Convert the specified object into a Float.
     *
     * @param value the value to convert
     * @return the converted value
     * @throws ConversionException thrown if the value cannot be converted to a Float
     */
    public static Float toFloat(Object value) throws ConversionException {
        Number n = toNumber(value, Float.class);
        if (n instanceof Float) {
            return (Float) n;
        } else {
            return new Float(n.floatValue());
        }
    }

    /**
     * Convert the specified object into a Double.
     *
     * @param value the value to convert
     * @return the converted value
     * @throws ConversionException thrown if the value cannot be converted to a Double
     */
    public static Double toDouble(Object value) throws ConversionException {
        Number n = toNumber(value, Double.class);
        if (n instanceof Double) {
            return (Double) n;
        } else {
            return new Double(n.doubleValue());
        }
    }

    /**
     * Convert the specified object into a BigInteger.
     *
     * @param value the value to convert
     * @return the converted value
     * @throws ConversionException thrown if the value cannot be converted to a BigInteger
     */
    public static BigInteger toBigInteger(Object value)
            throws ConversionException {
        Number n = toNumber(value, BigInteger.class);
        if (n instanceof BigInteger) {
            return (BigInteger) n;
        } else {
            return BigInteger.valueOf(n.longValue());
        }
    }

    /**
     * Convert the specified object into a BigDecimal.
     *
     * @param value the value to convert
     * @return the converted value
     * @throws ConversionException thrown if the value cannot be converted to a BigDecimal
     */
    public static BigDecimal toBigDecimal(Object value)
            throws ConversionException {
        Number n = toNumber(value, BigDecimal.class);
        if (n instanceof BigDecimal) {
            return (BigDecimal) n;
        } else {
            return new BigDecimal(n.doubleValue());
        }
    }

    /**
     * Tries to convert the specified object into a number object. This method
     * is used by the conversion methods for number types. Note that the return
     * value is not in always of the specified target class, but only if a new
     * object has to be created.
     *
     * @param value       the value to be converted (must not be null)
     * @param targetClass the target class of the conversion (must be derived from
     *                    {@code java.lang.Number})
     * @return the converted number
     * @throws ConversionException if the object cannot be converted
     */
    static Number toNumber(Object value, Class targetClass)
            throws ConversionException {
        if (value instanceof Number) {
            return (Number) value;
        } else {
            String str = value.toString();
            if (str.startsWith(HEX_PREFIX)) {
                try {
                    return new BigInteger(str.substring(HEX_PREFIX.length()),
                            HEX_RADIX);
                } catch (NumberFormatException nex) {
                    throw new ConversionException("Could not convert " + str
                            + " to " + targetClass.getName()
                            + "! Invalid hex number.", nex);
                }
            }

            if (str.startsWith(BIN_PREFIX)) {
                try {
                    return new BigInteger(str.substring(BIN_PREFIX.length()),
                            BIN_RADIX);
                } catch (NumberFormatException nex) {
                    throw new ConversionException("Could not convert " + str
                            + " to " + targetClass.getName()
                            + "! Invalid binary number.", nex);
                }
            }

            try {
                Constructor constr = targetClass.getConstructor(CONSTR_ARGS);
                return (Number) constr.newInstance(new Object[]{str});
            } catch (InvocationTargetException itex) {
                throw new ConversionException("Could not convert " + str
                        + " to " + targetClass.getName(),
                        itex.getTargetException());
            } catch (Exception ex) {
                // Treat all possible exceptions the same way
                throw new ConversionException(
                        "Conversion error when trying to convert " + str
                                + " to " + targetClass.getName(), ex);
            }
        }
    }

    /**
     * Convert the specified object into an URL.
     *
     * @param value the value to convert
     * @return the converted value
     * @throws ConversionException thrown if the value cannot be converted to an URL
     */
    public static URL toURL(Object value) throws ConversionException {
        if (value instanceof URL) {
            return (URL) value;
        } else if (value instanceof String) {
            try {
                return new URL((String) value);
            } catch (MalformedURLException e) {
                throw new ConversionException("The value " + value
                        + " can't be converted to an URL", e);
            }
        } else {
            throw new ConversionException("The value " + value
                    + " can't be converted to an URL");
        }
    }

    /**
     * Convert the specified object into a Locale.
     *
     * @param value the value to convert
     * @return the converted value
     * @throws ConversionException thrown if the value cannot be converted to a Locale
     */
    public static Locale toLocale(Object value) throws ConversionException {
        if (value instanceof Locale) {
            return (Locale) value;
        } else if (value instanceof String) {
            List elements = split((String) value, '_');
            int size = elements.size();

            boolean ok = size >= 1 && ((elements.get(0)).length() == 2 || (elements.get(0)).length() == 0);
            if (ok) {
                String language = elements.get(0);
                String country = (size >= 2) ? elements.get(1) : "";
                String variant = (size >= 3) ? elements.get(2) : "";

                return new Locale(language, country, variant);
            } else {
                throw new ConversionException("The value " + value
                        + " can't be converted to a Locale");
            }
        } else {
            throw new ConversionException("The value " + value
                    + " can't be converted to a Locale");
        }
    }

    /**
     * Split a string on the specified delimiter. To be removed when
     * commons-lang has a better replacement available (Tokenizer?).
     * 

* todo: replace with a commons-lang equivalent * * @param s the string to split * @param delimiter the delimiter * @param trim a flag whether the single elements should be trimmed * @return a list with the single tokens */ public static List split(String s, char delimiter, boolean trim) { if (s == null) { return new ArrayList(); } List list = new ArrayList(); StringBuilder token = new StringBuilder(); int begin = 0; boolean inEscape = false; while (begin < s.length()) { char c = s.charAt(begin); if (inEscape) { // last character was the escape marker // can current character be escaped? if (c != delimiter && c != LIST_ESC_CHAR) { // no, also add escape character token.append(LIST_ESC_CHAR); } token.append(c); inEscape = false; } else { if (c == delimiter) { // found a list delimiter -> add token and // resetDefaultFileSystem buffer String t = token.toString(); if (trim) { t = t.trim(); } list.add(t); token = new StringBuilder(); } else if (c == LIST_ESC_CHAR) { // eventually escape next character inEscape = true; } else { token.append(c); } } begin++; } // Trailing delimiter? if (inEscape) { token.append(LIST_ESC_CHAR); } // Add last token String t = token.toString(); if (trim) { t = t.trim(); } list.add(t); return list; } /** * Split a string on the specified delimiter always trimming the elements. * This is a shortcut for {@code split(s, delimiter, true)}. * * @param s the string to split * @param delimiter the delimiter * @return a list with the single tokens */ public static List split(String s, char delimiter) { return split(s, delimiter, true); } /** * Escapes the delimiters that might be contained in the given string. This * method works like {@link #escapeListDelimiter(String, char)}. In * addition, a single backslash will also be escaped. * * @param s the string with the value * @param delimiter the list delimiter to use * @return the correctly escaped string */ public static String escapeDelimiters(String s, char delimiter) { String s1 = StringUtils.replace(s, LIST_ESCAPE, LIST_ESCAPE + LIST_ESCAPE); return escapeListDelimiter(s1, delimiter); } /** * Escapes the list delimiter if it is contained in the given string. This * method ensures that list delimiter characters that are part of a * property's value are correctly escaped when a configuration is saved to a * file. Otherwise when loaded again the property will be treated as a list * property. * * @param s the string with the value * @param delimiter the list delimiter to use * @return the escaped string * @since 1.7 */ public static String escapeListDelimiter(String s, char delimiter) { return StringUtils.replace(s, String.valueOf(delimiter), LIST_ESCAPE + delimiter); } /** * Convert the specified object into a Color. If the value is a String, the * format allowed is (#)?[0-9A-F]{6}([0-9A-F]{2})?. Examples: *

    *
  • FF0000 (red)
  • *
  • 0000FFA0 (semi transparent blue)
  • *
  • #CCCCCC (gray)
  • *
  • #00FF00A0 (semi transparent green)
  • *
* * @param value the value to convert * @return the converted value * @throws ConversionException thrown if the value cannot be converted to a Color */ public static Color toColor(Object value) throws ConversionException { if (value instanceof Color) { return (Color) value; } else if (value instanceof String && !StringUtils.isBlank((String) value)) { String color = ((String) value).trim(); int[] components = new int[3]; // check the size of the string int minlength = components.length * 2; if (color.length() < minlength) { throw new ConversionException("The value " + value + " can't be converted to a Color"); } // remove the leading # if (color.startsWith("#")) { color = color.substring(1); } try { // parse the components for (int i = 0; i < components.length; i++) { components[i] = Integer.parseInt( color.substring(2 * i, 2 * i + 2), HEX_RADIX); } // parse the transparency int alpha; if (color.length() >= minlength + 2) { alpha = Integer.parseInt( color.substring(minlength, minlength + 2), HEX_RADIX); } else { alpha = Color.black.getAlpha(); } return new Color(components[0], components[1], components[2], alpha); } catch (Exception e) { throw new ConversionException("The value " + value + " can't be converted to a Color", e); } } else { throw new ConversionException("The value " + value + " can't be converted to a Color"); } } /** * Convert the specified value into an internet address. * * @param value the value to convert * @return the converted value * @throws ConversionException thrown if the value cannot be converted to a InetAddress * @since 1.5 */ static InetAddress toInetAddress(Object value) throws ConversionException { if (value instanceof InetAddress) { return (InetAddress) value; } else if (value instanceof String) { try { return InetAddress.getByName((String) value); } catch (UnknownHostException e) { throw new ConversionException("The value " + value + " can't be converted to a InetAddress", e); } } else { throw new ConversionException("The value " + value + " can't be converted to a InetAddress"); } } /** * Convert the specified value into an email address. * * @param value the value to convert * @return the converted value * @throws ConversionException thrown if the value cannot be converted to an email address * @since 1.5 */ static Object toInternetAddress(Object value) throws ConversionException { if (value.getClass().getName().equals(INTERNET_ADDRESS_CLASSNAME)) { return value; } else if (value instanceof String) { try { Constructor ctor = Class.forName(INTERNET_ADDRESS_CLASSNAME) .getConstructor(String.class); return ctor.newInstance(value); } catch (Exception e) { throw new ConversionException("The value " + value + " can't be converted to a InternetAddress", e); } } else { throw new ConversionException("The value " + value + " can't be converted to a InternetAddress"); } } /** * Calls Class.isEnum() on Java 5, returns false on older JRE. */ static boolean isEnum(Class cls) { return cls.isEnum(); } /** * Convert the specified value into a Java 5 enum. * * @param value the value to convert * @param cls the type of the enumeration * @return the converted value * @throws ConversionException thrown if the value cannot be converted to an enumeration * @since 1.5 */ static > E toEnum(Object value, Class cls) throws ConversionException { if (value.getClass().equals(cls)) { return cls.cast(value); } else if (value instanceof String) { try { return Enum.valueOf(cls, (String) value); } catch (Exception e) { throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName()); } } else if (value instanceof Number) { try { E[] enumConstants = cls.getEnumConstants(); return enumConstants[((Number) value).intValue()]; } catch (Exception e) { throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName()); } } else { throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName()); } } /** * Convert the specified object into a Date. * * @param value the value to convert * @param format the DateFormat pattern to parse String values * @return the converted value * @throws ConversionException thrown if the value cannot be converted to a Calendar */ public static Date toDate(Object value, String format) throws ConversionException { if (value instanceof Date) { return (Date) value; } else if (value instanceof Calendar) { return ((Calendar) value).getTime(); } else if (value instanceof String) { try { return new SimpleDateFormat(format).parse((String) value); } catch (ParseException e) { throw new ConversionException("The value " + value + " can't be converted to a Date", e); } } else { throw new ConversionException("The value " + value + " can't be converted to a Date"); } } /** * Convert the specified object into a Calendar. * * @param value the value to convert * @param format the DateFormat pattern to parse String values * @return the converted value * @throws ConversionException thrown if the value cannot be converted to a Calendar */ public static Calendar toCalendar(Object value, String format) throws ConversionException { if (value instanceof Calendar) { return (Calendar) value; } else if (value instanceof Date) { Calendar calendar = Calendar.getInstance(); calendar.setTime((Date) value); return calendar; } else if (value instanceof String) { try { Calendar calendar = Calendar.getInstance(); calendar.setTime(new SimpleDateFormat(format) .parse((String) value)); return calendar; } catch (ParseException e) { throw new ConversionException("The value " + value + " can't be converted to a Calendar", e); } } else { throw new ConversionException("The value " + value + " can't be converted to a Calendar"); } } /** * Returns an iterator over the simple values of a composite value. This * implementation calls {@link #flatten(Object, char)} and returns an * iterator over the returned collection. * * @param value the value to "split" * @param delimiter the delimiter for String values * @return an iterator for accessing the single values */ public static Iterator toIterator(Object value, char delimiter) { return flatten(value, delimiter).iterator(); } /** * Returns a collection with all values contained in the specified object. * This method is used for instance by the {@code addProperty()} * implementation of the default configurations to gather all values of the * property to add. Depending on the type of the passed in object the * following things happen: *
    *
  • Strings are checked for delimiter characters and split if necessary.
  • *
  • For objects implementing the {@code Iterable} interface, the * corresponding {@code Iterator} is obtained, and contained elements are * added to the resulting collection.
  • *
  • Arrays are treated as {@code Iterable} objects.
  • *
  • All other types are directly inserted.
  • *
  • Recursive combinations are supported, e.g. a collection containing an * array that contains strings: The resulting collection will only contain * primitive objects (hence the name "flatten").
  • *
* * @param value the value to be processed * @param delimiter the delimiter for String values * @return a "flat" collection containing all primitive values of * the passed in object */ private static Collection flatten(Object value, char delimiter) { if (value instanceof String) { String s = (String) value; if (s.indexOf(delimiter) > 0) { return split(s, delimiter); } } Collection result = new LinkedList(); if (value instanceof Iterable) { flattenIterator(result, ((Iterable) value).iterator(), delimiter); } else if (value instanceof Iterator) { flattenIterator(result, (Iterator) value, delimiter); } else if (value != null) { if (value.getClass().isArray()) { for (int len = Array.getLength(value), idx = 0; idx < len; idx++) { result.addAll(flatten(Array.get(value, idx), delimiter)); } } else { result.add(value); } } return result; } /** * Flattens the given iterator. For each element in the iteration * {@code flatten()} will be called recursively. * * @param target the target collection * @param it the iterator to process * @param delimiter the delimiter for String values */ private static void flattenIterator(Collection target, Iterator it, char delimiter) { while (it.hasNext()) { target.addAll(flatten(it.next(), delimiter)); } } /** * Helper method for converting a value to a constant of an enumeration * class. * conversion is safe because we know that the class is an Enum class * * @param enumClass the enumeration class * @param value the value to be converted * @return the converted value */ @SuppressWarnings("unchecked") private static Object convertToEnum(Class enumClass, Object value) { return toEnum(value, enumClass.asSubclass(Enum.class)); } }