IonValueDeserializer.java

/*
 * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. 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.
 * A copy of the License is located at:
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file 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 com.fasterxml.jackson.dataformat.ion.ionvalue;

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.util.AccessPattern;
import com.fasterxml.jackson.dataformat.ion.IonParser;
import com.amazon.ion.*;

/**
 * Deserializer that knows how to deserialize an IonValue.
 */
class IonValueDeserializer extends JsonDeserializer<IonValue> implements ContextualDeserializer {

    private final JavaType _targetType;

    public IonValueDeserializer() {
        this._targetType = null;
    }

    public IonValueDeserializer(JavaType targetType) {
        this._targetType = targetType;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
        JavaType contextualType = (property != null)
            ? property.getType()
            : ctxt.getContextualType(); // fallback
        return new IonValueDeserializer(contextualType);
    }

    @Override
    public IonValue deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {

        Object embeddedObject = jp.getEmbeddedObject();
        if (embeddedObject instanceof IonValue) {
            return (IonValue) embeddedObject;
        }
        // We rely on the IonParser's IonSystem to wrap supported types into an IonValue
        if (!(jp instanceof IonParser)) {
            throw JsonMappingException.from(jp, "Unsupported parser for deserializing "
                    + embeddedObject.getClass().getCanonicalName() + " into IonValue");
        }

        IonSystem ionSystem = ((IonParser) jp).getIonSystem();
        if (embeddedObject instanceof Timestamp) {
            return ionSystem.newTimestamp((Timestamp) embeddedObject);
        }
        if (embeddedObject instanceof byte[]) {
            // The parser provides no distinction between BLOB and CLOB, deserializing to a BLOB is the safest choice.
            return ionSystem.newBlob((byte[]) embeddedObject);
        }
        throw JsonMappingException.from(jp, "Cannot deserialize embedded object type "
                + embeddedObject.getClass().getCanonicalName() + " into IonValue");
    }

    @Override
    public IonValue getNullValue(DeserializationContext ctxt) throws JsonMappingException {
        try {
            final JsonParser parser = ctxt.getParser();
            if (parser != null && parser.getCurrentToken() != JsonToken.END_OBJECT) {
                final Object embeddedObj = parser.getEmbeddedObject();
                if (embeddedObj instanceof IonValue) {
                    IonValue iv = (IonValue) embeddedObj;
                    if (iv.isNullValue()) {
                        if (IonType.isContainer(iv.getType())) {
                            return iv;
                        }
                        IonType containerType = getIonContainerType();
                        if (containerType != null) {
                            IonSystem ionSystem = ((IonParser) parser).getIonSystem();
                            return ionSystem.newNull(containerType);
                        }
                        return iv;
                    }
                }
            }
            return super.getNullValue(ctxt);
        } catch (IOException e) {
            throw JsonMappingException.from(ctxt, e.toString());
        }
    }

    private IonType getIonContainerType() {
        if (_targetType != null) {
            Class<?> clazz = _targetType.getRawClass();
            if (IonStruct.class.isAssignableFrom(clazz)) return IonType.STRUCT;
            if (IonList.class.isAssignableFrom(clazz)) return IonType.LIST;
            if (IonSexp.class.isAssignableFrom(clazz)) return IonType.SEXP;
        }
        return null;
    }

    @Override
    public AccessPattern getNullAccessPattern() {
        return AccessPattern.DYNAMIC;
    }
}