IonObjectMapper.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 tools.jackson.dataformat.ion;

import java.io.IOException;
import java.util.Date;

import tools.jackson.core.JacksonException;
import tools.jackson.core.Version;
import tools.jackson.core.exc.JacksonIOException;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.cfg.MapperBuilderState;
import tools.jackson.databind.deser.DeserializationContextExt;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.ser.SerializationContextExt;
import tools.jackson.dataformat.ion.ionvalue.IonValueModule;

import com.amazon.ion.IonDatagram;
import com.amazon.ion.IonReader;
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonValue;
import com.amazon.ion.IonWriter;
import com.amazon.ion.system.IonSystemBuilder;

/**
 * Specialization of {@link ObjectMapper} that will set underlying
 * factory to be an instance of {@link IonFactory}.
 */
public class IonObjectMapper extends ObjectMapper
{
    private static final long serialVersionUID = 3L;

    /**
     * Base implementation for "Vanilla" {@link ObjectMapper}, used with
     * ION backend.
     *
     * @since 3.0
     */
    public static class Builder extends MapperBuilder<IonObjectMapper, Builder>
    {
        public Builder(IonFactory f) {
            super(f);

            // 04-Jan-2017, tatu: demoted from `IonValueMapper`
            addModule(new IonValueModule());
            addModule(new EnumAsIonSymbolModule());

            // !!! 04-Jan-2018, tatu: needs to be reworked in future; may remain a module
            // Use native Ion timestamps for dates
            SimpleModule m = new SimpleModule("IonTimestampModule", PackageVersion.VERSION,
                    "IonTimestampModule");
            m.addSerializer(Date.class, new IonTimestampSerializers.IonTimestampJavaDateSerializer());
            m.addSerializer(java.sql.Date.class, new IonTimestampSerializers.IonTimestampSQLDateSerializer());
            m.addDeserializer(Date.class, new IonTimestampDeserializers.IonTimestampJavaDateDeserializer());
            m.addDeserializer(java.sql.Date.class, new IonTimestampDeserializers.IonTimestampSQLDateDeserializer());
            addModule(m);
        }

        public Builder(StateImpl state) {
            super(state);
            // other stuff should have been preserved along with state
        }

        @Override
        public IonObjectMapper build() {
            return new IonObjectMapper(this);
        }

        @Override
        protected MapperBuilderState _saveState() {
            return new StateImpl(this);
        }

        /*
        /******************************************************************
        /* Format features
        /******************************************************************
         */

        public Builder enable(IonReadFeature... features) {
            for (IonReadFeature f : features) {
                _formatReadFeatures |= f.getMask();
            }
            return this;
        }

        public Builder disable(IonReadFeature... features) {
            for (IonReadFeature f : features) {
                _formatReadFeatures &= ~f.getMask();
            }
            return this;
        }

        public Builder configure(IonReadFeature feature, boolean state)
        {
            if (state) {
                _formatReadFeatures |= feature.getMask();
            } else {
                _formatReadFeatures &= ~feature.getMask();
            }
            return this;
        }

        public Builder enable(IonWriteFeature... features) {
            for (IonWriteFeature f : features) {
                _formatWriteFeatures |= f.getMask();
            }
            return this;
        }

        public Builder disable(IonWriteFeature... features) {
            for (IonWriteFeature f : features) {
                _formatWriteFeatures &= ~f.getMask();
            }
            return this;
        }

        public Builder configure(IonWriteFeature feature, boolean state)
        {
            if (state) {
                _formatWriteFeatures |= feature.getMask();
            } else {
                _formatWriteFeatures &= ~feature.getMask();
            }
            return this;
        }

        protected static class StateImpl extends MapperBuilderState
            implements java.io.Serializable // important!
        {
            private static final long serialVersionUID = 3L;

            public StateImpl(Builder src) {
                super(src);
            }

            // We also need actual instance of state as base class can not implement logic
             // for reinstating mapper (via mapper builder) from state.
            @Override
            protected Object readResolve() {
                return new Builder(this).build();
            }
        }
    }

    /*
    /**********************************************************************
    /* Life-cycle, constructors
    /**********************************************************************
     */

    public IonObjectMapper() {
        this(new IonFactory());
    }

    public IonObjectMapper(IonFactory f) {
        this(new Builder(f));
    }

    public IonObjectMapper(Builder b) {
        super(b);
    }

    /*
    /**********************************************************************
    /* Life-cycle, builders
    /**********************************************************************
     */

    /**
     * A builder for a mapper that will use textual writers by default. Same as
     * {@link #builderForTextualWriters()}.
     */
    public static Builder builder() {
        return builderForTextualWriters();
    }

    /**
     * A builder for a mapper that will use specified {@code streamFactory},
     * pre-configured.
     */
    public static Builder builder(IonFactory streamFactory) {
        return new Builder(streamFactory);
    }

    /**
     * A builder for a mapper that will use textual writers by default and the
     * provided {@link IonSystem}. Same as {@link #builderForTextualWriters(IonSystem)}.
     */
    public static Builder builder(IonSystem ionSystem) {
        return builderForTextualWriters(ionSystem);
    }

    /**
     * A builder for a mapper that will use binary writers by default.
     */
    public static Builder builderForBinaryWriters() {
        return builderForBinaryWriters(IonSystemBuilder.standard().build());
    }

    /**
     * A builder for a mapper that will use binary writers by default and the
     * provided {@link IonSystem}
     */
    public static Builder builderForBinaryWriters(IonSystem ionSystem) {
        return builder(IonFactory.builderForBinaryWriters()
                .ionSystem(ionSystem)
                .build());
    }

    /**
     * A builder for a mapper that will use textual writers by default.
     */
    public static Builder builderForTextualWriters() {
        return builderForTextualWriters(IonSystemBuilder.standard().build());
    }

    /**
     * A builder for a mapper that will use textual writers by default and the
     * provided {@link IonSystem}.
     */
    public static Builder builderForTextualWriters(IonSystem ionSystem) {
        return builder(IonFactory.builderForTextualWriters()
                .ionSystem(ionSystem)
                .build());
    }

    @SuppressWarnings("unchecked")
    @Override
    public Builder rebuild() {
        return new Builder((Builder.StateImpl) _savedBuilderState);
    }

    /*
    /**********************************************************************
    /* Life-cycle, shared "vanilla" (default configuration) instance
    /**********************************************************************
     */

    /**
     * Accessor method for getting globally shared "default" {@link IonObjectMapper}
     * instance: one that has default configuration, no modules registered, no
     * config overrides. Usable mostly when dealing "untyped" or Tree-style
     * content reading and writing.
     */
    public static IonObjectMapper shared() {
        return SharedWrapper.wrapped();
    }
    /*
    /**********************************************************************
    /* Basic accessor overrides
    /**********************************************************************
     */

    @Override
    public Version version() {
        return PackageVersion.VERSION;
    }

    @Override
    public IonFactory tokenStreamFactory() {
        return (IonFactory) _streamFactory;
    }

    /*
    /**********************************************************************
    /* Format-specific
    /**********************************************************************
     */

    public boolean isEnabled(IonReadFeature f) {
        return _deserializationConfig.hasFormatFeature(f);
    }

    public boolean isEnabled(IonWriteFeature f) {
        return _serializationConfig.hasFormatFeature(f);
    }


    /*
    /************************************************************************
    /* Additional convenience factory methods for parsers, generators
    /************************************************************************
     */

    /**
     * @since 3.0
     */
    public IonParser createParser(IonReader src) {
        DeserializationContextExt ctxt = _deserializationContext();
        return (IonParser) ctxt.assignAndReturnParser(tokenStreamFactory().createParser(ctxt, src));
    }

    /**
     * @since 3.0
     */
    public IonParser createParser(IonValue value) {
        DeserializationContextExt ctxt = _deserializationContext();
        return (IonParser) ctxt.assignAndReturnParser(tokenStreamFactory().createParser(ctxt, value));
    }

    /**
     * @since 3.0
     */
    public IonGenerator createGenerator(IonWriter out) {
        return (IonGenerator) tokenStreamFactory().createGenerator(_serializationContext(), out);
    }

    /*
    /************************************************************************
    /* Convenience read/write methods for IonReader, IonWriter, and IonValue,
    /* by analogy with the existing convenience methods of ObjectMapper
    /************************************************************************
     */

    /**
     * Deserialize an Ion value read from the supplied IonReader into a Java
     * type.
     * <p>
     * Note: method does not close the underlying reader
     */
    @SuppressWarnings("unchecked")
    public <T> T readValue(IonReader r, Class<T> valueType) throws JacksonException {
        DeserializationContextExt ctxt = _deserializationContext();
        return (T)_readMapAndClose(ctxt, tokenStreamFactory().createParser(ctxt, r),
                _typeFactory.constructType(valueType));
    }

    /**
     * Deserialize an Ion value read from the supplied IonReader into a Java
     * type.
     * <p>
     * Note: method does not close the underlying reader
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public <T> T readValue(IonReader r, TypeReference valueTypeRef) throws JacksonException {
        DeserializationContextExt ctxt = _deserializationContext();
        return (T)_readMapAndClose(ctxt, tokenStreamFactory().createParser(ctxt, r),
                _typeFactory.constructType(valueTypeRef));
    }

    /**
     * Deserialize an Ion value read from the supplied IonReader into a Java
     * type.
     * <p>
     * Note: method does not close the underlying reader
     */
    @SuppressWarnings("unchecked")
    public <T> T readValue(IonReader r, JavaType valueType) throws JacksonException {
        DeserializationContextExt ctxt = _deserializationContext();
        return (T)_readMapAndClose(ctxt, tokenStreamFactory().createParser(ctxt, r), valueType);
    }

    /**
     * Convenience method for converting Ion value into given value type.
     */
    @SuppressWarnings("unchecked")
    public <T> T readValue(IonValue value, Class<T> valueType) throws JacksonException {
        if (value == null) {
            return null;
        }
        DeserializationContextExt ctxt = _deserializationContext();
        return (T)_readMapAndClose(ctxt, tokenStreamFactory().createParser(ctxt, value),
                _typeFactory.constructType(valueType));
    }

    /**
     * Convenience method for converting Ion value into given value type.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public <T> T readValue(IonValue value, TypeReference valueTypeRef) throws JacksonException {
        if (value == null) {
            return null;
        }
        DeserializationContextExt ctxt = _deserializationContext();
        return (T)_readMapAndClose(ctxt, tokenStreamFactory().createParser(ctxt, value),
                _typeFactory.constructType(valueTypeRef));
    }

    /**
     * Convenience method for converting Ion value into given value type.
     */
    @SuppressWarnings("unchecked")
    public <T> T readValue(IonValue value, JavaType valueType) throws JacksonException  {
        if (value == null) {
            return null;
        }
        DeserializationContextExt ctxt = _deserializationContext();
        return (T)_readMapAndClose(ctxt, tokenStreamFactory().createParser(ctxt, value), valueType);
    }

    /**
     * Method that can be used to serialize any Java value as
     * Ion output, using IonWriter provided.
     *<p>
     * Note: method does not close the underlying writer explicitly
     */
    public void writeValue(IonWriter w, Object value) throws JacksonException {
        SerializationContextExt prov = _serializationContext();
        _configAndWriteValue(prov,
                tokenStreamFactory().createGenerator(prov, w), value);
    }

    /**
     * Method that can be used to map any Java value to an IonValue.
     */
    public IonValue writeValueAsIonValue(Object value) throws JacksonException
    {
        // 04-Jan-2017, tatu: Bit of incompatiblity wrt 2.x handling: should this result in
        //   Java `null`, or Ion null marker? For now, choose latter
/*
        if (value == null) {
            return null;
        }
        */

        IonFactory f = tokenStreamFactory();
        IonDatagram container = f._system.newDatagram();
        try (IonWriter writer = f._system.newWriter(container)) {
            writeValue(writer, value);
            IonValue result = container.get(0);
            result.removeFromContainer();
            return result;
        } catch (IOException e) {
            throw JacksonIOException.construct(e);
        }
    }

    /*
    /**********************************************************
    /* Helper class(es)
    /**********************************************************
     */

    /**
     * Helper class to contain dynamically constructed "shared" instance of
     * mapper, should one be needed via {@link #shared}.
     */
    private final static class SharedWrapper {
        private final static IonObjectMapper MAPPER = IonObjectMapper.builder().build();

        public static IonObjectMapper wrapped() { return MAPPER; }
    }
}