src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/mapper/JSONMappingFactory.java
author hb
Wed, 27 Dec 2017 18:39:52 +0530
branchjmx-rest-api
changeset 55997 f881344569d9
parent 55994 9721e36abeb0
child 56002 60ab3b595a8e
permissions -rw-r--r--
Added copyright header

/*
 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.oracle.jmx.remote.rest.mapper;

import com.oracle.jmx.remote.rest.json.JSONArray;
import com.oracle.jmx.remote.rest.json.JSONElement;
import com.oracle.jmx.remote.rest.json.JSONObject;
import com.oracle.jmx.remote.rest.json.JSONPrimitive;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.openmbean.*;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author harsha
 */
public final class JSONMappingFactory {

    public static final JSONMappingFactory INSTANCE;
    private static final Map<Class<?>, JSONMapper> typeMapper;
    private static final Map<String, Class<?>> primitiveMap = new HashMap<>();

    static {
        // Make order of Initialization explicit
        typeMapper = new HashMap<>();

        primitiveMap.put("boolean", Boolean.TYPE);
        primitiveMap.put("int", Integer.TYPE);
        primitiveMap.put("long", Long.TYPE);
        primitiveMap.put("double", Double.TYPE);
        primitiveMap.put("float", Float.TYPE);
        primitiveMap.put("bool", Boolean.TYPE);
        primitiveMap.put("char", Character.TYPE);
        primitiveMap.put("byte", Byte.TYPE);
        primitiveMap.put("void", Void.TYPE);
        primitiveMap.put("short", Short.TYPE);

        INSTANCE = new JSONMappingFactory();
    }

    private JSONMappingFactory() {

        typeMapper.put(void.class, null);
        typeMapper.put(Void.class, null);

        typeMapper.put(boolean.class, new BooleanMapper());
        typeMapper.put(Boolean.class, new BooleanMapper());

        typeMapper.put(byte.class, new ByteMapper());
        typeMapper.put(Byte.class, new ByteMapper());

        typeMapper.put(short.class, new ShortMapper());
        typeMapper.put(Short.class, new ShortMapper());

        typeMapper.put(int.class, new IntegerMapper());
        typeMapper.put(Integer.class, new IntegerMapper());

        typeMapper.put(long.class, new LongMapper());
        typeMapper.put(Long.class, new LongMapper());

        typeMapper.put(float.class, new FloatMapper());
        typeMapper.put(Float.class, new FloatMapper());

        typeMapper.put(double.class, new DoubleMapper());
        typeMapper.put(Double.class, new DoubleMapper());

        typeMapper.put(char.class, new CharacterMapper());
        typeMapper.put(Character.class, new CharacterMapper());

        typeMapper.put(String.class, new StringMapper());
        typeMapper.put(BigInteger.class, new BigIntegerMapper());
        typeMapper.put(BigDecimal.class, new BigDecimalMapper());
        typeMapper.put(ObjectName.class, new ObjectNameMapper());
        typeMapper.put(Date.class, new DateMapper());
    }

    private Class<?> getArrayComponent(Class<?> cls) {
        if (cls == null) {
            return cls;
        }
        Class<?> compType = cls;
        // TODO: Add check for max array dimention of 15
        while (compType.isArray()) {
            compType = compType.getComponentType();
        }
        return compType;
    }

    private Object getArrayElement(Object arrayObj) {
        if (arrayObj != null && arrayObj.getClass().isArray()) {
            while (arrayObj.getClass().isArray()) {
                Class<?> componentType = arrayObj.getClass().getComponentType();
                if (Array.getLength(arrayObj) > 0) {
                    Object component = null;
                    for (int i = 0; i < Array.getLength(arrayObj); i++) {
                        component = Array.get(arrayObj, i);
                        if (component != null) {
                            break;
                        }
                    }
                    if (component == null) {
                        return null;
                    }
                    if (componentType.isPrimitive()) {
                        componentType = component.getClass();
                    }
                    arrayObj = componentType.cast(component);
                } else {
                    return null;
                }
            }
        }
        return arrayObj;
    }

    // TODO: This should be access controlled. Define new permissions
    public void addMapping(Class<?> cls, JSONMapper mapper) {
        Class<?> input = cls;
        if (cls.isArray()) {
            input = getArrayComponent(cls);
        }
        if (!typeMapper.containsKey(input)) {
            typeMapper.put(input, mapper);
        }
    }

    public JSONMapper getTypeMapper(Object object) {
        if (object == null) return null;
        Object obj = object;
        Class<?> cls = object.getClass();
        if (cls.isArray()) {
            Object arrayElement = getArrayElement(obj);
            if (arrayElement instanceof CompositeData) {
                CompositeData cds = (CompositeData) arrayElement;
                return new OpenArrayTypeMapper(cls, cds.getCompositeType());
            } else if (arrayElement instanceof TabularData) {
                TabularData tds = (TabularData) arrayElement;
                return new OpenArrayTypeMapper(cls, tds.getTabularType());
            }
        }

        if (object instanceof CompositeData) {
            CompositeData cd = (CompositeData) object;
            return getTypeMapper(cd.getCompositeType());
        } else if (object instanceof TabularData) {
            TabularData cds = (TabularData) object;
            return getTypeMapper(cds.getTabularType());
        } else if (object instanceof Collection<?>) {
            Collection<?> c = (Collection<?>) object;
            boolean unknownMapper = c.stream().anyMatch(k -> (k != null) && (getTypeMapper(k) == null));
            if (unknownMapper)
                return null;
            else
                return new CollectionMapper();
        } else if (object instanceof Map<?, ?>) {
            Map<?, ?> map = (Map<?, ?>) object;
            boolean unknownMapper = map.keySet().stream().
                    anyMatch(k -> ((k != null) && (getTypeMapper(k) == null)
                            || (map.get(k) != null && getTypeMapper(map.get(k)) == null)));
            if (unknownMapper)
                return null;
            else
                return new MapMapper();
        } else {
            return getTypeMapper(cls);
        }
    }

    /*
    Using class object is better than class name as class objects for same class
    name differ for each classloader
     */
    public JSONMapper getTypeMapper(Class<?> type) {
        if (type == null) return null;
        if (type.isArray()) {
            Class<?> compType = getArrayComponent(type);
            if (typeMapper.get(compType) != null) {
                return new GenericArrayMapper(type);
            } else {
                return null;
            }
        }
        return typeMapper.get(type);
    }

    public JSONMapper getTypeMapper(OpenType<?> type) {
        if (type instanceof CompositeType) {
            return new OpenCompositeTypeMapper((CompositeType) type);
        } else if (type instanceof SimpleType) {
            try {
                return getTypeMapper(Class.forName(((SimpleType) type).getClassName()));
            } catch (ClassNotFoundException ex) { // This should not happen as SimpleTypes are always loaded
                throw new RuntimeException(ex);
            }
        } else if (type instanceof ArrayType) {
            try {
                ArrayType<?> at = (ArrayType) type;
                Class<?> arrayClass = Class.forName(type.getClassName());
                return new OpenArrayTypeMapper(arrayClass, at.getElementOpenType());
            } catch (ClassNotFoundException ex) { // This should not happen as SimpleTypes are always loaded
                throw new RuntimeException(ex);
            }
        } else if (type instanceof TabularType) {
            return new OpenTabularTypeMapper((TabularType) type);
        }
        return null; //keep compiler happy
    }

    public boolean isTypeMapped(String type) throws ClassNotFoundException {
        if (primitiveMap.get(type) != null) {
            return true;
        }
        Class<?> inputCls = Class.forName(type);
        inputCls = getArrayComponent(inputCls);
        if (inputCls.equals(CompositeData.class) || inputCls.equals(TabularData.class)
                || inputCls.equals(CompositeDataSupport.class) || inputCls.equals(TabularDataSupport.class)) {
            return true;
        }
        return JSONMappingFactory.INSTANCE.getTypeMapper(inputCls) != null;
    }

    private static class OpenArrayTypeMapper extends GenericArrayMapper {

        OpenArrayTypeMapper(Class<?> type, OpenType<?> elementOpenType) {
            super(type);
            mapper = JSONMappingFactory.INSTANCE.getTypeMapper(elementOpenType);
        }
    }

    // This mapper array type for any java class
    private static class GenericArrayMapper implements JSONMapper {

        private final Class<?> type;
        protected JSONMapper mapper;

        GenericArrayMapper(Class<?> type) {
            this.type = type;
            mapper = JSONMappingFactory.INSTANCE.getTypeMapper(JSONMappingFactory.INSTANCE.getArrayComponent(type));
        }

        private Object handleArrayType(Class<?> classType, JSONElement jsonValue) throws JSONDataException {
            if (!(jsonValue instanceof JSONArray)) {
                throw new JSONDataException("Invald JSON data format");
            }
            JSONArray jarr = (JSONArray) jsonValue;
            Class<?> compType = classType.getComponentType();
            Object resultArray = Array.newInstance(compType, jarr.size());

            for (int i = 0; i < jarr.size(); i++) {
                if (compType != null && compType.isArray()) {
                    Array.set(resultArray, i, handleArrayType(compType, jarr.get(i)));
                } else {
                    Array.set(resultArray, i, mapper.toJavaObject(jarr.get(i)));
                }
            }
            return resultArray;
        }

        @Override
        public Object toJavaObject(JSONElement jsonValue) throws JSONDataException {
            return handleArrayType(type, jsonValue);
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            if (data == null) {
                return new JSONPrimitive();
            }
            if (!data.getClass().equals(type)) {
                throw new JSONMappingException("Illegal type : " + data.getClass());
            }
            return getJasonValue(data);
        }

        private JSONElement getJasonValue(Object data) throws JSONMappingException {
            if (data == null) {
                return new JSONPrimitive();
            }
            if (!data.getClass().isArray()) {
                return mapper.toJsonValue(data);
            } else {
                JSONArray jArray = new JSONArray();
                for (int i = 0; i < Array.getLength(data); i++) {
                    jArray.add(getJasonValue(Array.get(data, i)));
                }
                return jArray;
            }
        }
    }

    /*
    Mapper for compositeType. CompositeData cannot be mapped without it's associated
    OpenType
     */
    private static class OpenCompositeTypeMapper implements JSONMapper {

        private final CompositeType type;

        OpenCompositeTypeMapper(CompositeType type) {
            this.type = type;
        }

        @Override
        public CompositeDataSupport toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (!(jsonValue instanceof JSONObject)) {
                throw new JSONDataException("JSON String not an object");
            }

            JSONObject jObject = (JSONObject) jsonValue;
            Map<String, Object> compositeDataMap = new HashMap<>();
            for (String itemName : type.keySet()) {
                OpenType<?> oType = type.getType(itemName);
                JSONMapper typeMapper = JSONMappingFactory.INSTANCE.getTypeMapper(oType);
                compositeDataMap.put(itemName, typeMapper.toJavaObject(jObject.get(itemName)));
            }
            try {
                return new CompositeDataSupport(type, compositeDataMap);
            } catch (OpenDataException ex) {
                throw new JSONDataException("Could not create CompositeData", ex);
            }
        }

        @Override
        public JSONElement toJsonValue(Object d) throws JSONMappingException {
            CompositeData data = (CompositeData) d;
            if (data == null) {
                return new JSONPrimitive();
            }
            JSONObject jObject = new JSONObject();
            for (String itemName : type.keySet()) {
                OpenType<?> oType = type.getType(itemName);
                JSONMapper typeMapper = JSONMappingFactory.INSTANCE.getTypeMapper(oType);
                if (typeMapper != null) {
                    jObject.put(itemName, typeMapper.toJsonValue(data.get(itemName)));
                } else {
                    System.out.println("Unable to find mapper for : " + oType);
                }
            }
            return jObject;
        }
    }

    private static class OpenTabularTypeMapper implements JSONMapper {

        private final TabularType type;

        public OpenTabularTypeMapper(TabularType type) {
            this.type = type;
        }

        /*
        Tabular data in JSON can be represented in below format
        [
            {
                "key" : [<list of elements>],
                "value": { <CompositeData> }
            },
            {
                "key" : [<list of elements>],
                "value": { <CompositeData> }
            }
        ]
         */
        @Override
        public TabularDataSupport toJavaObject(JSONElement jsonValue) throws JSONDataException {
//            if(jsonValue instanceof JSONArray) {
//                JSONArray jarr = (JSONArray) jsonValue;
//                for(JSONValue jval : jarr) {
//                    if(jval instanceof JSONObject) {
//                        JSONObject jObject = (JSONObject) jval;
//                        JSONValue jval1 = jObject.get("key");
//                        Object[] key;
//                        if(jval1 != null && jval1 instanceof JSONArray) {
//                            JSONArray jarr1 = (JSONArray) jval1;
//                            key = new Object[jarr1.size()];
//                            int i=0;
//                            for(JSONValue jval2: jarr1){
//                                key[i++] = ((JSONPrimitive)jval2).getValue();
//                            }
//                        }
//                        JSONValue jval2 = jObject.get("value");
//                        
//                        if(jval2 instanceof JSONObject) {
//                            JSONObject jObj1 = (JSONObject) jval2;
//                            JSONMapper typeMapper = JSONMappingFactory.INSTANCE.getTypeMapper(type.getRowType());
//                            Object valueObj = typeMapper.toJavaObject(jObj1);
//                        }
//                    }
//                }
//            }
            throw new UnsupportedOperationException();
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            if (data == null) {
                return new JSONPrimitive();
            }
            TabularDataSupport tds = (TabularDataSupport) data;
            JSONArray jsonArray = new JSONArray();
            for (Map.Entry<Object, Object> a : tds.entrySet()) {
                CompositeData cds = (CompositeData) a.getValue();
                JSONMapper cdsMapper = JSONMappingFactory.INSTANCE.getTypeMapper(cds);
                if(cdsMapper != null) {
                    jsonArray.add(cdsMapper.toJsonValue(cds));
                }
            }
            return jsonArray;
        }
    }

    private static class VoidMapper implements JSONMapper {

        @Override
        public Void toJavaObject(JSONElement jsonValue) throws JSONDataException {
            return null;
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            return new JSONPrimitive();
        }
    }

    private static class BooleanMapper implements JSONMapper {

        @Override
        public Boolean toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONPrimitive && ((JSONPrimitive) jsonValue).getValue() instanceof Boolean) {
                return (Boolean) ((JSONPrimitive) jsonValue).getValue();
            } else {
                throw new JSONDataException("Invalid type convertion - cannot convert "
                        + ((JSONPrimitive) jsonValue).getValue() + "(" + ((JSONPrimitive) jsonValue).getValue().getClass() + ")" + " to boolean");
            }
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            return new JSONPrimitive((Boolean) data);
        }
    }

    private static class ByteMapper implements JSONMapper {

        @Override
        public Byte toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONPrimitive && ((JSONPrimitive) jsonValue).getValue() instanceof Long) {
                return ((Long) ((JSONPrimitive) jsonValue).getValue()).byteValue();
            } else {
                throw new JSONDataException("Invalid type convertion - cannot convert "
                        + (((JSONPrimitive) jsonValue).getValue().getClass()) + " to byte");
            }
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            return new JSONPrimitive((Byte) data);
        }
    }

    private static class ShortMapper implements JSONMapper {

        @Override
        public Short toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONPrimitive && ((JSONPrimitive) jsonValue).getValue() instanceof Long) {
                return ((Long) ((JSONPrimitive) jsonValue).getValue()).shortValue();
            } else {
                throw new JSONDataException("Invalid JSON");
            }
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            return new JSONPrimitive((Short) data);
        }
    }

    private static class IntegerMapper implements JSONMapper {

        @Override
        public Integer toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONPrimitive && ((JSONPrimitive) jsonValue).getValue() instanceof Long) {
                return ((Long) ((JSONPrimitive) jsonValue).getValue()).intValue();
            } else {
                throw new JSONDataException("Invalid JSON");
            }
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            return new JSONPrimitive((Integer) data);
        }
    }

    private static class LongMapper implements JSONMapper {

        @Override
        public Long toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONPrimitive && ((JSONPrimitive) jsonValue).getValue() instanceof Long) {
                return (Long) ((JSONPrimitive) jsonValue).getValue();
            } else {
                throw new JSONDataException("Invalid JSON");
            }
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            return new JSONPrimitive((Long) data);
        }
    }

    private static class FloatMapper implements JSONMapper {

        @Override
        public Float toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONPrimitive && ((JSONPrimitive) jsonValue).getValue() instanceof Double) {
                return ((Double) ((JSONPrimitive) jsonValue).getValue()).floatValue();
            } else {
                throw new JSONDataException("Invalid JSON");
            }
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            return new JSONPrimitive((Float) data);
        }
    }

    private static class DoubleMapper implements JSONMapper {

        @Override
        public Double toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONPrimitive && ((JSONPrimitive) jsonValue).getValue() instanceof Long) {
                return (Double) ((JSONPrimitive) jsonValue).getValue();
            } else {
                throw new JSONDataException("Invalid JSON");
            }
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            return new JSONPrimitive((Double) data);
        }
    }

    private static class CharacterMapper implements JSONMapper {

        @Override
        public Character toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONPrimitive && ((JSONPrimitive) jsonValue).getValue() instanceof String) {
                String data = ((String) ((JSONPrimitive) jsonValue).getValue());
                if (data.length() < 1) {
                    throw new JSONDataException("Invalid char");
                }
                return data.charAt(0);
            } else {
                throw new JSONDataException("Invalid JSON");
            }
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            return new JSONPrimitive((Character) data);
        }
    }

    private static class StringMapper implements JSONMapper {

        @Override
        public String toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONPrimitive && ((JSONPrimitive) jsonValue).getValue() instanceof String) {
                return (String) ((JSONPrimitive) jsonValue).getValue();
            } else {
                throw new JSONDataException("Invalid JSON");
            }
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            return new JSONPrimitive((String) data);
        }
    }

    private static class BigDecimalMapper implements JSONMapper {

        @Override
        public BigDecimal toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONPrimitive && ((JSONPrimitive) jsonValue).getValue() instanceof Double) {
                return BigDecimal.valueOf((Double) ((JSONPrimitive) jsonValue).getValue());
            } else {
                throw new JSONDataException("Invalid JSON");
            }
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            return new JSONPrimitive(((BigDecimal) data).doubleValue());
        }
    }

    private static class BigIntegerMapper implements JSONMapper {

        @Override
        public BigInteger toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONPrimitive && ((JSONPrimitive) jsonValue).getValue() instanceof Long) {
                return BigInteger.valueOf((Long) ((JSONPrimitive) jsonValue).getValue());
            } else {
                throw new JSONDataException("Invalid JSON");
            }
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            return new JSONPrimitive(((BigInteger) data).longValue());
        }
    }

    private static class ObjectNameMapper implements JSONMapper {

        @Override
        public ObjectName toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONPrimitive && ((JSONPrimitive) jsonValue).getValue() instanceof String) {
                try {
                    return new ObjectName((String) ((JSONPrimitive) jsonValue).getValue());
                } catch (MalformedObjectNameException ex) {
                    throw new JSONDataException("Invalid Objectname");
                }
            } else {
                throw new JSONDataException("Invalid JSON");
            }
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            return new JSONPrimitive(data.toString());
        }
    }

    private static class DateMapper implements JSONMapper {

        private final DateFormat df = new SimpleDateFormat("YYYY-MM-DD");

        @Override
        public Date toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONPrimitive && ((JSONPrimitive) jsonValue).getValue() instanceof String) {
                String data = ((String) ((JSONPrimitive) jsonValue).getValue());
                try {
                    return df.parse(data);
                } catch (ParseException ex) {
                    throw new JSONDataException("Invalid Data " + data);
                }
            } else {
                throw new JSONDataException("Invalid JSON");
            }
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            return new JSONPrimitive(df.format((Date) data));
        }
    }

    private static final class MapMapper implements JSONMapper {

        @Override
        public Object toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONObject) {
                JSONObject obj = (JSONObject) jsonValue;
                Map<String, Object> result = new HashMap<>(obj.size());
                for (String k : result.keySet()) {
                    JSONElement elem = obj.get(k);
                    if (elem instanceof JSONPrimitive) {
                        result.put(k, ((JSONPrimitive) elem).getValue());
                    } else {
                        JSONMapper mapper;
                        if (elem instanceof JSONObject) {
                            mapper = new MapMapper();
                            result.put(k, mapper.toJavaObject(elem));
                        } else if (elem instanceof JSONArray) {
                            mapper = new CollectionMapper();
                            result.put(k, mapper.toJavaObject(elem));
                        } else {
                            throw new JSONDataException("Unable to map : " + elem.getClass());
                        }
                    }
                }
                return result;
            }
            throw new JSONDataException("Inalid input");
        }

        @Override
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            if (data instanceof Map) {
                JSONObject jobj = new JSONObject();
                Map<?, ?> input = (Map<?, ?>) data;
                for (Object k : input.keySet()) {
                    String key = k.toString();
                    final Object value = input.get(k);
                    if (value == null) {
                        jobj.put(key, new JSONPrimitive());
                    } else {
                        JSONMapper mapper = JSONMappingFactory.INSTANCE.getTypeMapper(value);
                        if (mapper == null) {
                            throw new JSONMappingException("Unable to map : " + value);
                        }
                        jobj.put(key, mapper.toJsonValue(value));
                    }
                }
                return jobj;
            } else {
                throw new JSONMappingException("Invalid Input");
            }
        }
    }

    private static final class CollectionMapper implements JSONMapper {

        public CollectionMapper() {
        }

        @Override
        @SuppressWarnings("unchecked")
        public Object toJavaObject(JSONElement jsonValue) throws JSONDataException {
            if (jsonValue instanceof JSONArray) {
                JSONArray jarr = (JSONArray) jsonValue;
                List<Object> result = new ArrayList<>(jarr.size());
                for (JSONElement elem : jarr) {
                    if (elem instanceof JSONPrimitive) {
                        result.add(((JSONPrimitive) elem).getValue());
                    } else {
                        JSONMapper mapper;
                        if (elem instanceof JSONObject) {
                            mapper = new MapMapper();
                            result.add(mapper.toJavaObject(elem));
                        } else if (elem instanceof JSONArray) {
                            mapper = new CollectionMapper();
                            result.add(mapper.toJavaObject(elem));
                        } else {
                            throw new JSONDataException("Unable to map : " + elem.getClass());
                        }
                    }
                }
                return result;
            }
            throw new JSONDataException("Inalid input");
        }

        @Override
        @SuppressWarnings("unchecked")
        public JSONElement toJsonValue(Object data) throws JSONMappingException {
            if (data instanceof Collection) {
                JSONArray jarr = new JSONArray();
                Collection<Object> c = (Collection<Object>) data;
                Iterator<Object> itr = c.iterator();
                while (itr.hasNext()) {
                    Object next = itr.next();
                    JSONMapper typeMapper = JSONMappingFactory.INSTANCE.getTypeMapper(next);
                    if (typeMapper == null) {
                        throw JSONMappingException.UNABLE_TO_MAP;
                    }
                    jarr.add(typeMapper.toJsonValue(next));
                }
                return jarr;
            } else {
                throw new JSONMappingException("Invalid Input");
            }
        }
    }
}