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.
   */
  package org.apache.arrow.vector;
  
  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
  import static org.junit.jupiter.api.Assertions.assertEquals;
  import static org.junit.jupiter.api.Assertions.assertFalse;
  import static org.junit.jupiter.api.Assertions.assertNull;
  import static org.junit.jupiter.api.Assertions.assertThrows;
  import static org.junit.jupiter.api.Assertions.assertTrue;
  
  import java.math.BigDecimal;
  import java.nio.ByteBuffer;
  import java.nio.charset.StandardCharsets;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import org.apache.arrow.memory.BufferAllocator;
  import org.apache.arrow.vector.complex.FixedSizeListVector;
  import org.apache.arrow.vector.complex.ListVector;
  import org.apache.arrow.vector.complex.impl.UnionFixedSizeListReader;
  import org.apache.arrow.vector.complex.impl.UnionFixedSizeListWriter;
  import org.apache.arrow.vector.complex.impl.UnionListReader;
  import org.apache.arrow.vector.complex.reader.FieldReader;
  import org.apache.arrow.vector.types.Types.MinorType;
  import org.apache.arrow.vector.types.pojo.ArrowType;
  import org.apache.arrow.vector.types.pojo.FieldType;
  import org.apache.arrow.vector.util.Text;
  import org.apache.arrow.vector.util.TransferPair;
  import org.junit.jupiter.api.AfterEach;
  import org.junit.jupiter.api.BeforeEach;
  import org.junit.jupiter.api.Test;
  
  public class TestFixedSizeListVector {
  
    private BufferAllocator allocator;
  
    @BeforeEach
    public void init() {
      allocator = new DirtyRootAllocator(Long.MAX_VALUE, (byte) 100);
    }
  
    @AfterEach
    public void terminate() throws Exception {
      allocator.close();
    }
  
    @Test
    public void testIntType() {
      try (FixedSizeListVector vector = FixedSizeListVector.empty("list", /* size= */ 2, allocator)) {
        IntVector nested =
            (IntVector)
                vector.addOrGetVector(FieldType.nullable(MinorType.INT.getType())).getVector();
        vector.allocateNew();
  
        for (int i = 0; i < 10; i++) {
          vector.setNotNull(i);
          nested.set(i * 2, i);
          nested.set(i * 2 + 1, i + 10);
        }
        vector.setValueCount(10);
  
        UnionFixedSizeListReader reader = vector.getReader();
        for (int i = 0; i < 10; i++) {
          reader.setPosition(i);
          assertTrue(reader.isSet());
          assertTrue(reader.next());
          assertEquals(i, reader.reader().readInteger().intValue());
          assertTrue(reader.next());
          assertEquals(i + 10, reader.reader().readInteger().intValue());
          assertFalse(reader.next());
          assertEquals(Arrays.asList(i, i + 10), reader.readObject());
        }
      }
    }
  
    @Test
    public void testFloatTypeNullable() {
      try (FixedSizeListVector vector = FixedSizeListVector.empty("list", /* size= */ 2, allocator)) {
        Float4Vector nested =
            (Float4Vector)
                vector.addOrGetVector(FieldType.nullable(MinorType.FLOAT4.getType())).getVector();
        vector.allocateNew();
  
        for (int i = 0; i < 10; i++) {
          if (i % 2 == 0) {
            vector.setNotNull(i);
            nested.set(i * 2, i + 0.1f);
            nested.set(i * 2 + 1, i + 10.1f);
          }
        }
        vector.setValueCount(10);
  
        UnionFixedSizeListReader reader = vector.getReader();
        for (int i = 0; i < 10; i++) {
          reader.setPosition(i);
          if (i % 2 == 0) {
            assertTrue(reader.isSet());
            assertTrue(reader.next());
            assertEquals(i + 0.1f, reader.reader().readFloat(), 0.00001);
            assertTrue(reader.next());
            assertEquals(i + 10.1f, reader.reader().readFloat(), 0.00001);
            assertFalse(reader.next());
            assertEquals(Arrays.asList(i + 0.1f, i + 10.1f), reader.readObject());
          } else {
            assertFalse(reader.isSet());
            assertNull(reader.readObject());
          }
        }
      }
    }
  
    @Test
    public void testNestedInList() {
      try (ListVector vector = ListVector.empty("list", allocator)) {
        FixedSizeListVector tuples =
            (FixedSizeListVector)
                vector.addOrGetVector(FieldType.nullable(new ArrowType.FixedSizeList(2))).getVector();
        IntVector innerVector =
            (IntVector)
                tuples.addOrGetVector(FieldType.nullable(MinorType.INT.getType())).getVector();
        vector.allocateNew();
  
        for (int i = 0; i < 10; i++) {
          if (i % 2 == 0) {
            int position = vector.startNewValue(i);
            for (int j = 0; j < i % 7; j++) {
              tuples.setNotNull(position + j);
              innerVector.set((position + j) * 2, j);
              innerVector.set((position + j) * 2 + 1, j + 1);
            }
            vector.endValue(i, i % 7);
          }
        }
        vector.setValueCount(10);
  
        UnionListReader reader = vector.getReader();
        for (int i = 0; i < 10; i++) {
          reader.setPosition(i);
          if (i % 2 == 0) {
            for (int j = 0; j < i % 7; j++) {
              assertTrue(reader.next());
              FieldReader innerListReader = reader.reader();
              for (int k = 0; k < 2; k++) {
                assertTrue(innerListReader.next());
                assertEquals(k + j, innerListReader.reader().readInteger().intValue());
              }
              assertFalse(innerListReader.next());
            }
            assertFalse(reader.next());
          } else {
            assertFalse(reader.isSet());
            assertNull(reader.readObject());
          }
        }
      }
    }
  
    @Test
    public void testTransferPair() {
      try (FixedSizeListVector from =
              new FixedSizeListVector(
                  "from",
                  allocator,
                  new FieldType(true, new ArrowType.FixedSizeList(2), null),
                  null);
          FixedSizeListVector to =
              new FixedSizeListVector(
                  "to", allocator, new FieldType(true, new ArrowType.FixedSizeList(2), null), null)) {
        Float4Vector nested =
            (Float4Vector)
                from.addOrGetVector(FieldType.nullable(MinorType.FLOAT4.getType())).getVector();
        from.allocateNew();
  
        for (int i = 0; i < 10; i++) {
          if (i % 2 == 0) {
            from.setNotNull(i);
            nested.set(i * 2, i + 0.1f);
            nested.set(i * 2 + 1, i + 10.1f);
          }
        }
        from.setValueCount(10);
  
        TransferPair pair = from.makeTransferPair(to);
  
        pair.copyValueSafe(0, 1);
        pair.copyValueSafe(2, 2);
        to.copyFromSafe(4, 3, from);
  
        to.setValueCount(10);
  
        UnionFixedSizeListReader reader = to.getReader();
  
        reader.setPosition(0);
        assertFalse(reader.isSet());
        assertNull(reader.readObject());
  
        reader.setPosition(1);
        assertTrue(reader.isSet());
        assertTrue(reader.next());
        assertEquals(0.1f, reader.reader().readFloat(), 0.00001);
        assertTrue(reader.next());
        assertEquals(10.1f, reader.reader().readFloat(), 0.00001);
        assertFalse(reader.next());
        assertEquals(Arrays.asList(0.1f, 10.1f), reader.readObject());
  
        reader.setPosition(2);
        assertTrue(reader.isSet());
        assertTrue(reader.next());
        assertEquals(2.1f, reader.reader().readFloat(), 0.00001);
        assertTrue(reader.next());
        assertEquals(12.1f, reader.reader().readFloat(), 0.00001);
        assertFalse(reader.next());
        assertEquals(Arrays.asList(2.1f, 12.1f), reader.readObject());
  
        reader.setPosition(3);
        assertTrue(reader.isSet());
        assertTrue(reader.next());
        assertEquals(4.1f, reader.reader().readFloat(), 0.00001);
        assertTrue(reader.next());
        assertEquals(14.1f, reader.reader().readFloat(), 0.00001);
        assertFalse(reader.next());
        assertEquals(Arrays.asList(4.1f, 14.1f), reader.readObject());
  
        for (int i = 4; i < 10; i++) {
          reader.setPosition(i);
          assertFalse(reader.isSet());
          assertNull(reader.readObject());
        }
      }
    }
  
    @Test
    public void testTransferEmptyVector() throws Exception {
      // #43320
      try (FixedSizeListVector src =
              new FixedSizeListVector(
                  "src", allocator, FieldType.nullable(new ArrowType.FixedSizeList(2)), null);
          FixedSizeListVector dest =
              new FixedSizeListVector(
                  "dest", allocator, FieldType.nullable(new ArrowType.FixedSizeList(2)), null)) {
        src.makeTransferPair(dest).transfer();
  
        IntVector els =
            (IntVector) dest.addOrGetVector(FieldType.nullable(MinorType.INT.getType())).getVector();
  
        dest.allocateNew();
        dest.startNewValue(0);
        els.setSafe(0, 1);
        els.setSafe(1, 2);
        dest.setValueCount(1);
  
        List<Integer> expected = new ArrayList<>(2);
        expected.add(1);
        expected.add(2);
  
        assertEquals(expected, dest.getObject(0));
      }
    }
  
    @Test
    public void testConsistentChildName() throws Exception {
      try (FixedSizeListVector listVector =
          FixedSizeListVector.empty("sourceVector", /* size= */ 2, allocator)) {
        String emptyListStr = listVector.getField().toString();
        assertTrue(emptyListStr.contains(ListVector.DATA_VECTOR_NAME));
  
        listVector.addOrGetVector(FieldType.nullable(MinorType.INT.getType()));
        String emptyVectorStr = listVector.getField().toString();
        assertTrue(emptyVectorStr.contains(ListVector.DATA_VECTOR_NAME));
      }
    }
  
    @Test
    public void testUnionFixedSizeListWriterWithNulls() throws Exception {
      /* Write to a decimal list vector
       * each list of size 3 and having its data values alternating between null and a non-null.
       * Read and verify
       */
      try (final FixedSizeListVector vector =
          FixedSizeListVector.empty("vector", /* size= */ 3, allocator)) {
  
        UnionFixedSizeListWriter writer = vector.getWriter();
        writer.allocate();
  
        final int valueCount = 100;
  
        for (int i = 0; i < valueCount; i++) {
          writer.startList();
          writer.decimal().writeDecimal(new BigDecimal(i));
          writer.writeNull();
          writer.decimal().writeDecimal(new BigDecimal(i * 3));
          writer.endList();
        }
        vector.setValueCount(valueCount);
  
        for (int i = 0; i < valueCount; i++) {
          List<BigDecimal> values = (List<BigDecimal>) vector.getObject(i);
          assertEquals(3, values.size());
          assertEquals(new BigDecimal(i), values.get(0));
          assertEquals(null, values.get(1));
          assertEquals(new BigDecimal(i * 3), values.get(2));
        }
      }
    }
  
    @Test
    public void testUnionFixedSizeListWriter() throws Exception {
      try (final FixedSizeListVector vector1 =
          FixedSizeListVector.empty("vector", /* size= */ 3, allocator)) {
  
        UnionFixedSizeListWriter writer1 = vector1.getWriter();
        writer1.allocate();
  
        int[] values1 = new int[] {1, 2, 3};
        int[] values2 = new int[] {4, 5, 6};
        int[] values3 = new int[] {7, 8, 9};
  
        // set some values
        writeListVector(vector1, writer1, values1);
        writeListVector(vector1, writer1, values2);
        writeListVector(vector1, writer1, values3);
        writer1.setValueCount(3);
  
        assertEquals(3, vector1.getValueCount());
  
        int[] realValue1 = convertListToIntArray(vector1.getObject(0));
        assertTrue(Arrays.equals(values1, realValue1));
        int[] realValue2 = convertListToIntArray(vector1.getObject(1));
        assertTrue(Arrays.equals(values2, realValue2));
        int[] realValue3 = convertListToIntArray(vector1.getObject(2));
        assertTrue(Arrays.equals(values3, realValue3));
      }
    }
  
    @Test
    public void testWriteDecimal() throws Exception {
      try (final FixedSizeListVector vector =
          FixedSizeListVector.empty("vector", /* size= */ 3, allocator)) {
  
        UnionFixedSizeListWriter writer = vector.getWriter();
        writer.allocate();
  
        final int valueCount = 100;
  
        for (int i = 0; i < valueCount; i++) {
          writer.startList();
          writer.decimal().writeDecimal(new BigDecimal(i));
          writer.decimal().writeDecimal(new BigDecimal(i * 2));
          writer.decimal().writeDecimal(new BigDecimal(i * 3));
          writer.endList();
        }
        vector.setValueCount(valueCount);
  
        for (int i = 0; i < valueCount; i++) {
          List<BigDecimal> values = (List<BigDecimal>) vector.getObject(i);
          assertEquals(3, values.size());
          assertEquals(new BigDecimal(i), values.get(0));
          assertEquals(new BigDecimal(i * 2), values.get(1));
          assertEquals(new BigDecimal(i * 3), values.get(2));
        }
      }
    }
  
    @Test
    public void testDecimalIndexCheck() throws Exception {
      try (final FixedSizeListVector vector =
          FixedSizeListVector.empty("vector", /* size= */ 3, allocator)) {
  
        UnionFixedSizeListWriter writer = vector.getWriter();
        writer.allocate();
  
        IllegalStateException e =
            assertThrows(
                IllegalStateException.class,
                () -> {
                  writer.startList();
                  writer.decimal().writeDecimal(new BigDecimal(1));
                  writer.decimal().writeDecimal(new BigDecimal(2));
                  writer.decimal().writeDecimal(new BigDecimal(3));
                  writer.decimal().writeDecimal(new BigDecimal(4));
                  writer.endList();
                });
        assertEquals("values at index 0 is greater than listSize 3", e.getMessage());
      }
    }
  
    @Test
    public void testWriteIllegalData() throws Exception {
      assertThrows(
          IllegalStateException.class,
          () -> {
            try (final FixedSizeListVector vector1 =
                FixedSizeListVector.empty("vector", /* size= */ 3, allocator)) {
  
              UnionFixedSizeListWriter writer1 = vector1.getWriter();
              writer1.allocate();
  
              int[] values1 = new int[] {1, 2, 3};
              int[] values2 = new int[] {4, 5, 6, 7, 8};
  
              // set some values
              writeListVector(vector1, writer1, values1);
              writeListVector(vector1, writer1, values2);
              writer1.setValueCount(3);
  
              assertEquals(3, vector1.getValueCount());
              int[] realValue1 = convertListToIntArray(vector1.getObject(0));
              assertTrue(Arrays.equals(values1, realValue1));
              int[] realValue2 = convertListToIntArray(vector1.getObject(1));
              assertTrue(Arrays.equals(values2, realValue2));
            }
          });
    }
  
    @Test
    public void testSplitAndTransfer() throws Exception {
      try (final FixedSizeListVector vector1 =
          FixedSizeListVector.empty("vector", /* size= */ 3, allocator)) {
  
        UnionFixedSizeListWriter writer1 = vector1.getWriter();
        writer1.allocate();
  
        int[] values1 = new int[] {1, 2, 3};
        int[] values2 = new int[] {4, 5, 6};
        int[] values3 = new int[] {7, 8, 9};
  
        // set some values
        writeListVector(vector1, writer1, values1);
        writeListVector(vector1, writer1, values2);
        writeListVector(vector1, writer1, values3);
        writer1.setValueCount(3);
  
        TransferPair transferPair = vector1.getTransferPair(allocator);
        transferPair.splitAndTransfer(0, 2);
        FixedSizeListVector targetVector = (FixedSizeListVector) transferPair.getTo();
  
        assertEquals(2, targetVector.getValueCount());
        int[] realValue1 = convertListToIntArray(targetVector.getObject(0));
        assertArrayEquals(values1, realValue1);
        int[] realValue2 = convertListToIntArray(targetVector.getObject(1));
        assertArrayEquals(values2, realValue2);
  
        targetVector.clear();
      }
    }
  
    @Test
    public void testZeroWidthVector() {
      try (final FixedSizeListVector vector1 =
          FixedSizeListVector.empty("vector", /* size= */ 0, allocator)) {
  
        UnionFixedSizeListWriter writer1 = vector1.getWriter();
        writer1.allocate();
  
        int[] values1 = new int[] {};
        int[] values2 = new int[] {};
        int[] values3 = null;
        int[] values4 = new int[] {};
  
        // set some values
        writeListVector(vector1, writer1, values1);
        writeListVector(vector1, writer1, values2);
        writeListVector(vector1, writer1, values3);
        writeListVector(vector1, writer1, values4);
        writer1.setValueCount(4);
  
        assertEquals(4, vector1.getValueCount());
  
        int[] realValue1 = convertListToIntArray(vector1.getObject(0));
        assertArrayEquals(values1, realValue1);
        int[] realValue2 = convertListToIntArray(vector1.getObject(1));
        assertArrayEquals(values2, realValue2);
        assertNull(vector1.getObject(2));
        int[] realValue4 = convertListToIntArray(vector1.getObject(3));
        assertArrayEquals(values4, realValue4);
      }
    }
  
    @Test
    public void testVectorWithNulls() {
      try (final FixedSizeListVector vector1 =
          FixedSizeListVector.empty("vector", /* size= */ 4, allocator)) {
  
        UnionFixedSizeListWriter writer1 = vector1.getWriter();
        writer1.allocate();
  
        List<Integer> values1 = Arrays.asList(null, 1, 2, 3);
        List<Integer> values2 = Arrays.asList(4, null, 5, 6);
        List<Integer> values3 = null;
        List<Integer> values4 = Arrays.asList(7, 8, null, 9);
  
        // set some values
        writeListVector(vector1, writer1, values1);
        writeListVector(vector1, writer1, values2);
        writeListVector(vector1, writer1, values3);
        writeListVector(vector1, writer1, values4);
        writer1.setValueCount(4);
  
        assertEquals(4, vector1.getValueCount());
  
        List<?> realValue1 = vector1.getObject(0);
        assertEquals(values1, realValue1);
        List<?> realValue2 = vector1.getObject(1);
        assertEquals(values2, realValue2);
        List<?> realValue3 = vector1.getObject(2);
        assertEquals(values3, realValue3);
        List<?> realValue4 = vector1.getObject(3);
        assertEquals(values4, realValue4);
      }
    }
  
    @Test
    public void testWriteVarCharHelpers() throws Exception {
      try (final FixedSizeListVector vector =
          FixedSizeListVector.empty("vector", /* size= */ 4, allocator)) {
  
        UnionFixedSizeListWriter writer = vector.getWriter();
        writer.allocate();
  
        writer.startList();
        writer.writeVarChar("row1,1");
        writer.writeVarChar(new Text("row1,2"));
        writer.writeNull();
        writer.writeNull();
        writer.endList();
  
        assertEquals("row1,1", vector.getObject(0).get(0).toString());
        assertEquals("row1,2", vector.getObject(0).get(1).toString());
      }
    }
  
    @Test
    public void testWriteLargeVarCharHelpers() throws Exception {
      try (final FixedSizeListVector vector =
          FixedSizeListVector.empty("vector", /* size= */ 4, allocator)) {
  
        UnionFixedSizeListWriter writer = vector.getWriter();
        writer.allocate();
  
        writer.startList();
        writer.writeLargeVarChar("row1,1");
        writer.writeLargeVarChar(new Text("row1,2"));
        writer.writeNull();
        writer.writeNull();
        writer.endList();
  
        assertEquals("row1,1", vector.getObject(0).get(0).toString());
        assertEquals("row1,2", vector.getObject(0).get(1).toString());
      }
    }
  
    @Test
    public void testWriteVarBinaryHelpers() throws Exception {
      try (final FixedSizeListVector vector =
          FixedSizeListVector.empty("vector", /* size= */ 4, allocator)) {
  
        UnionFixedSizeListWriter writer = vector.getWriter();
        writer.allocate();
  
        writer.startList();
        writer.writeVarBinary("row1,1".getBytes(StandardCharsets.UTF_8));
        writer.writeVarBinary(
            "row1,2".getBytes(StandardCharsets.UTF_8),
            0,
            "row1,2".getBytes(StandardCharsets.UTF_8).length);
        writer.writeVarBinary(ByteBuffer.wrap("row1,3".getBytes(StandardCharsets.UTF_8)));
        writer.writeVarBinary(
            ByteBuffer.wrap("row1,4".getBytes(StandardCharsets.UTF_8)),
            0,
            "row1,4".getBytes(StandardCharsets.UTF_8).length);
        writer.endList();
  
        assertEquals(
            "row1,1", new String((byte[]) vector.getObject(0).get(0), StandardCharsets.UTF_8));
        assertEquals(
            "row1,2", new String((byte[]) vector.getObject(0).get(1), StandardCharsets.UTF_8));
        assertEquals(
            "row1,3", new String((byte[]) vector.getObject(0).get(2), StandardCharsets.UTF_8));
        assertEquals(
            "row1,4", new String((byte[]) vector.getObject(0).get(3), StandardCharsets.UTF_8));
      }
    }
  
    @Test
    public void testWriteLargeVarBinaryHelpers() throws Exception {
      try (final FixedSizeListVector vector =
          FixedSizeListVector.empty("vector", /* size= */ 4, allocator)) {
  
        UnionFixedSizeListWriter writer = vector.getWriter();
        writer.allocate();
  
        writer.startList();
        writer.writeLargeVarBinary("row1,1".getBytes(StandardCharsets.UTF_8));
        writer.writeLargeVarBinary(
            "row1,2".getBytes(StandardCharsets.UTF_8),
            0,
            "row1,2".getBytes(StandardCharsets.UTF_8).length);
        writer.writeLargeVarBinary(ByteBuffer.wrap("row1,3".getBytes(StandardCharsets.UTF_8)));
        writer.writeLargeVarBinary(
            ByteBuffer.wrap("row1,4".getBytes(StandardCharsets.UTF_8)),
            0,
            "row1,4".getBytes(StandardCharsets.UTF_8).length);
        writer.endList();
  
        assertEquals(
            "row1,1", new String((byte[]) vector.getObject(0).get(0), StandardCharsets.UTF_8));
        assertEquals(
            "row1,2", new String((byte[]) vector.getObject(0).get(1), StandardCharsets.UTF_8));
        assertEquals(
            "row1,3", new String((byte[]) vector.getObject(0).get(2), StandardCharsets.UTF_8));
        assertEquals(
            "row1,4", new String((byte[]) vector.getObject(0).get(3), StandardCharsets.UTF_8));
      }
    }
  
    private int[] convertListToIntArray(List<?> list) {
      int[] values = new int[list.size()];
      for (int i = 0; i < list.size(); i++) {
        values[i] = (int) list.get(i);
      }
      return values;
    }
  
    private void writeListVector(
        FixedSizeListVector vector, UnionFixedSizeListWriter writer, int[] values) {
      writer.startList();
      if (values != null) {
        for (int v : values) {
          writer.integer().writeInt(v);
        }
      } else {
        vector.setNull(writer.getPosition());
      }
      writer.endList();
    }
  
    private void writeListVector(
        FixedSizeListVector vector, UnionFixedSizeListWriter writer, List<Integer> values) {
      writer.startList();
      if (values != null) {
        for (Integer v : values) {
          if (v == null) {
            writer.writeNull();
          } else {
            writer.integer().writeInt(v);
          }
        }
      } else {
        vector.setNull(writer.getPosition());
      }
      writer.endList();
    }
  }
  