JavaPropsMapper.java

package tools.jackson.dataformat.javaprop;

import java.io.IOException;
import java.util.*;

import tools.jackson.core.JsonParser;
import tools.jackson.core.Version;

import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.cfg.*;
import tools.jackson.databind.ser.SerializationContextExt;

public class JavaPropsMapper extends ObjectMapper
{
    private static final long serialVersionUID = 3L;

    /**
     * Base implementation for "Vanilla" {@link ObjectMapper}, used with
     * Java Properties backend.
     *
     * @since 3.0
     */
    public static class Builder extends MapperBuilder<JavaPropsMapper, Builder>
    {
        public Builder(JavaPropsFactory f) {
            super(f);
            // 09-Apr-2021, tatu: [dataformats-text#255]: take empty String to
            //    mean `null` where applicable; also accept Blank same way
            enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);

            _coercionConfigs.defaultCoercions()
                .setAcceptBlankAsEmpty(Boolean.TRUE)
                .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsEmpty)
            ;
        }

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

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

        @Override
        protected MapperBuilderState _saveState() {
            return new StateImpl(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
    /**********************************************************************
     */

    public JavaPropsMapper() {
        this(new JavaPropsFactory());
    }

    public JavaPropsMapper(JavaPropsFactory f) {
        this(new Builder(f));
    }

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

    public static Builder builder() {
        return new Builder(new JavaPropsFactory());
    }

    public static Builder builder(JavaPropsFactory streamFactory) {
        return new Builder(streamFactory);
    }

    @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 JavaPropsMapper}
     * 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 JavaPropsMapper shared() {
        return SharedWrapper.wrapped();
    }

    /*
    /**********************************************************************
    /* Life-cycle: JDK serialization support
    /**********************************************************************
     */

    // 27-Feb-2018, tatu: Not sure why but it seems base class definitions
    //   are not sufficient alone; sub-classes must re-define.
    @Override
    protected Object writeReplace() {
        return _savedBuilderState;
    }

    @Override
    protected Object readResolve() {
        throw new IllegalStateException("Should never deserialize `"+getClass().getName()+"` directly");
    }

    /*
    /**********************************************************************
    /* Basic accessor overrides
    /**********************************************************************
     */

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

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

    /*
    /**********************************************************************
    /* Extended read methods, from Properties objects
    /**********************************************************************
     */

    /**
     * Convenience method which uses given `Properties` as the source
     * as if they had been read from an external source, processes
     * them (splits paths etc), and then binds as given result
     * value.
     *<p>
     * Note that this is NOT identical to calling {@link #convertValue(Object, Class)};
     * rather, it would be similar to writing `Properties` out into a File,
     * then calling `readValue()` on this mapper to bind contents.
     */
    @SuppressWarnings("resource")
    public <T> T readPropertiesAs(Properties props, JavaPropsSchema schema,
            Class<T> valueType) throws IOException
    {
        DeserializationContext ctxt = _deserializationContext();
        JsonParser p = tokenStreamFactory().createParser(ctxt, schema, props);
        return (T) readValue(p, valueType);
    }

    /**
     * Convenience method which uses given `Properties` as the source
     * as if they had been read from an external source, processes
     * them (splits paths etc), and then binds as given result
     * value.
     *<p>
     * Note that this is NOT identical to calling {@link #convertValue(Object, Class)};
     * rather, it would be similar to writing `Properties` out into a File,
     * then calling `readValue()` on this mapper to bind contents.
     */
    @SuppressWarnings({ "resource", "unchecked" })
    public <T> T readPropertiesAs(Properties props, JavaPropsSchema schema,
            JavaType valueType) throws IOException
    {
        DeserializationContext ctxt = _deserializationContext();
        JsonParser p = tokenStreamFactory().createParser(ctxt, schema, props);
        return (T) readValue(p, valueType);
    }

    /**
     * Convenience method, functionally equivalent to:
     *<pre>
     *   readPropertiesAs(props, JavaPropsSchema.emptySchema(), valueType);
     *</pre>
     */
    public <T> T readPropertiesAs(Properties props, Class<T> valueType) throws IOException {
        return readPropertiesAs(props, JavaPropsSchema.emptySchema(), valueType);
    }

    /**
     * Convenience method, functionally equivalent to:
     *<pre>
     *   readPropertiesAs(props, JavaPropsSchema.emptySchema(), valueType);
     *</pre>
     */
    public <T> T readPropertiesAs(Properties props, JavaType valueType) throws IOException {
        return readPropertiesAs(props, JavaPropsSchema.emptySchema(), valueType);
    }

    /*
    /**********************************************************************
    /* Extended read methods, from Map objects
    /**********************************************************************
     */

    /**
     * Convenience method which uses given `Properties` as the source
     * as if they had been read from an external source, processes
     * them (splits paths etc), and then binds as given result
     * value.
     *<p>
     * Note that this is NOT identical to calling {@link #convertValue(Object, Class)};
     * rather, it would be similar to writing `Properties` out into a File,
     * then calling `readValue()` on this mapper to bind contents.
     */
    @SuppressWarnings("resource")
    public <T> T readMapAs(Map<String, String> map, JavaPropsSchema schema,
            Class<T> valueType) throws IOException {
        DeserializationContext ctxt = _deserializationContext();
        JsonParser p = tokenStreamFactory().createParser(ctxt, schema, map);
        return (T) readValue(p, valueType);
    }

    /**
     * Convenience method which uses given `Properties` as the source
     * as if they had been read from an external source, processes
     * them (splits paths etc), and then binds as given result
     * value.
     *<p>
     * Note that this is NOT identical to calling {@link #convertValue(Object, Class)};
     * rather, it would be similar to writing `Properties` out into a File,
     * then calling `readValue()` on this mapper to bind contents.
     */
    @SuppressWarnings({ "resource", "unchecked" })
    public <T> T readMapAs(Map<String, String> map, JavaPropsSchema schema,
            JavaType valueType) throws IOException {
        DeserializationContext ctxt = _deserializationContext();
        JsonParser p = tokenStreamFactory().createParser(ctxt, schema, map);
        return (T) readValue(p, valueType);
    }

    /**
     * Convenience method, functionally equivalent to:
     *<pre>
     *   readPropertiesAs(props, JavaPropsSchema.emptySchema(), valueType);
     *</pre>
     */
    public <T> T readMapAs(Map<String, String> map, Class<T> valueType) throws IOException {
        return readMapAs(map, JavaPropsSchema.emptySchema(), valueType);
    }

    /**
     * Convenience method, functionally equivalent to:
     *<pre>
     *   readPropertiesAs(props, JavaPropsSchema.emptySchema(), valueType);
     *</pre>
     */
    public <T> T readMapAs(Map<String, String> map, JavaType valueType) throws IOException {
        return readMapAs(map, JavaPropsSchema.emptySchema(), valueType);
    }
    
    /*
    /**********************************************************************
    /* Extended read methods, from System Properties
    /**********************************************************************
     */

    /**
     * Convenience method, functionally equivalent to:
     *<pre>
     *   readPropertiesAs(System.getProperties(), schema, valueType);
     *</pre>
     */
    public <T> T readSystemPropertiesAs(JavaPropsSchema schema, 
            Class<T> valueType) throws IOException {
        return readPropertiesAs(System.getProperties(), schema, valueType);
    }

    /**
     * Convenience method, functionally equivalent to:
     *<pre>
     *   readPropertiesAs(System.getProperties(), schema, valueType);
     *</pre>
     */
    public <T> T readSystemPropertiesAs(JavaPropsSchema schema,
            JavaType valueType) throws IOException {
        return readPropertiesAs(System.getProperties(), schema, valueType);
    }

    /*
    /**********************************************************************
    /* Extended read methods, from Env variables
    /**********************************************************************
     */
    
    /**
     * Convenience method, functionally equivalent to:
     *<pre>
     *   readPropertiesAs(convertMapToProperties(System.getenv()), schema, valueType);
     *</pre>
     */
    public <T> T readEnvVariablesAs(JavaPropsSchema schema, 
            Class<T> valueType) throws IOException {
        return readPropertiesAs(_env(), schema, valueType);
    }

    /**
     * Convenience method, functionally equivalent to:
     *<pre>
     *   readPropertiesAs(convertMapToProperties(System.getenv()), schema, valueType);
     *</pre>
     */
    public <T> T readEnvVariablesAs(JavaPropsSchema schema,
            JavaType valueType) throws IOException {
        return readPropertiesAs(_env(), schema, valueType);
    }

    protected Properties _env() {
        Properties props = new Properties();
        props.putAll(System.getenv());
        return props;
    }

    /*
    /**********************************************************************
    /* Extended write methods
    /**********************************************************************
     */

    /**
     * Convenience method that "writes" given `value` as properties
     * in given {@link Map} object.
     */
    public void writeValue(Map<?,?> target, Object value) throws IOException
    {
        if (target == null) {
            throw new IllegalArgumentException("Can not pass `null` target");
        }
        SerializationContextExt prov = _serializationContext();
        try (JavaPropsGenerator g = tokenStreamFactory().createGenerator(prov, null, target)) {
            writeValue(g, value);
        }
    }

    /**
     * Convenience method that "writes" given `value` as properties
     * in given {@link Map} object.
     */
    public void writeValue(Map<?,?> target, Object value, JavaPropsSchema schema)
            throws IOException
    {
        if (target == null) {
            throw new IllegalArgumentException("Can not pass `null` target");
        }
        SerializationContextExt prov = _serializationContext();
        try (JavaPropsGenerator g = tokenStreamFactory().createGenerator(prov, schema, target)) {
            writeValue(g, value);
        }
    }

    /**
     * Convenience method that serializes given value but so that results are
     * stored in a newly constructed {@link Properties}. Functionally equivalent
     * to serializing in a File and reading contents into {@link Properties}.
     */
    public Properties writeValueAsProperties(Object value)
        throws IOException
    {
        final Properties props = new Properties();
        writeValue(props, value);
        return props;
    }

    /**
     * Convenience method that serializes given value but so that results are
     * stored in given {@link Properties} instance.
     */
    public Properties writeValueAsProperties(Object value, JavaPropsSchema schema)
        throws IOException
    {
        final Properties props = new Properties();
        writeValue(props, value, schema);
        return props;
    }

    /**
     * Convenience method that serializes given value but so that results are
     * stored in a newly constructed {@link Properties}. Functionally equivalent
     * to serializing in a File and reading contents into {@link Properties}.
     */
    public Map<String, String> writeValueAsMap(Object value) throws IOException
    {
        final Map<String, String> map = new LinkedHashMap<>();
        writeValue(map, value);
        return map;
    }

    /**
     * Convenience method that serializes given value but so that results are
     * stored in given {@link Properties} instance.
     */
    public Map<String, String> writeValueAsMap(Object value, JavaPropsSchema schema)
        throws IOException
    {
        final Map<String, String> map = new LinkedHashMap<>();
        writeValue(map, value, schema);
        return map;
    }

    /*
    /**********************************************************************
    /* Schema support methods?
    /**********************************************************************
     */

    // do we have any actually?

    /*
    /**********************************************************
    /* 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 JavaPropsMapper MAPPER = JavaPropsMapper.builder().build();

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