org.nodel.reflection.Serialisation.java Source code

Java tutorial

Introduction

Here is the source code for org.nodel.reflection.Serialisation.java

Source

package org.nodel.reflection;

/* 
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 
 */

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.nodel.Base64;
import org.nodel.Strings;
import org.nodel.UUIDs;
import org.nodel.json.JSONArray;
import org.nodel.json.JSONException;
import org.nodel.json.JSONObject;
import org.nodel.json.JSONString;
import org.nodel.reflection.Reflection.AllowedInstanceInfo;

/**
 * Very flexible serialisation and deserialisation / coercion.
 */
public class Serialisation {

    /**
     * Convenience method: coerces from a JSON.
     */
    public static Object coerceFromJSON(Object dstObjOrClass, CharSequence json) {
        return coerceFromJSON(dstObjOrClass, json, null, null, false);
    }

    /**
     * Coerces a JSON string into a destination class (providing generics hints and other serialisation rules).
     */
    public static Object coerceFromJSON(Object dstObjOrClass, CharSequence json, Class<?> genericClassA,
            Class<?> genericClassB) {
        return coerceFromJSON(dstObjOrClass, json, genericClassA, genericClassB, false);
    }

    /**
     * Coerces a JSON string into a destination class (providing generics hints and other serialisation rules).
     */
    public static Object coerceFromJSON(Object dstObjOrClass, CharSequence json, Class<?> genericClassA,
            Class<?> genericClassB, boolean treatEmptyStringsAsNull) {
        try {
            // unfortunately a String instead of a CharSequence has to be used for this JSON-library
            JSONObject jsonObject = new JSONObject(json.toString());

            return coerce(dstObjOrClass, jsonObject, null, genericClassA, genericClassB, treatEmptyStringsAsNull);
        } catch (JSONException exc) {
            throw new SerialisationException("JSON not formatted correctly.", exc);
        }
    }

    /**
     * Coerces an object (native or from 'json.org') package into a destination class.
     */
    public static Object coerce(Object dstObjOrClass, Object srcObj) {
        return coerce(dstObjOrClass, srcObj, null, null, null, false);
    }

    /**
     * Coerces an object into a destination class (providing generics hints).
     */
    public static Object coerce(Object dstObjOrClass, Object srcObj, Class<?> genericClassA,
            Class<?> genericClassB) {
        return coerce(dstObjOrClass, srcObj, null, genericClassA, genericClassB, false);
    }

    /**
     * Coerces an object into a destination class (providing generics hints and other serialisation rules).
     */
    public static Object coerce(Object dstObjOrClass, Object srcObj, Class<?> genericClassA, Class<?> genericClassB,
            boolean treatEmptyStringsAsNull) {
        return coerce(dstObjOrClass, srcObj, null, genericClassA, genericClassB, treatEmptyStringsAsNull);
    }

    /**
     * @param valueInfo used if generics are involved.
     */
    private static Object coerce(Object dstObjOrClass, Object srcObj, ValueInfo valueInfo, Class<?> genericClassA,
            Class<?> genericClassB, boolean treatEmptyStringsAsNull) {
        // The order that the following tests are performed relates to the likelihood 
        // of each test being required, hopefully leading to faster 
        // typical runtime operation; readability could otherwise be improved.

        Class<?> klass;

        if (dstObjOrClass instanceof Class<?>) {
            klass = (Class<?>) dstObjOrClass;
        } else {
            klass = (dstObjOrClass != null ? dstObjOrClass.getClass() : null);
        }

        if (klass == null || klass.equals(Object.class)) {
            // we have no idea what specific class so just return the object itself
            // but normalise to standard maps and lists if needed (instead of 
            // JSONArrays of JSONObjects)
            if (srcObj instanceof JSONObject) {
                return coerce(Map.class, srcObj, null, null, treatEmptyStringsAsNull);

            } else if (srcObj instanceof JSONArray) {
                return coerce(List.class, srcObj, null, null, treatEmptyStringsAsNull);

            } else if (srcObj == JSONObject.NULL) {
                return null;

            } else {
                // return the objects as they are

                // (treat empty strings as nulls?)
                if (treatEmptyStringsAsNull && srcObj instanceof String && ((String) srcObj).length() == 0)
                    return null;
                else
                    return srcObj;
            }
        }

        // test string at the top because it's most commonly used

        if (klass == String.class) {
            if (srcObj == null)
                return null;
            else if (srcObj instanceof String) {
                if (treatEmptyStringsAsNull && ((String) srcObj).length() == 0)
                    return null;
                else
                    return srcObj;
            } else if (srcObj instanceof JSONObject || srcObj instanceof JSONArray)
                throw new SerialisationException("Could not coerce into a string.");
            else if (srcObj.equals(JSONObject.NULL))
                return null;
            else
                // the rest should be value types (boolean, int, float, etc.)
                return srcObj.toString();
        }

        // test all the primitive types first
        else if (klass == Integer.class || klass == int.class) {
            if (srcObj == null)
                return 0;
            else if (srcObj instanceof Number)
                return ((Number) srcObj).intValue();
            else if (srcObj instanceof String)
                return Integer.parseInt((String) srcObj);
            else if (srcObj.equals(JSONObject.NULL))
                return null;
            else
                return (Integer) srcObj;
        }

        else if (klass == Long.class || klass == long.class) {
            if (srcObj == null)
                return 0;
            else if (srcObj instanceof Number)
                return ((Number) srcObj).longValue();
            else if (srcObj instanceof String)
                return Long.parseLong((String) srcObj);
            else if (srcObj.equals(JSONObject.NULL))
                return null;
            else
                return (Long) srcObj;
        }

        else if (klass == Double.class || klass == double.class) {
            if (srcObj == null)
                return 0;
            else if (srcObj instanceof Number)
                return ((Number) srcObj).doubleValue();
            else if (srcObj instanceof String)
                return Double.parseDouble((String) srcObj);
            else if (srcObj.equals(JSONObject.NULL))
                return null;
            else
                return (Double) srcObj;
        }

        else if (klass == Float.class || klass == float.class) {
            if (srcObj == null)
                return 0;
            else if (srcObj instanceof Number)
                return ((Number) srcObj).floatValue();
            else if (srcObj instanceof String)
                return Float.parseFloat((String) srcObj);
            else if (srcObj.equals(JSONObject.NULL))
                return null;
            else
                return (Float) srcObj;
        }

        else if (klass == Byte.class || klass == byte.class) {
            if (srcObj == null)
                return 0;
            else if (srcObj instanceof Number)
                return ((Number) srcObj).byteValue();
            else if (srcObj instanceof String)
                return Byte.parseByte((String) srcObj);
            else if (srcObj.equals(JSONObject.NULL))
                return null;
            else
                return (Byte) srcObj;
        }

        else if (klass == Boolean.class || klass == boolean.class) {
            if (srcObj == null)
                return false;
            else if (srcObj instanceof String)
                return Boolean.parseBoolean((String) srcObj);
            else if (srcObj.equals(JSONObject.NULL))
                return null;
            else
                return (Boolean) srcObj;

        } else if (klass == Byte[].class || klass == byte[].class) {
            if (srcObj == null)
                return null;
            else if (srcObj instanceof String)
                return Base64.decode((String) srcObj);
            else if (srcObj.equals(JSONObject.NULL))
                return null;
            else
                return (Byte[]) srcObj;
        }

        // Test non-primitives now

        else if (srcObj == null) {
            // will be null regardless
            return null;
        }

        // test object types now

        else if (srcObj.equals(JSONObject.NULL)) {
            return null;
        }

        else if (klass == DateTime.class) {
            return tryParseDate(srcObj);
        }

        else if (klass == Date.class) {
            try {
                return DateFormat.getDateInstance().parse(srcObj.toString());
            } catch (ParseException e) {
                throw new SerialisationException(e);
            }
        }

        else if (klass == UUID.class) {
            return UUIDs.fromString(srcObj.toString());
        }

        else if (klass.isEnum()) {
            return Reflection.getEnumConstantInfo(klass, srcObj.toString()).constant;
        }

        // pure arrays
        else if (klass.isArray()) {
            return coerceIntoArray(dstObjOrClass, srcObj, treatEmptyStringsAsNull);
        }

        // collections (*)
        else if (Collection.class.isAssignableFrom(klass)) {
            if (valueInfo == null)
                return coerceIntoCollection(dstObjOrClass, srcObj, genericClassA, treatEmptyStringsAsNull);
            else
                return coerceIntoCollection(dstObjOrClass, srcObj, valueInfo.genericClassA,
                        treatEmptyStringsAsNull);
        }

        // maps (*)
        else if (Map.class.isAssignableFrom(klass)) {
            if (valueInfo == null)
                return coerceIntoMap(dstObjOrClass, srcObj, genericClassA, genericClassB, treatEmptyStringsAsNull);
            else
                return coerceIntoMap(dstObjOrClass, srcObj, valueInfo.genericClassA, valueInfo.genericClassB,
                        treatEmptyStringsAsNull);
        }

        // plain object
        else {
            return coerceIntoPlainObject(dstObjOrClass, srcObj, treatEmptyStringsAsNull);
        }

    } // (method)

    /**
      * All other objects
      * 
      * (prechecked args)
      */
    private static Object coerceIntoPlainObject(Object dstObjOrClass, Object srcObj,
            boolean treatEmptyStringsAsNull) {
        Class<?> klass;
        Object dstObj;

        if (dstObjOrClass instanceof Class<?>) {
            klass = (Class<?>) dstObjOrClass;
            dstObj = null;
        } else {
            klass = (dstObjOrClass != null ? dstObjOrClass.getClass() : null);
            dstObj = dstObjOrClass;
        }

        if (srcObj instanceof String) {
            // try create an instance of it using String arg constructor or other standard methods
            return Reflection.createInstanceFromString(klass, (String) srcObj);
        }

        // common currency
        JSONObject jsonObject;

        if (srcObj instanceof JSONObject) {
            jsonObject = (JSONObject) srcObj;
        } else {
            Object wrapped = wrap(srcObj);

            if (wrapped instanceof JSONObject)
                jsonObject = (JSONObject) wrapped;
            else
                throw new SerialisationException(
                        "The provided object could not be wrapped into a usable form (special object).");
        }

        // if it's just a general 'Object', no point in doing anything else but using the 'wrapped' form
        if (klass == Object.class) {
            return srcObj;
        }

        // check whether to choose one of allowable instances
        AllowedInstanceInfo[] allowedInstanceInfos = Reflection.getAllowedInstances(klass);
        if (allowedInstanceInfos != null && allowedInstanceInfos.length > 0) {
            Class<?> selectedClass = null;

            // go through the allowed instances, rejecting the ones that don't contain matching fields
            for (AllowedInstanceInfo allowedInstance : allowedInstanceInfos) {
                selectedClass = allowedInstance.clazz;

                Map<String, ValueInfo> valueMap = Reflection.getValueInfoMap(selectedClass);

                // go through all the present fields
                Iterator<String> fields = jsonObject.keys();
                while (fields.hasNext()) {
                    String field = fields.next();

                    if (!valueMap.containsKey(field.toLowerCase())) {
                        selectedClass = null;
                        break;
                    }
                }

                // keep going until we have a selected class
                if (selectedClass == null)
                    continue;
                else
                    break;
            } // (for)

            // if we still don't have a selected class, just use the first one
            if (selectedClass == null)
                klass = allowedInstanceInfos[0].clazz;
            else
                klass = selectedClass;
        }

        // create a new instance of the object if required
        Object object;

        // create a new instance or ...
        if (dstObj == null) {
            try {
                object = klass.newInstance();
            } catch (Exception exc) {
                throw new SerialisationException(
                        "Could not create instance of requested type plain object, " + klass.getName(), exc);
            }
        } else {
            // ... use provided object
            object = dstObj;
        }

        // go through each listed field info and deserialise
        Iterator<String> keys = jsonObject.keys();
        while (keys.hasNext()) {
            String key = keys.next();

            ValueInfo fieldInfo = Reflection.getValueInfosByName(klass, key);
            if (fieldInfo == null || !(fieldInfo.member instanceof Field))
                // only 0 argument methods are valid as fields 
                continue;

            Field field = (Field) fieldInfo.member;

            Class<?> valueClass = field.getType();

            Object jsonValue;
            try {
                jsonValue = jsonObject.get(key);
            } catch (JSONException exc) {
                throw new SerialisationException("Could not get entry '" + key + "' out of the object.");
            }

            // choose when set the field or use a designated 'setter'
            SetterInfo setterInfo = fieldInfo.setter;
            if (setterInfo == null) {
                Object objValue = coerce(valueClass, jsonValue, fieldInfo, null, null, treatEmptyStringsAsNull);

                try {
                    // set field directly
                    field.setAccessible(true);

                    if (objValue != null) {
                        field.set(object, objValue);
                    }

                } catch (Exception e) {
                    throw new SerialisationException("Could not set field '" + field.getName() + "'.");
                }

            } else {
                // set indirectly using a 'setter' method
                Method setterMethod = setterInfo.method;

                Class<?>[] paramClasses = setterMethod.getParameterTypes();

                Object objValue = coerce(paramClasses[0], jsonValue, fieldInfo, null, null,
                        treatEmptyStringsAsNull);

                Exception exception = null;

                try {
                    if (objValue != null)
                        setterMethod.invoke(object, objValue);

                } catch (InvocationTargetException exc) {
                    Throwable actualException = exc.getTargetException();
                    throw new RuntimeException(actualException);

                } catch (IllegalAccessException e) {
                    exception = e;

                } catch (IllegalArgumentException e) {
                    exception = e;
                }

                if (exception != null)
                    throw new SerialisationException("Could not set field '" + field.getName() + "'.", exception);
            }
        } // (while)

        return object;
    }

    /**
     * (args all prechecked) 
     * @param treatEmptyStringsAsNull 
     */
    private static Object coerceIntoArray(Object dstObjOrClass, Object srcObj, boolean treatEmptyStringsAsNull) {
        Class<?> klass;
        Object dstObj;

        if (dstObjOrClass instanceof Class<?>) {
            klass = (Class<?>) dstObjOrClass;
            dstObj = null;
        } else {
            klass = (dstObjOrClass != null ? dstObjOrClass.getClass() : null);
            dstObj = dstObjOrClass;
        }

        JSONArray jsonArray;
        if (srcObj instanceof JSONArray) {
            jsonArray = (JSONArray) srcObj;
        } else {
            // not an array, convert it into one
            Object wrapped = wrap(srcObj);
            if (wrapped instanceof JSONArray)
                jsonArray = (JSONArray) wrapped;
            else {
                // convert whatever object it is into a single-element array
                jsonArray = new JSONArray();
                jsonArray.put(wrapped);
            }
        }

        // get the class of the items in the array
        Class<?> componentType = klass.getComponentType();

        // get the length of the array
        int length = jsonArray.length();

        // create the array instance or use the provided object
        Object array;
        if (dstObj == null)
            array = Array.newInstance(componentType, length);
        else
            array = dstObj;

        for (int index = 0; index < length; index++) {
            Object jsonValue;
            try {
                jsonValue = jsonArray.get(index);
            } catch (JSONException e) {
                throw new SerialisationException("Could not retrieve an item out of the array.");
            }
            Object objValue = coerce(componentType, jsonValue, null, null, null, treatEmptyStringsAsNull);

            if (objValue != null)
                Array.set(array, index, objValue);
        } // (for)

        return array;
    } // (method)

    /**
     * (args all prechecked) 
     * @param treatEmptyStringsAsNull 
     */
    @SuppressWarnings("unchecked")
    private static Object coerceIntoCollection(Object dstObjOrClass, Object srcObj, Class<?> componentType,
            boolean treatEmptyStringsAsNull) {
        Class<?> klass;
        Object dstObj;

        if (dstObjOrClass instanceof Class<?>) {
            klass = (Class<?>) dstObjOrClass;
            dstObj = null;
        } else {
            klass = (dstObjOrClass != null ? dstObjOrClass.getClass() : null);
            dstObj = dstObjOrClass;
        }

        JSONArray jsonArray;
        if (srcObj instanceof JSONArray) {
            jsonArray = (JSONArray) srcObj;
        } else {
            // not an array, convert it into one
            Object wrapped = wrap(srcObj);
            if (wrapped instanceof JSONArray)
                jsonArray = (JSONArray) wrapped;
            else {
                // convert whatever object it is into a single-element array
                jsonArray = new JSONArray();
                jsonArray.put(wrapped);
            }
        }

        Collection<Object> instance;

        // create own instance or...
        if (dstObj == null) {
            if (klass.isInterface() || Modifier.isAbstract(klass.getModifiers())) {
                // cannot create instance of interface or abstract classes, so use
                // a well new 'Collection' class, ArrayList
                instance = new ArrayList<Object>();
            } else {
                try {
                    instance = (Collection<Object>) klass.newInstance();
                } catch (Exception exc) {
                    throw new SerialisationException("Could not create instance of requested type, Collection.",
                            exc);
                }
            }
        } else {
            // ...use provided one
            instance = (Collection<Object>) dstObj;
        }

        // get the length of the array
        int length = jsonArray.length();

        for (int index = 0; index < length; index++) {
            Object jsonValue;
            try {
                jsonValue = jsonArray.get(index);
            } catch (JSONException exc) {
                throw new SerialisationException("Could not get an item out of an array - index " + index, exc);
            }
            Object objValue = coerce(componentType, jsonValue, null, null, null, treatEmptyStringsAsNull);

            instance.add(objValue);
        } // (for)

        return instance;
    } // (method)

    /**
     * (args all prechecked) 
     * @param treatEmptyStringsAsNull 
     */
    @SuppressWarnings("unchecked")
    private static Object coerceIntoMap(Object dstObjOrClass, Object srcObj, Class<?> keyType, Class<?> valueType,
            boolean treatEmptyStringsAsNull) {
        Class<?> klass;
        Object dstObj;

        if (dstObjOrClass instanceof Class<?>) {
            klass = (Class<?>) dstObjOrClass;
            dstObj = null;
        } else {
            klass = (dstObjOrClass != null ? dstObjOrClass.getClass() : null);
            dstObj = dstObjOrClass;
        }

        JSONObject jsonMap;
        if (srcObj instanceof JSONObject) {
            jsonMap = (JSONObject) srcObj;
        } else {
            Object wrappedObject = wrap(srcObj);

            if (!(wrappedObject instanceof JSONObject))
                throw new SerialisationException(
                        "The provided object could not be wrapped into a usable form (special map).");

            jsonMap = (JSONObject) wrappedObject;
        }

        Map<Object, Object> instance;

        // create a fresh instance or...
        if (dstObj == null) {
            if (klass.isInterface() || Modifier.isAbstract(klass.getModifiers())) {
                // cannot create instance of interface or abstract classes, so use
                // a well new 'Map' class, LinkedHashMap which preservers order
                instance = new LinkedHashMap<Object, Object>();
            } else {
                // use the klass that was specified
                try {
                    instance = (Map<Object, Object>) klass.newInstance();
                } catch (Exception exc) {
                    throw new SerialisationException("Could not create an instance of a requested type, Map.", exc);
                }
            }
        } else {
            // ... use the provided object
            instance = (Map<Object, Object>) dstObj;
        }

        Iterator<String> keys = jsonMap.keys();
        while (keys.hasNext()) {
            String jsonKey = keys.next();

            Object objKey;
            if (keyType != String.class && keyType != Object.class) {
                objKey = coerce(keyType, jsonKey, null, null, null, treatEmptyStringsAsNull);
            } else {
                objKey = jsonKey;
            }

            Object jsonValue;
            try {
                jsonValue = jsonMap.get(jsonKey);

            } catch (JSONException exc) {
                throw new SerialisationException("Could not retrieve entry '" + jsonKey + "' out of map.");
            }
            Object objValue = coerce(valueType, jsonValue, null, null, null, treatEmptyStringsAsNull);

            instance.put(objKey, objValue);

        } // (while)

        return instance;
    } // (method)

    /**
     * (overloaded - no indentation specified)
     */
    public static String serialise(Object object) throws SerialisationException {
        return serialise(object, 0);
    }

    /**
     * Performs serialisation of an object.
     */
    public static String serialise(Object object, int indent) {
        return serialise(object, indent, false);
    }

    /**
     * (optionally exclude passwords)
     */
    public static String serialise(Object object, int indent, boolean excludePasswords) {
        try {
            Object wrappedObject = wrap(object, excludePasswords);

            if (wrappedObject instanceof JSONObject)
                return ((JSONObject) wrappedObject).toString(indent);

            else if (wrappedObject instanceof JSONArray)
                return ((JSONArray) wrappedObject).toString(indent);

            else
                return wrappedObject.toString();

        } catch (JSONException exc) {
            throw new SerialisationException(exc);
        }
    } // (method)

    /**
     * Deserialises an object from JSON text.
     */
    public static Object deserialise(Class<?> klass, String json) {
        return coerceFromJSON(klass, json);
    }

    /**
     * Wraps a object into a JSONObject-supported type.
     * 
     * NOTE: This is different from JSONObject.wrap(...) in that it
     *       handles specially "annotated" classes correctly instead of
     *       Java Bean objects. 
     */
    public static Object wrap(Object object) {
        return wrap(object, false);
    }

    /**
     * (optionally exclude passwords for annotated classes)
     */
    public static Object wrap(Object object, boolean excludePasswords) {
        try {
            if (object == null) {
                return JSONObject.NULL;
            }

            if (object instanceof JSONObject || object instanceof JSONArray || JSONObject.NULL.equals(object)
                    || object instanceof JSONString || object instanceof Number || object instanceof Character
                    || object instanceof Boolean || object instanceof String) {
                return object;
            }

            if (object instanceof byte[]) {
                return Base64.encode((byte[]) object);
            }

            if (object instanceof Collection) {
                JSONArray jsonArray = new JSONArray();

                Iterator<?> iter = ((Collection<?>) object).iterator();
                while (iter.hasNext())
                    jsonArray.put(wrap(iter.next(), excludePasswords));

                return jsonArray;
            }

            if (object.getClass().isArray()) {
                JSONArray jsonArray = new JSONArray();

                int length = Array.getLength(object);
                for (int i = 0; i < length; i++)
                    jsonArray.put(wrap(Array.get(object, i), excludePasswords));

                return jsonArray;
            }

            if (object instanceof Map) {
                JSONObject jsonObject = new JSONObject();

                Iterator<?> i = ((Map<?, ?>) object).entrySet().iterator();
                while (i.hasNext()) {
                    Map.Entry<?, ?> e = (Map.Entry<?, ?>) i.next();
                    Object value = e.getValue();
                    if (value != null) {
                        jsonObject.put(e.getKey().toString(), wrap(value, excludePasswords));
                    }
                }

                return jsonObject;
            }

            // revert to specially annotated classes
            Class<?> klass = object.getClass();

            if (klass.isEnum()) {
                return Reflection.getEnumConstantInfo(klass, object).title;
            }

            // get the fields if there are any
            ValueInfo[] fieldInfos = Reflection.getValueInfos(klass);
            if (fieldInfos.length > 0) {
                JSONObject jsonObject = new JSONObject();

                for (ValueInfo fieldInfo : fieldInfos) {
                    try {
                        String key = fieldInfo.name;
                        if (Strings.isNullOrEmpty(key))
                            key = fieldInfo.member.getName();

                        if (excludePasswords) {
                            // if directed to, skip the format if it's 'password'
                            String format = fieldInfo.format;
                            if ("password".equalsIgnoreCase(format))
                                continue;
                        }

                        Object result = null;

                        if (fieldInfo.member instanceof Field) {
                            Field field = (Field) fieldInfo.member;
                            field.setAccessible(true);

                            // invoke the getter
                            result = field.get(object);
                        } else {
                            // treat as method
                            Method method = (Method) fieldInfo.member;

                            result = method.invoke(object);
                        }

                        if (result != null) {
                            // jsonObject()
                            jsonObject.put(key, wrap(result, excludePasswords));
                        }
                    } catch (Exception ignore) {
                        System.out.println(ignore);
                        // ignore any reflection related issues
                    }
                } // (for)

                return jsonObject;
            }

            // no fields, so just return the .toString() method
            return object.toString();

        } catch (Exception exception) {
            return null;
        }
    }

    private static DateTimeFormatter _customFullFormatter = DateTimeFormat
            .forPattern("EEE MMM dd HH:mm:ss 'EST' yyyy");

    /**
     * Tries to parse a date
     */
    private static DateTime tryParseDate(Object jsonValue) {
        if (jsonValue == null)
            return null;

        String strValue = jsonValue.toString();
        DateTime result;

        result = tryParseISODateTime(strValue);
        if (result != null)
            return result;

        result = tryParseFullDateTime(strValue);
        if (result != null)
            return result;

        result = tryParseLongDateTime(strValue);
        if (result != null)
            return result;

        result = tryParseMediumDateTime(strValue);
        if (result != null)
            return result;

        result = tryParseShortDateTime(strValue);
        if (result != null)
            return result;

        result = tryParseCustomFullDateTime(strValue);
        if (result != null)
            return result;

        throw new SerialisationException("Could not parse into a common date format - '" + jsonValue + "'");
    }

    private static DateTime tryParseISODateTime(String value) {
        try {
            return DateTime.parse(value);
        } catch (Exception exc) {
            return null;
        }
    }

    private static DateTime tryParseFullDateTime(String value) {
        try {
            return DateTime.parse(value, DateTimeFormat.fullDateTime());
        } catch (Exception exc) {
            return null;
        }
    }

    private static DateTime tryParseLongDateTime(String value) {
        try {
            return DateTime.parse(value, DateTimeFormat.longDateTime());
        } catch (Exception exc) {
            return null;
        }
    }

    private static DateTime tryParseMediumDateTime(String value) {
        try {
            return DateTime.parse(value, DateTimeFormat.mediumDateTime());
        } catch (Exception exc) {
            return null;
        }
    }

    private static DateTime tryParseShortDateTime(String value) {
        try {
            return DateTime.parse(value, DateTimeFormat.shortDateTime());
        } catch (Exception exc) {
            return null;
        }
    }

    private static DateTime tryParseCustomFullDateTime(String value) {
        try {
            return DateTime.parse(value, _customFullFormatter);
        } catch (Exception exc) {
            return null;
        }
    }

} // (class)