VisitorFormatWrapperImpl.java

package com.fasterxml.jackson.dataformat.avro.schema;

import java.time.temporal.Temporal;

import com.fasterxml.jackson.core.JsonGenerator;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.jsonFormatVisitors.*;

import com.fasterxml.jackson.dataformat.avro.AvroSchema;

import org.apache.avro.Schema;

public class VisitorFormatWrapperImpl
    implements JsonFormatVisitorWrapper
{
    protected SerializerProvider _provider;

    protected final DefinedSchemas _schemas;

    /**
     * @since 2.13
     */
    protected boolean _logicalTypesEnabled = false;

    /**
     * @since 2.18
     */
    protected boolean _writeEnumAsString = false;

    /**
     * Visitor used for resolving actual Schema, if structured type
     * (or one with complex configuration)
     */
    protected SchemaBuilder _builder;

    /**
     * Schema for simple types that do not need a visitor.
     */
    protected Schema _valueSchema;

    /*
    /**********************************************************************
    /* Construction
    /**********************************************************************
     */

    public VisitorFormatWrapperImpl(DefinedSchemas schemas, SerializerProvider p) {
        _schemas = schemas;
        _provider = p;
    }


    protected VisitorFormatWrapperImpl(VisitorFormatWrapperImpl src) {
        this._schemas = src._schemas;
        this._provider = src._provider;
        this._logicalTypesEnabled = src._logicalTypesEnabled;
    }

    /**
     * Creates new {@link VisitorFormatWrapperImpl} instance with shared schemas,
     * serialization provider and same configuration.
     *
     * @return new instance with shared properties and configuration.
     */
    protected VisitorFormatWrapperImpl createChildWrapper() {
        return new VisitorFormatWrapperImpl(this);
    }

    @Override
    public SerializerProvider getProvider() {
        return _provider;
    }

    @Override
    public void setProvider(SerializerProvider provider) {
        _schemas.setProvider(provider);
        _provider = provider;
    }

    protected DefinedSchemas getSchemas() {
        return _schemas;
    }

    /*
    /**********************************************************************
    /* Extended API
    /**********************************************************************
     */

    public Schema getAvroSchema() {
        if (_valueSchema != null) {
            return _valueSchema;
        }
        if (_builder == null) {
            throw new IllegalStateException("No visit methods called on "+getClass().getName()
                    +": no schema generated");
        }
        return _builder.builtAvroSchema();
    }

    /**
     * Enables Avro schema with Logical Types generation.
     *
     * @since 2.13
     */
    public VisitorFormatWrapperImpl enableLogicalTypes() {
        _logicalTypesEnabled = true;
        return this;
    }

    /**
     * Disables Avro schema with Logical Types generation.
     *
     * @since 2.13
     */
    public VisitorFormatWrapperImpl disableLogicalTypes() {
        _logicalTypesEnabled = false;
        return this;
    }

    public boolean isLogicalTypesEnabled() {
        return _logicalTypesEnabled;
    }

    /**
     * Enable Java enum to Avro string mapping.
     *
     * @since 2.18
     */
    public VisitorFormatWrapperImpl enableWriteEnumAsString() {
    	_writeEnumAsString = true;
        return this;
    }

    /**
     * Disable Java enum to Avro string mapping.
     *
     * @since 2.18
     */
    public VisitorFormatWrapperImpl disableWriteEnumAsString() {
        _writeEnumAsString = false;
        return this;
    }

    // @since 2.18
    public boolean isWriteEnumAsStringEnabled() {
        return _writeEnumAsString;
    }

    /*
    /**********************************************************************
    /* Callbacks
    /**********************************************************************
     */

    public void expectAvroFormat(AvroSchema schema) {
        _valueSchema = schema.getAvroSchema();
    }

    @Override
    public JsonObjectFormatVisitor expectObjectFormat(JavaType type) {

        Schema s = _schemas.findSchema(type);
        if (s != null) {
            _valueSchema = s;
            return null;
        }
        RecordVisitor v = new RecordVisitor(_provider, type, this);
        _builder = v;
        return v;
    }

    @Override
    public JsonMapFormatVisitor expectMapFormat(JavaType mapType) {
        MapVisitor v = new MapVisitor(_provider, mapType, this);
        _builder = v;
        return v;
    }

    @Override
    public JsonArrayFormatVisitor expectArrayFormat(final JavaType convertedType) {
        // 22-Mar-2016, tatu: Actually we can detect byte[] quite easily here can't we?
        if (convertedType.isArrayType()) {
            JavaType vt = convertedType.getContentType();
            if (vt.hasRawClass(Byte.TYPE)) {
                _builder = () -> AvroSchemaHelper.typedSchema(Schema.Type.BYTES, convertedType);
                return null;
            }
        }
        ArrayVisitor v = new ArrayVisitor(_provider, convertedType, this);
        _builder = v;
        return v;
    }

    @Override
    public JsonStringFormatVisitor expectStringFormat(JavaType type)
    {
        // may be getting ref to Enum type:
        Schema s = _schemas.findSchema(type);
        if (s != null) {
            _valueSchema = s;
            return null;
        }

        // 06-Jun-2024: [dataformats-binary#494] Enums may be exposed either
        //   as native Avro Enums, or as Avro Strings:
        if (type.isEnumType() && !isWriteEnumAsStringEnabled()) {
            EnumVisitor v = new EnumVisitor(_provider, _schemas, type);
            _builder = v;
            return v;
        }

        StringVisitor v = new StringVisitor(_provider, type);
        _builder = v;
        return v;
    }

    @Override
    public JsonNumberFormatVisitor expectNumberFormat(JavaType convertedType) {
        DoubleVisitor v = new DoubleVisitor(convertedType);
        _builder = v;
        return v;
    }

    @Override
    public JsonIntegerFormatVisitor expectIntegerFormat(JavaType type) {
        // possible we might be getting Enum type, using indexes:
        // may be getting ref to Enum type:
        Schema s = _schemas.findSchema(type);
        if (s != null) {
            _valueSchema = s;
            return null;
        }

        if (isLogicalTypesEnabled() && _isDateTimeType(type)) {
            DateTimeVisitor v = new DateTimeVisitor(type);
            _builder = v;
            return v;
        }

        IntegerVisitor v = new IntegerVisitor(type);
        _builder = v;
        return v;
    }

    @Override
    public JsonBooleanFormatVisitor expectBooleanFormat(JavaType convertedType) {
        _valueSchema = Schema.create(Schema.Type.BOOLEAN);
        // We don't really need anything from there so:
        return null;
    }

    @Override
    public JsonNullFormatVisitor expectNullFormat(JavaType convertedType) {
        _valueSchema = Schema.create(Schema.Type.NULL);
        // no info on null type that we care about so:
        return null;
    }

    @Override
    public JsonAnyFormatVisitor expectAnyFormat(JavaType convertedType) throws JsonMappingException {
        // could theoretically create union of all possible types but...
        final String msg = "\"Any\" type (usually for `java.lang.Object`) not supported: `expectAnyFormat` called with type "+convertedType;
        throw InvalidDefinitionException.from((JsonGenerator) null, msg, convertedType);
    }

    /*
    /**********************************************************************
    /* Internal methods
    /**********************************************************************
     */

    protected <T> T _throwUnsupported() {
        return _throwUnsupported("Format variation not supported");
    }

    protected <T> T _throwUnsupported(String msg) {
        throw new UnsupportedOperationException(msg);
    }

    private boolean _isDateTimeType(JavaType type) {
        return Temporal.class.isAssignableFrom(type.getRawClass());
    }
}