ProtoFeature.java

package net.morimekta.proto.jackson;

import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;

/**
 * Providence specific features or attributes used to configure
 * serialization further. These are technically 'attributes' on
 * the serialization config, but is used are simple feature
 * flags.
 */
public enum ProtoFeature {
    /**
     * Fail when encountering an enum value (from string) that is unknown.
     */
    FAIL_ON_UNKNOWN_ENUM(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true),
    /**
     * Fail when encountering an unknown field on a message.
     */
    FAIL_ON_UNKNOWN_FIELD(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false),
    /**
     * Fail when setting null values to fields.
     */
    FAIL_ON_NULL_VALUE(true),

    /**
     * Ignore unknown types on <code>google.protobuf.Any</code> when parsing unwrapped
     * messages.
     */
    IGNORE_UNKNOWN_ANY_TYPE(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true),

    /**
     * Allow using the <code>json.compact</code> compact notation for
     * messages. If set to true the messages will be encoded as an array
     * instead of an object.
     */
    WRITE_COMPACT_MESSAGES(false),
    /**
     * Allows using an unwrapped JSON in place of the binary content of an
     * Any struct.
     */
    WRITE_UNPACKED_ANY(true),
    /**
     * If set to true will write field names using the field ID.
     */
    WRITE_FIELD_AS_NUMBER(false),
    /**
     * If set to true will write enum values using the enum number value.
     */
    WRITE_ENUM_AS_NUMBER(SerializationFeature.WRITE_ENUMS_USING_INDEX),
    /**
     * Write google.proto.Timestamp values as ISO timestamp.
     */
    WRITE_TIMESTAMP_AS_ISO(true),
    /**
     * Write google.proto.Duration as string value.
     */
    WRITE_DURATION_AS_STRING(true),
    ;
    private final DeserializationFeature deserializationFeature;
    private final boolean                flipMeaningOfDeserializationFeature;
    private final SerializationFeature   serializationFeature;
    private final boolean                enabledByDefault;

    ProtoFeature(boolean enabledByDefault) {
        this.deserializationFeature = null;
        this.flipMeaningOfDeserializationFeature = false;
        this.serializationFeature = null;
        this.enabledByDefault = enabledByDefault;
    }

    ProtoFeature(DeserializationFeature deserializationFeature, boolean flip) {
        this.deserializationFeature = deserializationFeature;
        this.flipMeaningOfDeserializationFeature = flip;
        this.serializationFeature = null;
        this.enabledByDefault = flip != deserializationFeature.enabledByDefault();
    }

    ProtoFeature(SerializationFeature serializationFeature) {
        this.deserializationFeature = null;
        this.flipMeaningOfDeserializationFeature = false;
        this.serializationFeature = serializationFeature;
        this.enabledByDefault = serializationFeature.enabledByDefault();
    }

    /**
     * Enable features on mapper.
     *
     * @param mapper   Object mapper.
     * @param features Features to be enabled.
     * @return The mapper.
     */
    public static ObjectMapper enableFeatures(ObjectMapper mapper, ProtoFeature... features) {
        for (var feature : features) {
            feature.enable(mapper);
        }
        return mapper;
    }

    /**
     * Disable features on mapper.
     *
     * @param mapper   Object mapper.
     * @param features Features to disable.
     * @return The mapper.
     */
    public static ObjectMapper disableFeatures(ObjectMapper mapper, ProtoFeature... features) {
        for (var feature : features) {
            feature.disable(mapper);
        }
        return mapper;
    }

    /**
     * Enable feature on mapper.
     *
     * @param mapper Object mapper.
     * @return The mapper.
     */
    public ObjectMapper enable(ObjectMapper mapper) {
        if (serializationFeature != null) {
            mapper.enable(serializationFeature);
        } else if (deserializationFeature != null) {
            if (flipMeaningOfDeserializationFeature) {
                mapper.disable(deserializationFeature);
            } else {
                mapper.enable(deserializationFeature);
            }
        } else if (enabledByDefault) {
            mapper.setConfig(mapper.getSerializationConfig().withoutAttribute(this));
            mapper.setConfig(mapper.getDeserializationConfig().withoutAttribute(this));
        } else {
            mapper.setConfig(mapper.getSerializationConfig().withAttribute(this, true));
            mapper.setConfig(mapper.getDeserializationConfig().withAttribute(this, true));
        }
        return mapper;
    }

    /**
     * Disable feature on mapper.
     *
     * @param mapper Object mapper.
     * @return The mapper.
     */
    public ObjectMapper disable(ObjectMapper mapper) {
        if (serializationFeature != null) {
            mapper.disable(serializationFeature);
        } else if (deserializationFeature != null) {
            if (flipMeaningOfDeserializationFeature) {
                mapper.enable(deserializationFeature);
            } else {
                mapper.disable(deserializationFeature);
            }
        } else if (enabledByDefault) {
            mapper.setConfig(mapper.getSerializationConfig().withAttribute(this, false));
            mapper.setConfig(mapper.getDeserializationConfig().withAttribute(this, false));
        } else {
            mapper.setConfig(mapper.getSerializationConfig().withoutAttribute(this));
            mapper.setConfig(mapper.getDeserializationConfig().withoutAttribute(this));
        }
        return mapper;
    }

    /**
     * Check if feature is enabled for serializer.
     *
     * @param sp The serializer provider.
     * @return If feature is enabled.
     */
    public boolean isEnabled(SerializerProvider sp) {
        if (serializationFeature != null) {
            return sp.isEnabled(serializationFeature);
        }
        Object o = sp.getAttribute(this);
        if (o == null) {
            return enabledByDefault;
        }
        return Boolean.TRUE.equals(o);
    }

    /**
     * Check if feature is enabled for deserializer.
     *
     * @param dc The deserializer config.
     * @return If feature is enabled.
     */
    public boolean isEnabled(DeserializationConfig dc) {
        if (deserializationFeature != null) {
            if (flipMeaningOfDeserializationFeature) {
                return !dc.isEnabled(deserializationFeature);
            } else {
                return dc.isEnabled(deserializationFeature);
            }
        }
        Object o = dc.getAttributes().getAttribute(this);
        if (o == null) {
            return enabledByDefault;
        }
        return Boolean.TRUE.equals(o);
    }
}