hu.javaforum.util.internal.ReflectionHelper.java Source code

Java tutorial

Introduction

Here is the source code for hu.javaforum.util.internal.ReflectionHelper.java

Source

/**
 * CC-LGPL 2.1
 * http://creativecommons.org/licenses/LGPL/2.1/
 */
package hu.javaforum.util.internal;

import hu.javaforum.logger.Logger;
import hu.javaforum.logger.PerfLogger;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.xml.bind.annotation.XmlElement;
import org.apache.commons.codec.binary.Base64;

/**
 * The ReflectionHelper is provides static methods to invoke getter and setter
 * methods in JavaBean instances.
 *
 * Changelog:
 * JFPORTAL-94 (2011-07-31)
 * ANDROIDSOAP-6 (2011-01-08)
 * ANDROIDSOAP-1 (2011-01-06)
 * JFPORTAL-94 (2010-02-24)
 * JFPORTAL-94 (2009-11-01)
 * JFPORTAL-79 (2009-10-11)
 * JFPORTAL-79 (2009-09-12)
 * JFPORTAL-78 (2009-09-03)
 * First implementation (2009-05-26)
 *
 * @author Auth Gbor <auth.gabor@javaforum.hu>
 */
public abstract class ReflectionHelper implements Serializable {

    /**
     * The static instance of the logger.
     */
    private static final Logger LOGGER = new Logger(ReflectionHelper.class);
    /**
     * It contains the regex patterns and the date formatting patterns.
     */
    private static final Map<String, String> DATE_FORMAT_PATTERNS;
    /**
     * Constant of "get".
     */
    protected static final String GET_WORD = "get";
    /**
     * It contains the primitive classes.
     */
    private static final Set<Class> PRIMITIVE_WRAPPER_CLASSES;
    /**
     * Constant of "set".
     */
    protected static final String SET_WORD = "set";
    /**
     * True, if the "javax.xml.bind.annotation.XmlElement" is on classpath.
     */
    protected static final Boolean XML_ELEMENT_LOADED;

    static {
        Map<String, String> patterns = new HashMap<String, String>();
        patterns.put(
                "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9][0-9][0-9].*",
                "yyyy-MM-dd'T'HH:mm:ss.SSSZ");
        patterns.put("[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]",
                "yyyy-MM-dd HH:mm:ss");
        patterns.put("[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]", "yyyy-MM-dd HH:mm");
        patterns.put("[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]", "yyyy-MM-dd");

        DATE_FORMAT_PATTERNS = Collections.unmodifiableMap(patterns);

        Set<Class> classes = new HashSet<Class>();
        classes.add(Boolean.class);
        classes.add(Byte.class);
        classes.add(Double.class);
        classes.add(Float.class);
        classes.add(Integer.class);
        classes.add(Long.class);
        classes.add(Short.class);

        PRIMITIVE_WRAPPER_CLASSES = Collections.unmodifiableSet(classes);

        /**
         * Some platform (like Android) isn't contains XmlElement annotation,
         * and the Reflection don't ignore them (like a normal JDK/JRE), but
         * throws ClassNotFoundException.
         */
        Boolean xmlElementLoaded = Boolean.TRUE;
        try {
            Class.forName("javax.xml.bind.annotation.XmlElement");
        } catch (ClassNotFoundException except) {
            xmlElementLoaded = Boolean.FALSE;
        }
        XML_ELEMENT_LOADED = xmlElementLoaded;
    }

    /**
     * Protected constructor, because all methods are static.
     */
    protected ReflectionHelper() {
        super();
    }

    /**
     * Gets the the field.
     *
     * @param objectClass The object
     * @param fieldName The field name of the object
     * @return The class
     * @exception NoSuchFieldException Throws, when the field isn't exists
     */
    public static Field getField(final Class objectClass, final String fieldName) throws NoSuchFieldException {
        Field field;
        if ("return".equals(fieldName)) {
            field = objectClass.getDeclaredField("_" + fieldName);
        } else {
            field = objectClass.getDeclaredField(fieldName);
        }

        return field;
    }

    /**
     * Returns with the name of the field.
     * Returns:
     * - with annotated name, if the Field has XmlElement annotation
     * - otherwise with the name of the field
     *
     * @param field The Field instance
     * @return Field name as char array
     */
    public static char[] getFieldName(final Field field) {
        if (field == null) {
            return new char[0];
        }

        if (XML_ELEMENT_LOADED) {
            final Annotation[] annotations = field.getAnnotations();
            for (Annotation annotation : annotations) {
                if (XmlElement.class.equals(annotation.annotationType())) {
                    if (!"##default".equals(((XmlElement) annotation).name())) {
                        return ((XmlElement) annotation).name().toCharArray();
                    }
                }
            }
        }

        return field.getName().toCharArray();
    }

    /**
     * Returns 'true', if the field is exists in the bean instance.
     *
     * @param instanceClass The bean class
     * @param instance The bean instance
     * @param fieldName The field name
     * @return True, if the field is exists
     */
    protected static Boolean isGetterExists(final Class instanceClass, final Object instance,
            final String fieldName) {
        return getGetterMethod(instanceClass, instance, fieldName) != null;
    }

    /**
     * Returns the getter method of the field. It is recursive method.
     *
     * @param instanceClass The bean class
     * @param instance The bean instance
     * @param fieldName The field name
     * @return The getter method, if it is exists
     * null, if the getter method is not exists
     */
    protected static Method getGetterMethod(final Class instanceClass, final Object instance,
            final String fieldName) {
        PerfLogger logger = new PerfLogger(LOGGER);

        if ("java.lang.Object".equals(instanceClass.getName())) {
            return null;
        }

        try {
            StringBuilder sb = new StringBuilder(fieldName.length() + GET_WORD.length());
            sb.append(GET_WORD);
            sb.append(fieldName);
            sb.setCharAt(GET_WORD.length(), Character.toUpperCase(sb.charAt(GET_WORD.length())));
            return instanceClass.getDeclaredMethod(sb.toString());
        } catch (NoSuchMethodException except) {
            logger.debug("Invoking %1$s.%2$s() because %3$s", instanceClass.getSuperclass().getName(), fieldName,
                    except.getMessage());
            return getGetterMethod(instanceClass.getSuperclass(), instance, fieldName);
        }
    }

    /**
     * Creates a parameter from the value. If the value instance of String then
     * this method calls the converter methods.
     *
     * @param fieldClass The class of field
     * @param value The value
     * @return The converted instance
     * @throws ClassNotFoundException If class not found
     * @throws ParseException If the pattern cannot be parseable
     * @throws UnsupportedEncodingException If the Base64 stream contains non UTF-8 chars
     */
    protected static Object createParameterFromValue(final Class fieldClass, final Object value)
            throws ClassNotFoundException, ParseException, UnsupportedEncodingException {
        Object parameter = value;

        if (!value.getClass().equals(fieldClass) && !PRIMITIVE_WRAPPER_CLASSES.contains(value.getClass())) {
            if (fieldClass.isArray() && value instanceof List) {
                String arrayElementTypeName = fieldClass.getName().substring(2, fieldClass.getName().length() - 1);
                Class arrayElementType = Class.forName(arrayElementTypeName);
                Object[] at = (Object[]) Array.newInstance(arrayElementType, 0);
                parameter = ((List<Object>) value).toArray(at);
            } else {
                String stringValue = createStringFromValue(value);
                if (stringValue != null) {
                    parameter = convertToPrimitive(fieldClass, stringValue);
                    parameter = parameter == null ? convertToPrimitiveWrapper(fieldClass, stringValue) : parameter;
                    parameter = parameter == null ? convertToBigNumbers(fieldClass, stringValue) : parameter;
                    parameter = parameter == null ? convertToOthers(fieldClass, stringValue) : parameter;
                }
            }
        }

        return parameter;
    }

    /**
     * Returns a value, if the class of the value is String or String[].
     *
     * @param value The value
     * @return The text value
     */
    private static String createStringFromValue(final Object value) {
        PerfLogger logger = new PerfLogger(LOGGER);

        String stringValue = null;
        if (value instanceof String) {
            stringValue = (String) value;
        } else if (value instanceof String[]) {
            String[] stringArrayValue = (String[]) value;
            if (stringArrayValue.length == 0) {
                logger.warn("Not found element in the String array...");
                return null;
            }
            if (stringArrayValue.length > 1) {
                logger.warn("More than one element found in the String array...");
                return null;
            }

            stringValue = stringArrayValue[0];
        }

        return stringValue;
    }

    /**
     * Converts the string value to instance of primitive wrapper.
     *
     * @param fieldClass The field class
     * @param stringValue The string value
     * @return The instance of the field class
     * @throws ParseException Throws when the string isn't parseable
     */
    private static Object convertToPrimitive(final Class fieldClass, final String stringValue)
            throws ParseException {
        Object parameter = null;

        if (fieldClass.equals(boolean.class)) {
            parameter = Boolean.parseBoolean(stringValue);
        } else if (fieldClass.equals(byte.class)) {
            parameter = Byte.parseByte(stringValue);
        } else if (fieldClass.equals(double.class)) {
            parameter = Double.parseDouble(stringValue);
        } else if (fieldClass.equals(float.class)) {
            parameter = Float.parseFloat(stringValue);
        } else if (fieldClass.equals(int.class)) {
            parameter = Integer.parseInt(stringValue);
        } else if (fieldClass.equals(long.class)) {
            parameter = Long.parseLong(stringValue);
        } else if (fieldClass.equals(short.class)) {
            parameter = Short.parseShort(stringValue);
        }

        return parameter;
    }

    /**
     * Converts the string value to instance of primitive wrapper.
     *
     * @param fieldClass The field class
     * @param stringValue The string value
     * @return The instance of the field class
     * @throws ParseException Throws when the string isn't parseable
     */
    private static Object convertToPrimitiveWrapper(final Class fieldClass, final String stringValue)
            throws ParseException {
        Object parameter = null;

        if (fieldClass.equals(Boolean.class)) {
            parameter = Boolean.parseBoolean(stringValue);
        } else if (fieldClass.equals(Byte.class)) {
            parameter = Byte.parseByte(stringValue);
        } else if (fieldClass.equals(Double.class)) {
            parameter = Double.parseDouble(stringValue);
        } else if (fieldClass.equals(Float.class)) {
            parameter = Float.parseFloat(stringValue);
        } else if (fieldClass.equals(Integer.class)) {
            parameter = Integer.parseInt(stringValue);
        } else if (fieldClass.equals(Long.class)) {
            parameter = Long.parseLong(stringValue);
        } else if (fieldClass.equals(Short.class)) {
            parameter = Short.parseShort(stringValue);
        }

        return parameter;
    }

    /**
     * Converts the string value to instance of BigDecimal or BigInteger.
     *
     * @param fieldClass The field class
     * @param stringValue The string value
     * @return The instance of the field class
     * @throws ParseException Throws when the string isn't parseable
     */
    private static Object convertToBigNumbers(final Class fieldClass, final String stringValue)
            throws ParseException {
        Object parameter = null;

        if (fieldClass.equals(BigDecimal.class)) {
            parameter = new BigDecimal(stringValue);
        } else if (fieldClass.equals(BigInteger.class)) {
            parameter = new BigInteger(stringValue);
        }

        return parameter;
    }

    /**
     * Converts the string value to instance of the field class.
     *
     * @param fieldClass The field class
     * @param stringValue The string value
     * @return The instance of the field class
     * @throws ParseException Throws when the string isn't parseable
     * @throws UnsupportedEncodingException If the Base64 stream contains non UTF-8 chars
     */
    private static Object convertToOthers(final Class fieldClass, final String stringValue)
            throws ParseException, UnsupportedEncodingException {
        Object parameter = null;

        if (fieldClass.isEnum()) {
            parameter = Enum.valueOf((Class<Enum>) fieldClass, stringValue);
        } else if (fieldClass.equals(byte[].class)) {
            parameter = Base64.decodeBase64(stringValue.getBytes("UTF-8"));
        } else if (fieldClass.equals(Date.class)) {
            parameter = stringToDate(stringValue);
        } else if (fieldClass.equals(Calendar.class)) {
            Calendar cal = Calendar.getInstance();
            cal.setTime(stringToDate(stringValue));
            parameter = cal;
        }

        return parameter;
    }

    /**
     * Converts String to Date.
     *
     * @param stringValue The string value
     * @return The Date instance
     * @throws ParseException If the string is not parseable
     */
    private static Date stringToDate(final String stringValue) throws ParseException {
        PerfLogger logger = new PerfLogger(LOGGER);

        try {
            String pattern = "yyyy-MM-dd'T'HH:mm:ssZ";
            for (Map.Entry<String, String> entry : DATE_FORMAT_PATTERNS.entrySet()) {
                if (stringValue.matches(entry.getKey())) {
                    pattern = entry.getValue();
                    break;
                }
            }

            DateFormat format = new SimpleDateFormat(pattern, Locale.ENGLISH);
            return format.parse(stringValue);
        } catch (ParseException except) {
            logger.warn("Unsupported date format '%1$s'", stringValue);
            throw except;
        }
    }
}