# JacksonValueDeserializer.java

    /*
     * Copyright (c) 2012, Paul Merlin. All Rights Reserved.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     *
     *     You may obtain a copy of the License at
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package org.qi4j.valueserialization.jackson;

    import com.fasterxml.jackson.core.JsonFactory;
    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.core.JsonToken;
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.MappingJsonFactory;
    import com.fasterxml.jackson.databind.node.ArrayNode;
    import com.fasterxml.jackson.databind.node.ObjectNode;
    import java.io.InputStream;
    import java.util.Collection;
    import java.util.Iterator;
    import java.util.Map;
    import org.qi4j.api.injection.scope.Service;
    import org.qi4j.api.injection.scope.Structure;
    import org.qi4j.api.service.ServiceReference;
    import org.qi4j.api.structure.Application;
    import org.qi4j.api.structure.Module;
    import org.qi4j.api.type.ValueType;
    import org.qi4j.api.value.ValueDeserializer;
    import org.qi4j.api.value.ValueSerializationException;
    import org.qi4j.functional.Function;
    import org.qi4j.spi.value.ValueDeserializerAdapter;

    /**
     * ValueDeserializer reading Values from JSON documents using Jackson.
     */
    public class JacksonValueDeserializer
        extends ValueDeserializerAdapter<JsonParser, JsonNode>
    {

        private final JsonFactory jsonFactory = new MappingJsonFactory();

        public JacksonValueDeserializer( @Structure Application application,
                                         @Structure Module module,
                                         @Service ServiceReference<ValueDeserializer> serviceRef )
        {
            super( application, module, serviceRef );
        }

        @Override
        protected JsonParser adaptInput( InputStream input )
            throws Exception
        {
            return jsonFactory.createParser( input );
        }

        @Override
        protected void onDeserializationEnd( ValueType valueType, JsonParser input )
            throws Exception
        {
            input.close();
        }

        @Override
        protected Object readPlainValue( JsonParser input )
            throws Exception
        {
            JsonNode jsonNode = input.readValueAsTree();
            if( jsonNode.isArray() || jsonNode.isObject() )
            {
                throw new ValueSerializationException( "Asked for a Value but found an Object or an Array at "
                                                       + input.getCurrentLocation().toString() );
            }
            if( jsonNode.isDouble() )
            {
                return jsonNode.asDouble();
            }
            if( jsonNode.isLong() )
            {
                return jsonNode.asLong();
            }
            if( jsonNode.isInt() )
            {
                return jsonNode.asInt();
            }
            if( jsonNode.isBoolean() )
            {
                return jsonNode.asBoolean();
            }
            if( jsonNode.isNull() )
            {
                return null;
            }
            return jsonNode.asText();
        }

        @Override
        protected <T> Collection<T> readArrayInCollection( JsonParser input,
                                                           Function<JsonParser, T> deserializer,
                                                           Collection<T> collection )
            throws Exception
        {
            JsonToken token = input.getCurrentToken();
            if( token == JsonToken.VALUE_NULL )
            {
                return null;
            }
            if( token != JsonToken.START_ARRAY )
            {
                token = input.nextToken();
            }
            if( token == JsonToken.VALUE_NULL )
            {
                return null;
            }
            if( token != JsonToken.START_ARRAY )
            {
                throw new ValueSerializationException( "Expected an array start at "
                                                       + input.getCurrentLocation().toString() );
            }
            while( input.nextToken() != JsonToken.END_ARRAY )
            {
                T element = deserializer.map( input );
                collection.add( element );
            }
            return collection;
        }

        @Override
        protected <K, V> Map<K, V> readMapInMap( JsonParser input,
                                                 Function<JsonParser, K> keyDeserializer,
                                                 Function<JsonParser, V> valueDeserializer,
                                                 Map<K, V> map )
            throws Exception
        {
            JsonToken token = input.getCurrentToken();
            if( token == JsonToken.VALUE_NULL )
            {
                return null;
            }
            if( token != JsonToken.START_ARRAY )
            {
                token = input.nextToken();
            }
            if( token == JsonToken.VALUE_NULL )
            {
                return null;
            }
            if( token != JsonToken.START_ARRAY )
            {
                throw new ValueSerializationException( "Expected an array start at "
                                                       + input.getCurrentLocation().toString() );
            }
            JsonToken currentToken = input.nextToken();
            while( currentToken != JsonToken.END_ARRAY )
            {
                if( currentToken != JsonToken.START_OBJECT )
                {
                    throw new ValueSerializationException( "Expected an object start at "
                                                           + input.getCurrentLocation().toString() );
                }
                currentToken = input.nextToken();
                K key = null;
                V value = null;
                while( currentToken != JsonToken.END_OBJECT )
                {
                    String objectKey = input.getCurrentName();
                    input.nextToken();
                    if( "key".equals( objectKey ) )
                    {
                        key = keyDeserializer.map( input );
                    }
                    else if( "value".equals( objectKey ) )
                    {
                        value = valueDeserializer.map( input );
                    }
                    else
                    {
                        //input.nextToken();
                        input.skipChildren();
                    }
                    currentToken = input.nextToken();
                }
                if( key != null )
                {
                    map.put( key, value );
                }
                currentToken = input.nextToken();
            }
            return map;
        }

        @Override
        protected ObjectNode readObjectTree( JsonParser input )
            throws Exception
        {
            JsonToken token = input.getCurrentToken();
            if( token == JsonToken.VALUE_NULL )
            {
                return null;
            }
            if( token != JsonToken.START_OBJECT )
            {
                token = input.nextToken();
            }
            if( token != JsonToken.START_OBJECT )
            {
                throw new ValueSerializationException( "Expected an object start at "
                                                       + input.getCurrentLocation().toString() );
            }
            return (ObjectNode) input.readValueAsTree();
        }

        @Override
        protected Object asSimpleValue( JsonNode inputNode )
            throws Exception
        {
            if( isNullOrMissing( inputNode ) )
            {
                return null;
            }
            if( !inputNode.isValueNode() )
            {
                throw new ValueSerializationException( "Expected a value node but got a container node " + inputNode );
            }
            if( inputNode.isDouble() )
            {
                return inputNode.asDouble();
            }
            if( inputNode.isLong() )
            {
                return inputNode.asLong();
            }
            if( inputNode.isInt() )
            {
                return inputNode.asInt();
            }
            if( inputNode.isBoolean() )
            {
                return inputNode.asBoolean();
            }
            return inputNode.asText();
        }

        @Override
        protected boolean isObjectValue( JsonNode inputNode )
            throws Exception
        {
            if( isNullOrMissing( inputNode ) )
            {
                return false;
            }
            return inputNode.isObject();
        }

        @Override
        protected boolean objectHasField( JsonNode inputNode, String key )
            throws Exception
        {
            if( isNullOrMissing( inputNode ) )
            {
                return false;
            }
            if( !inputNode.isObject() )
            {
                throw new ValueSerializationException( "Expected an object but got " + inputNode );
            }
            return inputNode.has( key );
        }

        @Override
        protected <T> T getObjectFieldValue( JsonNode inputNode, String key, Function<JsonNode, T> valueDeserializer )
            throws Exception
        {
            JsonNode valueNode = inputNode.get( key );
            if( isNullOrMissing( valueNode ) )
            {
                return null;
            }
            T value = valueDeserializer.map( valueNode );
            return value;
        }

        @Override
        protected <T> void putArrayNodeInCollection( JsonNode inputNode,
                                                     Function<JsonNode, T> deserializer,
                                                     Collection<T> collection )
            throws Exception
        {
            if( isNullOrMissing( inputNode ) )
            {
                return;
            }
            if( !inputNode.isArray() )
            {
                throw new ValueSerializationException( "Expected an array but got " + inputNode );
            }
            ArrayNode array = (ArrayNode) inputNode;
            for( JsonNode item : array )
            {
                T value = deserializer.map( item );
                collection.add( value );
            }
        }

        @Override
        protected <K, V> void putArrayNodeInMap( JsonNode inputNode,
                                                 Function<JsonNode, K> keyDeserializer,
                                                 Function<JsonNode, V> valueDeserializer,
                                                 Map<K, V> map )
            throws Exception
        {
            if( isNullOrMissing( inputNode ) )
            {
                return;
            }
            if( !inputNode.isArray() )
            {
                throw new ValueSerializationException( "Expected an array but got " + inputNode );
            }
            ArrayNode array = (ArrayNode) inputNode;
            for( JsonNode item : array )
            {
                if( !item.isObject() )
                {
                    throw new ValueSerializationException( "Expected an object but got " + inputNode );
                }
                JsonNode keyNode = item.get( "key" );
                JsonNode valueNode = item.get( "value" );
                K key = keyDeserializer.map( keyNode );
                V value = valueDeserializer.map( valueNode );
                if( key != null )
                {
                    map.put( key, value );
                }
            }
        }

        @Override
        protected <V> void putObjectNodeInMap( JsonNode inputNode,
                                               Function<JsonNode, V> valueDeserializer,
                                               Map<String, V> map
        )
            throws Exception
        {
            if( isNullOrMissing( inputNode ) )
            {
                return;
            }
            if( !inputNode.isObject() )
            {
                throw new ValueSerializationException( "Expected an object but got " + inputNode );
            }
            ObjectNode object = (ObjectNode) inputNode;
            Iterator<Map.Entry<String, JsonNode>> fields = object.fields();
            while( fields.hasNext() )
            {
                Map.Entry<String, JsonNode> entry = fields.next();
                V value = valueDeserializer.map( entry.getValue() );
                map.put( entry.getKey(), value );
            }
        }

        private static boolean isNullOrMissing( JsonNode inputNode )
        {
            return inputNode == null || inputNode.isNull() || inputNode.isMissingNode();
        }
    }