content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *    http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License 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.
   */
  
  <@pp.dropOutputFile />
  <@pp.changeOutputFile name="/org/apache/arrow/vector/complex/impl/PromotableWriter.java" />
  
  <#include "/@includes/license.ftl" />
  
  package org.apache.arrow.vector.complex.impl;
  
  import java.util.Locale;
  <#include "/@includes/vv_imports.ftl" />
  
  /**
   * This FieldWriter implementation delegates all FieldWriter API calls to an inner FieldWriter. This
   * inner field writer can start as a specific type, and this class will promote the writer to a
   * UnionWriter if a call is made that the specifically typed writer cannot handle. A new UnionVector
   * is created, wrapping the original vector, and replaces the original vector in the parent vector,
   * which can be either an AbstractStructVector or a ListVector.
   *
   * <p>The writer used can either be for single elements (struct) or lists.
   */
  public class PromotableWriter extends AbstractPromotableFieldWriter {
  
    protected final AbstractStructVector parentContainer;
    protected final ListVector listVector;
    protected final ListViewVector listViewVector;
    protected final FixedSizeListVector fixedListVector;
    protected final LargeListVector largeListVector;
    protected final LargeListViewVector largeListViewVector;
    protected final NullableStructWriterFactory nullableStructWriterFactory;
    protected int position;
    protected static final int MAX_DECIMAL_PRECISION = 38;
    protected static final int MAX_DECIMAL256_PRECISION = 76;
  
    protected enum State {
      UNTYPED,
      SINGLE,
      UNION
    }
  
    protected MinorType type;
    protected ValueVector vector;
    protected UnionVector unionVector;
    protected State state;
    protected FieldWriter writer;
  
    /**
     * Constructs a new instance.
     *
     * @param v The vector to write.
     * @param parentContainer The parent container for the vector.
     */
    public PromotableWriter(ValueVector v, AbstractStructVector parentContainer) {
      this(v, parentContainer, NullableStructWriterFactory.getNullableStructWriterFactoryInstance());
    }
  
    /**
     * Constructs a new instance.
     *
     * @param v The vector to initialize the writer with.
     * @param parentContainer The parent container for the vector.
     * @param nullableStructWriterFactory The factory to create the delegate writer.
     */
    public PromotableWriter(
        ValueVector v,
        AbstractStructVector parentContainer,
        NullableStructWriterFactory nullableStructWriterFactory) {
      this.parentContainer = parentContainer;
      this.listVector = null;
      this.listViewVector = null;
      this.fixedListVector = null;
      this.largeListVector = null;
      this.largeListViewVector = null;
      this.nullableStructWriterFactory = nullableStructWriterFactory;
      init(v);
    }
  
    /**
     * Constructs a new instance.
     *
     * @param v The vector to initialize the writer with.
     * @param listVector The vector that serves as a parent of v.
     */
    public PromotableWriter(ValueVector v, ListVector listVector) {
      this(v, listVector, NullableStructWriterFactory.getNullableStructWriterFactoryInstance());
    }
  
    /**
     * Constructs a new instance.
     *
     * @param v The vector to initialize the writer with.
     * @param fixedListVector The vector that serves as a parent of v.
     */
    public PromotableWriter(ValueVector v, FixedSizeListVector fixedListVector) {
      this(v, fixedListVector, NullableStructWriterFactory.getNullableStructWriterFactoryInstance());
    }
  
    /**
     * Constructs a new instance.
     *
     * @param v The vector to initialize the writer with.
     * @param largeListVector The vector that serves as a parent of v.
     */
    public PromotableWriter(ValueVector v, LargeListVector largeListVector) {
      this(v, largeListVector, NullableStructWriterFactory.getNullableStructWriterFactoryInstance());
    }
  
    /**
     * Constructs a new instance.
     *
     * @param v The vector to initialize the writer with.
     * @param listViewVector The vector that serves as a parent of v.
     */
    public PromotableWriter(ValueVector v, ListViewVector listViewVector) {
      this(v, listViewVector, NullableStructWriterFactory.getNullableStructWriterFactoryInstance());
    }
  
    /**
     * Constructs a new instance.
     *
     * @param v The vector to initialize the writer with.
     * @param largeListViewVector The vector that serves as a parent of v.
     */
    public PromotableWriter(ValueVector v, LargeListViewVector largeListViewVector) {
      this(v, largeListViewVector, NullableStructWriterFactory.getNullableStructWriterFactoryInstance());
    }
  
    /**
     * Constructs a new instance.
     *
     * @param v The vector to initialize the writer with.
     * @param listVector The vector that serves as a parent of v.
     * @param nullableStructWriterFactory The factory to create the delegate writer.
     */
    public PromotableWriter(
        ValueVector v,
        ListVector listVector,
        NullableStructWriterFactory nullableStructWriterFactory) {
      this.listVector = listVector;
      this.listViewVector = null;
      this.parentContainer = null;
      this.fixedListVector = null;
      this.largeListVector = null;
      this.largeListViewVector = null;
      this.nullableStructWriterFactory = nullableStructWriterFactory;
      init(v);
    }
  
    /**
     * Constructs a new instance.
     *
     * @param v The vector to initialize the writer with.
     * @param listViewVector The vector that serves as a parent of v.
     * @param nullableStructWriterFactory The factory to create the delegate writer.
     */
    public PromotableWriter(
        ValueVector v,
        ListViewVector listViewVector,
        NullableStructWriterFactory nullableStructWriterFactory) {
      this.listViewVector = listViewVector;
      this.listVector = null;
      this.parentContainer = null;
      this.fixedListVector = null;
      this.largeListVector = null;
      this.largeListViewVector = null;
      this.nullableStructWriterFactory = nullableStructWriterFactory;
      init(v);
    }
  
    /**
     * Constructs a new instance.
     *
     * @param v The vector to initialize the writer with.
     * @param fixedListVector The vector that serves as a parent of v.
     * @param nullableStructWriterFactory The factory to create the delegate writer.
     */
    public PromotableWriter(
        ValueVector v,
        FixedSizeListVector fixedListVector,
        NullableStructWriterFactory nullableStructWriterFactory) {
      this.fixedListVector = fixedListVector;
      this.parentContainer = null;
      this.listVector = null;
      this.listViewVector = null;
      this.largeListVector = null;
      this.largeListViewVector = null;
      this.nullableStructWriterFactory = nullableStructWriterFactory;
      init(v);
    }
  
    /**
     * Constructs a new instance.
     *
     * @param v The vector to initialize the writer with.
     * @param largeListVector The vector that serves as a parent of v.
     * @param nullableStructWriterFactory The factory to create the delegate writer.
     */
    public PromotableWriter(
        ValueVector v,
        LargeListVector largeListVector,
        NullableStructWriterFactory nullableStructWriterFactory) {
      this.largeListVector = largeListVector;
      this.fixedListVector = null;
      this.parentContainer = null;
      this.listVector = null;
      this.listViewVector = null;
      this.largeListViewVector = null;
      this.nullableStructWriterFactory = nullableStructWriterFactory;
      init(v);
    }
  
    /**
     * Constructs a new instance.
     *
     * @param v The vector to initialize the writer with.
     * @param largeListViewVector The vector that serves as a parent of v.
     * @param nullableStructWriterFactory The factory to create the delegate writer.
     */
    public PromotableWriter(
        ValueVector v,
        LargeListViewVector largeListViewVector,
        NullableStructWriterFactory nullableStructWriterFactory) {
      this.largeListViewVector = largeListViewVector;
      this.fixedListVector = null;
      this.parentContainer = null;
      this.listVector = null;
      this.listViewVector = null;
      this.largeListVector = null;
      this.nullableStructWriterFactory = nullableStructWriterFactory;
      init(v);
    }
  
    private void init(ValueVector v) {
      if (v instanceof UnionVector) {
        state = State.UNION;
        unionVector = (UnionVector) v;
        writer = new UnionWriter(unionVector, nullableStructWriterFactory);
      } else if (v instanceof NullVector) {
        state = State.UNTYPED;
      } else {
        setWriter(v);
      }
    }
  
    @Override
    public void setAddVectorAsNullable(boolean nullable) {
      super.setAddVectorAsNullable(nullable);
      if (writer instanceof AbstractFieldWriter) {
        ((AbstractFieldWriter) writer).setAddVectorAsNullable(nullable);
      }
    }
  
    protected void setWriter(ValueVector v) {
      state = State.SINGLE;
      vector = v;
      type = v.getMinorType();
      switch (type) {
        case STRUCT:
          writer = nullableStructWriterFactory.build((StructVector) vector);
          break;
        case LIST:
          writer = new UnionListWriter((ListVector) vector, nullableStructWriterFactory);
          break;
        case LISTVIEW:
          writer = new UnionListViewWriter((ListViewVector) vector, nullableStructWriterFactory);
          break;
        case MAP:
          writer = new UnionMapWriter((MapVector) vector);
          break;
        case UNION:
          writer = new UnionWriter((UnionVector) vector, nullableStructWriterFactory);
          break;
        case EXTENSIONTYPE:
          writer = new UnionExtensionWriter((ExtensionTypeVector) vector);
          break;
        default:
          writer = type.getNewFieldWriter(vector);
          break;
      }
    }
  
    @Override
    public void writeNull() {
      FieldWriter w = getWriter();
      if (w != null) {
        w.writeNull();
      }
      setPosition(idx() + 1);
    }
  
    @Override
    public void setPosition(int index) {
      super.setPosition(index);
      FieldWriter w = getWriter();
      if (w == null) {
        position = index;
      } else {
        w.setPosition(index);
      }
    }
  
    protected boolean requiresArrowType(MinorType type) {
      return type == MinorType.DECIMAL
          || type == MinorType.MAP
          || type == MinorType.DURATION
          || type == MinorType.FIXEDSIZEBINARY
          || type == MinorType.EXTENSIONTYPE
          || (type.name().startsWith("TIMESTAMP") && type.name().endsWith("TZ"));
    }
  
    @Override
    protected FieldWriter getWriter(MinorType type, ArrowType arrowType) {
      if (state == State.UNION) {
        if (requiresArrowType(type)) {
          ((UnionWriter) writer).getWriter(type, arrowType);
        } else {
          ((UnionWriter) writer).getWriter(type);
        }
      } else if (state == State.UNTYPED) {
        if (type == null) {
          // ???
          return null;
        }
        if (arrowType == null) {
          arrowType = type.getType();
        }
        FieldType fieldType = new FieldType(addVectorAsNullable, arrowType, null, null);
        ValueVector v;
        if (listVector != null) {
          v = listVector.addOrGetVector(fieldType).getVector();
        } else if (fixedListVector != null) {
          v = fixedListVector.addOrGetVector(fieldType).getVector();
        } else if (listViewVector != null) {
          v = listViewVector.addOrGetVector(fieldType).getVector();
        } else {
          v = largeListVector.addOrGetVector(fieldType).getVector();
        }
        v.allocateNew();
        setWriter(v);
        writer.setPosition(position);
      } else if (type != this.type) {
        promoteToUnion();
        if (requiresArrowType(type)) {
          ((UnionWriter) writer).getWriter(type, arrowType);
        } else {
          ((UnionWriter) writer).getWriter(type);
        }
      }
      return writer;
    }
  
    @Override
    public boolean isEmptyStruct() {
      return writer.isEmptyStruct();
    }
  
    @Override
    protected FieldWriter getWriter() {
      return writer;
    }
  
    protected FieldWriter promoteToUnion() {
      String name = vector.getField().getName();
      TransferPair tp =
          vector.getTransferPair(
              vector.getMinorType().name().toLowerCase(Locale.ROOT), vector.getAllocator());
      tp.transfer();
      if (parentContainer != null) {
        // TODO allow dictionaries in complex types
        unionVector = parentContainer.addOrGetUnion(name);
        unionVector.allocateNew();
      } else if (listVector != null) {
        unionVector = listVector.promoteToUnion();
      } else if (fixedListVector != null) {
        unionVector = fixedListVector.promoteToUnion();
      } else if (largeListVector != null) {
        unionVector = largeListVector.promoteToUnion();
      } else if (listViewVector != null) {
        unionVector = listViewVector.promoteToUnion();
      }
      unionVector.addVector((FieldVector) tp.getTo());
      writer = new UnionWriter(unionVector, nullableStructWriterFactory);
      writer.setPosition(idx());
      for (int i = 0; i <= idx(); i++) {
        unionVector.setType(i, vector.getMinorType());
      }
      vector = null;
      state = State.UNION;
      return writer;
    }
  
    @Override
    public void write(DecimalHolder holder) {
      getWriter(
          MinorType.DECIMAL,
          new ArrowType.Decimal(MAX_DECIMAL_PRECISION, holder.scale, /*bitWidth=*/ 128))
          .write(holder);
    }
  
    @Override
    public void writeDecimal(long start, ArrowBuf buffer, ArrowType arrowType) {
      getWriter(
          MinorType.DECIMAL,
          new ArrowType.Decimal(
              MAX_DECIMAL_PRECISION,
              ((ArrowType.Decimal) arrowType).getScale(),
              /*bitWidth=*/ 128))
          .writeDecimal(start, buffer, arrowType);
    }
  
    @Override
    public void writeDecimal(BigDecimal value) {
      getWriter(
          MinorType.DECIMAL,
          new ArrowType.Decimal(MAX_DECIMAL_PRECISION, value.scale(), /*bitWidth=*/ 128))
          .writeDecimal(value);
    }
  
    @Override
    public void writeBigEndianBytesToDecimal(byte[] value, ArrowType arrowType) {
      getWriter(
          MinorType.DECIMAL,
          new ArrowType.Decimal(
              MAX_DECIMAL_PRECISION,
              ((ArrowType.Decimal) arrowType).getScale(),
              /*bitWidth=*/ 128))
          .writeBigEndianBytesToDecimal(value, arrowType);
    }
  
    @Override
    public void write(Decimal256Holder holder) {
      getWriter(
          MinorType.DECIMAL256,
          new ArrowType.Decimal(MAX_DECIMAL256_PRECISION, holder.scale, /*bitWidth=*/ 256))
          .write(holder);
    }
  
    @Override
    public void writeDecimal256(long start, ArrowBuf buffer, ArrowType arrowType) {
      getWriter(
          MinorType.DECIMAL256,
          new ArrowType.Decimal(
              MAX_DECIMAL256_PRECISION,
              ((ArrowType.Decimal) arrowType).getScale(),
              /*bitWidth=*/ 256))
          .writeDecimal256(start, buffer, arrowType);
    }
  
    @Override
    public void writeDecimal256(BigDecimal value) {
      getWriter(
          MinorType.DECIMAL256,
          new ArrowType.Decimal(MAX_DECIMAL256_PRECISION, value.scale(), /*bitWidth=*/ 256))
          .writeDecimal256(value);
    }
  
    @Override
    public void writeBigEndianBytesToDecimal256(byte[] value, ArrowType arrowType) {
      getWriter(
          MinorType.DECIMAL256,
          new ArrowType.Decimal(
              MAX_DECIMAL256_PRECISION,
              ((ArrowType.Decimal) arrowType).getScale(),
              /*bitWidth=*/ 256))
          .writeBigEndianBytesToDecimal256(value, arrowType);
    }
  
    @Override
    public void writeVarBinary(byte[] value) {
      getWriter(MinorType.VARBINARY).writeVarBinary(value);
    }
  
    @Override
    public void writeVarBinary(byte[] value, int offset, int length) {
      getWriter(MinorType.VARBINARY).writeVarBinary(value, offset, length);
    }
  
    @Override
    public void writeVarBinary(ByteBuffer value) {
      getWriter(MinorType.VARBINARY).writeVarBinary(value);
    }
  
    @Override
    public void writeVarBinary(ByteBuffer value, int offset, int length) {
      getWriter(MinorType.VARBINARY).writeVarBinary(value, offset, length);
    }
  
    @Override
    public void writeLargeVarBinary(byte[] value) {
      getWriter(MinorType.LARGEVARBINARY).writeLargeVarBinary(value);
    }
  
    @Override
    public void writeLargeVarBinary(byte[] value, int offset, int length) {
      getWriter(MinorType.LARGEVARBINARY).writeLargeVarBinary(value, offset, length);
    }
  
    @Override
    public void writeLargeVarBinary(ByteBuffer value) {
      getWriter(MinorType.LARGEVARBINARY).writeLargeVarBinary(value);
    }
  
    @Override
    public void writeLargeVarBinary(ByteBuffer value, int offset, int length) {
      getWriter(MinorType.LARGEVARBINARY).writeLargeVarBinary(value, offset, length);
    }
  
    @Override
    public void writeVarChar(Text value) {
      getWriter(MinorType.VARCHAR).writeVarChar(value);
    }
  
    @Override
    public void writeVarChar(String value) {
      getWriter(MinorType.VARCHAR).writeVarChar(value);
    }
  
    @Override
    public void writeLargeVarChar(Text value) {
      getWriter(MinorType.LARGEVARCHAR).writeLargeVarChar(value);
    }
  
    @Override
    public void writeLargeVarChar(String value) {
      getWriter(MinorType.LARGEVARCHAR).writeLargeVarChar(value);
    }
  
    @Override
    public void writeExtension(Object value) {
      getWriter(MinorType.EXTENSIONTYPE).writeExtension(value);
    }
  
    @Override
    public void addExtensionTypeWriterFactory(ExtensionTypeWriterFactory factory) {
      getWriter(MinorType.EXTENSIONTYPE).addExtensionTypeWriterFactory(factory);
    }
  
    public void addExtensionTypeWriterFactory(ExtensionTypeWriterFactory factory, ArrowType arrowType) {
      getWriter(MinorType.EXTENSIONTYPE, arrowType).addExtensionTypeWriterFactory(factory);
    }
  
    @Override
    public void allocate() {
      getWriter().allocate();
    }
  
    @Override
    public void clear() {
      getWriter().clear();
    }
  
    @Override
    public Field getField() {
      return getWriter().getField();
    }
  
    @Override
    public int getValueCapacity() {
      return getWriter().getValueCapacity();
    }
  
    @Override
    public void close() throws Exception {
      getWriter().close();
    }
  
    /**
     * Convert the writer to a PromotableViewWriter.
     *
     * @return The writer as a PromotableViewWriter.
     */
    public PromotableViewWriter toViewWriter() {
      PromotableViewWriter promotableViewWriter = new PromotableViewWriter(unionVector, parentContainer, nullableStructWriterFactory);
      promotableViewWriter.position = position;
      promotableViewWriter.writer = writer;
      promotableViewWriter.state = state;
      promotableViewWriter.unionVector = unionVector;
      promotableViewWriter.type = MinorType.LISTVIEW;
      return promotableViewWriter;
    }
  }
  