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
   *
   *     https://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.avro.generic;
  
  import static org.apache.avro.TestCircularReferences.Reference;
  import static org.apache.avro.TestCircularReferences.Referenceable;
  import static org.junit.jupiter.api.Assertions.*;
  
  import com.fasterxml.jackson.core.JsonFactory;
  import com.fasterxml.jackson.core.JsonParseException;
  import com.fasterxml.jackson.core.JsonParser;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.nio.ByteBuffer;
  import java.time.Instant;
  import java.time.LocalDate;
  import java.time.LocalDateTime;
  import java.time.LocalTime;
  import java.util.ArrayDeque;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.UUID;
  
  import org.apache.avro.AvroRuntimeException;
  import org.apache.avro.Schema;
  import org.apache.avro.Schema.Field;
  import org.apache.avro.Schema.Type;
  import org.apache.avro.SchemaBuilder;
  import org.apache.avro.TestCircularReferences.ReferenceManager;
  import org.apache.avro.generic.GenericData.Record;
  import org.apache.avro.io.BinaryData;
  import org.apache.avro.io.BinaryEncoder;
  import org.apache.avro.io.EncoderFactory;
  import org.apache.avro.util.Utf8;
  import org.junit.jupiter.api.Test;
  
  public class TestGenericData {
  
    @Test
    void recordConstructorNullSchema() throws Exception {
      assertThrows(AvroRuntimeException.class, () -> {
        new GenericData.Record(null);
      });
    }
  
    @Test
    void recordConstructorWrongSchema() throws Exception {
      assertThrows(AvroRuntimeException.class, () -> {
        new GenericData.Record(Schema.create(Schema.Type.INT));
      });
    }
  
    @Test
    void arrayConstructorNullSchema() throws Exception {
      assertThrows(AvroRuntimeException.class, () -> {
        new GenericData.Array<>(1, null);
      });
    }
  
    @Test
    void arrayConstructorWrongSchema() throws Exception {
      assertThrows(AvroRuntimeException.class, () -> {
        new GenericData.Array<>(1, Schema.create(Schema.Type.INT));
      });
    }
  
    @Test
    void recordCreateEmptySchema() throws Exception {
      assertThrows(AvroRuntimeException.class, () -> {
        Schema s = Schema.createRecord("schemaName", "schemaDoc", "namespace", false);
        new GenericData.Record(s);
      });
    }
  
    @Test
    void getEmptySchemaFields() throws Exception {
      assertThrows(AvroRuntimeException.class, () -> {
        Schema s = Schema.createRecord("schemaName", "schemaDoc", "namespace", false);
        s.getFields();
      });
    }
  
    @Test
    void getEmptySchemaField() throws Exception {
      assertThrows(AvroRuntimeException.class, () -> {
        Schema s = Schema.createRecord("schemaName", "schemaDoc", "namespace", false);
        s.getField("foo");
      });
    }
  
    @Test
    void recordPutInvalidField() throws Exception {
      assertThrows(AvroRuntimeException.class, () -> {
        Schema s = Schema.createRecord("schemaName", "schemaDoc", "namespace", false);
        List<Schema.Field> fields = new ArrayList<>();
        fields.add(new Schema.Field("someFieldName", s, "docs", null));
        s.setFields(fields);
        Record r = new GenericData.Record(s);
        r.put("invalidFieldName", "someValue");
      });
    }
  
    /** Make sure that even with nulls, hashCode() doesn't throw NPE. */
    @Test
    void testHashCode() {
      GenericData.get().hashCode(null, Schema.create(Type.NULL));
      GenericData.get().hashCode(null,
          Schema.createUnion(Arrays.asList(Schema.create(Type.BOOLEAN), Schema.create(Type.STRING))));
      List<CharSequence> stuff = new ArrayList<>();
      stuff.add("string");
      Schema schema = recordSchema();
      GenericRecord r = new GenericData.Record(schema);
      r.put(0, stuff);
      GenericData.get().hashCode(r, schema);
    }
  
    @Test
    void testEquals() {
      Schema s = recordSchema();
      GenericRecord r0 = new GenericData.Record(s);
      GenericRecord r1 = new GenericData.Record(s);
      GenericRecord r2 = new GenericData.Record(s);
      Collection<CharSequence> l0 = new ArrayDeque<>();
      List<CharSequence> l1 = new ArrayList<>();
      GenericArray<CharSequence> l2 = new GenericData.Array<>(1, s.getFields().get(0).schema());
      String foo = "foo";
      l0.add(new StringBuilder(foo));
      l1.add(foo);
      l2.add(new Utf8(foo));
      r0.put(0, l0);
      r1.put(0, l1);
      r2.put(0, l2);
      assertEquals(r0, r1);
      assertEquals(r0, r2);
      assertEquals(r1, r2);
    }
  
    @Test
    public void testMapKeyEqualsStringAndUtf8Compatibility() {
      Field myMapField = new Field("my_map", Schema.createMap(Schema.create(Schema.Type.STRING)), null, null);
      Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
      schema.setFields(Arrays.asList(myMapField));
      GenericRecord r0 = new GenericData.Record(schema);
      GenericRecord r1 = new GenericData.Record(schema);
  
      HashMap<CharSequence, String> pair1 = new HashMap<>();
      pair1.put("keyOne", "valueOne");
      r0.put("my_map", pair1);
  
      HashMap<CharSequence, String> pair2 = new HashMap<>();
      pair2.put(new Utf8("keyOne"), "valueOne");
      r1.put("my_map", pair2);
  
      assertEquals(r0, r1);
      assertEquals(r1, r0);
    }
  
    @Test
    public void testMapValuesEqualsStringAndUtf8Compatibility() {
      Field myMapField = new Field("my_map", Schema.createMap(Schema.create(Schema.Type.STRING)), null, null);
      Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
      schema.setFields(Arrays.asList(myMapField));
      GenericRecord r0 = new GenericData.Record(schema);
      GenericRecord r1 = new GenericData.Record(schema);
  
      HashMap<CharSequence, CharSequence> pair1 = new HashMap<>();
      pair1.put("keyOne", "valueOne");
      r0.put("my_map", pair1);
  
      HashMap<CharSequence, CharSequence> pair2 = new HashMap<>();
      pair2.put("keyOne", new Utf8("valueOne"));
      r1.put("my_map", pair2);
  
      assertEquals(r0, r1);
      assertEquals(r1, r0);
    }
  
    @Test
    public void testEqualsEmptyMaps() {
      Field myMapField = new Field("my_map", Schema.createMap(Schema.create(Schema.Type.STRING)), null, null);
      Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
      schema.setFields(Arrays.asList(myMapField));
  
      GenericRecord r0 = new GenericData.Record(schema);
      r0.put("my_map", new HashMap<>());
      GenericRecord r1 = new GenericData.Record(schema);
      r1.put("my_map", new HashMap<>());
  
      assertEquals(r0, r1);
      assertEquals(r1, r0);
    }
  
    @Test
    public void testEqualsEmptyMapAndNonEmptyMap() {
      Field myMapField = new Field("my_map", Schema.createMap(Schema.create(Schema.Type.STRING)), null, null);
      Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
      schema.setFields(Arrays.asList(myMapField));
  
      GenericRecord r0 = new GenericData.Record(schema);
      r0.put("my_map", new HashMap<>());
      GenericRecord r1 = new GenericData.Record(schema);
      HashMap<CharSequence, CharSequence> pair1 = new HashMap<>();
      pair1.put("keyOne", "valueOne");
      r1.put("my_map", pair1);
  
      assertNotEquals(r0, r1);
      assertNotEquals(r1, r0);
    }
  
    @Test
    public void testEqualsMapAndSubset() {
      Field myMapField = new Field("my_map", Schema.createMap(Schema.create(Schema.Type.STRING)), null, null);
      Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
      schema.setFields(Arrays.asList(myMapField));
  
      GenericRecord r0 = new GenericData.Record(schema);
      HashMap<CharSequence, String> m1 = new HashMap<>();
      m1.put("keyOne", "valueOne");
      m1.put("keyTwo", "valueTwo");
      r0.put("my_map", m1);
  
      GenericRecord r1 = new GenericData.Record(schema);
      HashMap<CharSequence, String> m2 = new HashMap<>();
      m2.put("keyOne", "valueOne");
      r1.put("my_map", m2);
  
      assertNotEquals(r0, r1);
      assertNotEquals(r1, r0);
    }
  
    @Test
    public void testEqualsMapAndSameSizeMapWithDifferentKeys() {
      Field myMapField = new Field("my_map", Schema.createMap(Schema.create(Schema.Type.STRING)), null, null);
      Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
      schema.setFields(Arrays.asList(myMapField));
  
      GenericRecord r0 = new GenericData.Record(schema);
      HashMap<CharSequence, String> m1 = new HashMap<>();
      m1.put("keyOne", "valueOne");
      r0.put("my_map", m1);
  
      GenericRecord r1 = new GenericData.Record(schema);
      HashMap<CharSequence, String> m2 = new HashMap<>();
      m2.put("keyTwo", "valueTwo");
      r1.put("my_map", m2);
  
      assertNotEquals(r0, r1);
      assertNotEquals(r1, r0);
    }
  
    @Test
    public void testEqualsMapAndSameSizeMapWithDifferentValues() {
      Field myMapField = new Field("my_map", Schema.createMap(Schema.create(Schema.Type.STRING)), null, null);
      Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
      schema.setFields(Arrays.asList(myMapField));
  
      GenericRecord r0 = new GenericData.Record(schema);
      HashMap<CharSequence, String> m1 = new HashMap<>();
      m1.put("keyOne", "valueOne");
      r0.put("my_map", m1);
  
      GenericRecord r1 = new GenericData.Record(schema);
      HashMap<CharSequence, String> m2 = new HashMap<>();
      m2.put("keyOne", "valueTwo");
      r1.put("my_map", m2);
  
      assertNotEquals(r0, r1);
      assertNotEquals(r1, r0);
    }
  
    @Test
    public void testArrayValuesEqualsStringAndUtf8Compatibility() {
      Field myArrayField = new Field("my_array", Schema.createArray(Schema.create(Schema.Type.STRING)), null, null);
      Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
      schema.setFields(Arrays.asList(myArrayField));
      GenericRecord r0 = new GenericData.Record(schema);
      GenericRecord r1 = new GenericData.Record(schema);
  
      List<CharSequence> array1 = Arrays.asList("valueOne");
      r0.put("my_array", array1);
  
      List<CharSequence> array2 = Arrays.asList(new Utf8("valueOne"));
      r1.put("my_array", array2);
  
      assertEquals(r0, r1);
      assertEquals(r1, r0);
    }
  
    // AVRO-4139
    @Test
    public void testEqualsMapInArray() {
      Schema schema = Schema.createArray(Schema.createMap(Schema.create(Schema.Type.STRING)));
      GenericData.Array<Map<String, String>> a1 = new GenericData.Array<>(10, schema);
      GenericData.Array<Map<String, String>> a2 = new GenericData.Array<>(10, schema);
      a1.add(Map.of("a", "b"));
      a2.add(Map.of("a", "b"));
      assertEquals(a1, a2);
    }
  
    private Schema recordSchema() {
      List<Field> fields = new ArrayList<>();
      fields.add(new Field("anArray", Schema.createArray(Schema.create(Type.STRING)), null, null));
      Schema schema = Schema.createRecord("arrayFoo", "test", "mytest", false);
      schema.setFields(fields);
  
      return schema;
    }
  
    @Test
    void equals2() {
      Schema schema1 = Schema.createRecord("r", null, "x", false);
      List<Field> fields1 = new ArrayList<>();
      fields1.add(new Field("a", Schema.create(Schema.Type.STRING), null, null, Field.Order.IGNORE));
      schema1.setFields(fields1);
  
      // only differs in field order
      Schema schema2 = Schema.createRecord("r", null, "x", false);
      List<Field> fields2 = new ArrayList<>();
      fields2.add(new Field("a", Schema.create(Schema.Type.STRING), null, null, Field.Order.ASCENDING));
      schema2.setFields(fields2);
  
      GenericRecord record1 = new GenericData.Record(schema1);
      record1.put("a", "1");
  
      GenericRecord record2 = new GenericData.Record(schema2);
      record2.put("a", "2");
  
      assertNotEquals(record2, record1);
      assertNotEquals(record1, record2);
    }
  
    @Test
    void recordGetFieldDoesntExist() throws Exception {
      assertThrows(AvroRuntimeException.class, () -> {
        Schema schema = Schema.createRecord("test", "doc", "test", false, Collections.EMPTY_LIST);
        GenericData.Record record = new GenericData.Record(schema);
        record.get("does not exist");
      });
    }
  
    @Test
    void arrayReversal() {
      Schema schema = Schema.createArray(Schema.create(Schema.Type.INT));
      GenericArray<Integer> forward = new GenericData.Array<>(10, schema);
      GenericArray<Integer> backward = new GenericData.Array<>(10, schema);
      for (int i = 0; i <= 9; i++) {
        forward.add(i);
      }
      for (int i = 9; i >= 0; i--) {
        backward.add(i);
      }
      forward.reverse();
      assertEquals(forward, backward);
    }
  
    @Test
    void arrayListInterface() {
      Schema schema = Schema.createArray(Schema.create(Schema.Type.INT));
      GenericArray<Integer> array = new GenericData.Array<>(1, schema);
      array.add(99);
      assertEquals(Integer.valueOf(99), array.get(0));
      List<Integer> list = new ArrayList<>();
      list.add(99);
      assertEquals(array, list);
      assertEquals(list, array);
      assertEquals(list.hashCode(), array.hashCode());
      try {
        array.get(2);
        fail("Expected IndexOutOfBoundsException getting index 2");
      } catch (IndexOutOfBoundsException e) {
      }
      array.clear();
      assertEquals(0, array.size());
      try {
        array.get(0);
        fail("Expected IndexOutOfBoundsException getting index 0 after clear()");
      } catch (IndexOutOfBoundsException e) {
      }
  
    }
  
    @Test
    void arrayAddAtLocation() {
      Schema schema = Schema.createArray(Schema.create(Schema.Type.INT));
      GenericArray<Integer> array = new GenericData.Array<>(6, schema);
      array.clear();
      for (int i = 0; i < 5; ++i)
        array.add(i);
      assertEquals(5, array.size());
      array.add(0, 6);
      assertEquals(Integer.valueOf(6), array.get(0));
      assertEquals(6, array.size());
      assertEquals(Integer.valueOf(0), array.get(1));
      assertEquals(Integer.valueOf(4), array.get(5));
      array.add(6, 7);
      assertEquals(Integer.valueOf(7), array.get(6));
      assertEquals(7, array.size());
      assertEquals(Integer.valueOf(6), array.get(0));
      assertEquals(Integer.valueOf(4), array.get(5));
      array.add(1, 8);
      assertEquals(Integer.valueOf(8), array.get(1));
      assertEquals(Integer.valueOf(0), array.get(2));
      assertEquals(Integer.valueOf(6), array.get(0));
      assertEquals(8, array.size());
      try {
        array.get(9);
        fail("Expected IndexOutOfBoundsException after adding elements");
      } catch (IndexOutOfBoundsException e) {
      }
    }
  
    @Test
    void arrayRemove() {
      Schema schema = Schema.createArray(Schema.create(Schema.Type.INT));
      GenericArray<Integer> array = new GenericData.Array<>(10, schema);
      array.clear();
      for (int i = 0; i < 10; ++i)
        array.add(i);
      assertEquals(10, array.size());
      assertEquals(Integer.valueOf(0), array.get(0));
      assertEquals(Integer.valueOf(9), array.get(9));
  
      array.remove(0);
      assertEquals(9, array.size());
      assertEquals(Integer.valueOf(1), array.get(0));
      assertEquals(Integer.valueOf(2), array.get(1));
      assertEquals(Integer.valueOf(9), array.get(8));
  
      // Test boundary errors.
      try {
        array.get(9);
        fail("Expected IndexOutOfBoundsException after removing an element");
      } catch (IndexOutOfBoundsException e) {
      }
      try {
        array.set(9, 99);
        fail("Expected IndexOutOfBoundsException after removing an element");
      } catch (IndexOutOfBoundsException e) {
      }
      try {
        array.remove(9);
        fail("Expected IndexOutOfBoundsException after removing an element");
      } catch (IndexOutOfBoundsException e) {
      }
  
      // Test that we can still remove for properly sized arrays, and the rval
      assertEquals(Integer.valueOf(9), array.remove(8));
      assertEquals(8, array.size());
  
      // Test insertion after remove
      array.add(88);
      assertEquals(Integer.valueOf(88), array.get(8));
    }
  
    @Test
    void arraySet() {
      Schema schema = Schema.createArray(Schema.create(Schema.Type.INT));
      GenericArray<Integer> array = new GenericData.Array<>(10, schema);
      array.clear();
      for (int i = 0; i < 10; ++i)
        array.add(i);
      assertEquals(10, array.size());
      assertEquals(Integer.valueOf(0), array.get(0));
      assertEquals(Integer.valueOf(5), array.get(5));
  
      assertEquals(Integer.valueOf(5), array.set(5, 55));
      assertEquals(10, array.size());
      assertEquals(Integer.valueOf(55), array.get(5));
    }
  
    @Test
    void toStringIsJson() throws JsonParseException, IOException {
      Field stringField = new Field("string", Schema.create(Type.STRING), null, null);
      Field enumField = new Field("enum", Schema.createEnum("my_enum", "doc", null, Arrays.asList("a", "b", "c")), null,
          null);
      Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
      schema.setFields(Arrays.asList(stringField, enumField));
  
      GenericRecord r = new GenericData.Record(schema);
      // \u2013 is EN DASH
      r.put(stringField.name(), "hello\nthere\"\tyou\u2013}");
      r.put(enumField.name(), new GenericData.EnumSymbol(enumField.schema(), "a"));
  
      String json = r.toString();
      JsonFactory factory = new JsonFactory();
      JsonParser parser = factory.createParser(json);
      ObjectMapper mapper = new ObjectMapper();
  
      // will throw exception if string is not parsable json
      mapper.readTree(parser);
    }
  
    @Test
    void mapWithNonStringKeyToStringIsJson() throws Exception {
      Schema intMapSchema = new Schema.Parser()
          .parse("{\"type\": \"map\", \"values\": \"string\", \"java-key-class\" : \"java.lang.Integer\"}");
      Field intMapField = new Field("intMap", Schema.createMap(intMapSchema), null, null);
      Schema decMapSchema = new Schema.Parser()
          .parse("{\"type\": \"map\", \"values\": \"string\", \"java-key-class\" : \"java.math.BigDecimal\"}");
      Field decMapField = new Field("decMap", Schema.createMap(decMapSchema), null, null);
      Schema boolMapSchema = new Schema.Parser()
          .parse("{\"type\": \"map\", \"values\": \"string\", \"java-key-class\" : \"java.lang.Boolean\"}");
      Field boolMapField = new Field("boolMap", Schema.createMap(boolMapSchema), null, null);
      Schema fileMapSchema = new Schema.Parser()
          .parse("{\"type\": \"map\", \"values\": \"string\", \"java-key-class\" : \"java.io.File\"}");
      Field fileMapField = new Field("fileMap", Schema.createMap(fileMapSchema), null, null);
      Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
      schema.setFields(Arrays.asList(intMapField, decMapField, boolMapField, fileMapField));
  
      HashMap<Integer, String> intPair = new HashMap<>();
      intPair.put(1, "one");
      intPair.put(2, "two");
  
      HashMap<java.math.BigDecimal, String> decPair = new HashMap<>();
      decPair.put(java.math.BigDecimal.valueOf(1), "one");
      decPair.put(java.math.BigDecimal.valueOf(2), "two");
  
      HashMap<Boolean, String> boolPair = new HashMap<>();
      boolPair.put(true, "isTrue");
      boolPair.put(false, "isFalse");
      boolPair.put(null, null);
  
      HashMap<java.io.File, String> filePair = new HashMap<>();
      java.io.File f = new java.io.File(getClass().getResource("/SchemaBuilder.avsc").toURI());
      filePair.put(f, "File");
  
      GenericRecord r = new GenericData.Record(schema);
      r.put(intMapField.name(), intPair);
      r.put(decMapField.name(), decPair);
      r.put(boolMapField.name(), boolPair);
      r.put(fileMapField.name(), filePair);
  
      String json = r.toString();
      JsonFactory factory = new JsonFactory();
      JsonParser parser = factory.createParser(json);
      ObjectMapper mapper = new ObjectMapper();
  
      // will throw exception if string is not parsable json
      mapper.readTree(parser);
    }
  
    @Test
    void toStringEscapesControlCharsInBytes() throws Exception {
      GenericData data = GenericData.get();
      ByteBuffer bytes = ByteBuffer.wrap(new byte[] { 'a', '\n', 'b' });
      assertEquals("\"a\\nb\"", data.toString(bytes));
      assertEquals("\"a\\nb\"", data.toString(bytes));
    }
  
    @Test
    void toStringEscapesControlCharsInMap() {
      GenericData data = GenericData.get();
      Map<String, String> m = new HashMap<>();
      m.put("a\n\\b", "a\n\\b");
      assertEquals("{\"a\\n\\\\b\": \"a\\n\\\\b\"}", data.toString(m));
    }
  
    @Test
    void toStringFixed() throws Exception {
      GenericData data = GenericData.get();
      assertEquals("[97, 10, 98]",
          data.toString(new GenericData.Fixed(Schema.createFixed("test", null, null, 3), new byte[] { 'a', '\n', 'b' })));
    }
  
    @Test
    void toStringDoesNotEscapeForwardSlash() throws Exception {
      GenericData data = GenericData.get();
      assertEquals("\"/\"", data.toString("/"));
    }
  
    @Test
    void toStringNanInfinity() throws Exception {
      GenericData data = GenericData.get();
      assertEquals("\"Infinity\"", data.toString(Float.POSITIVE_INFINITY));
      assertEquals("\"-Infinity\"", data.toString(Float.NEGATIVE_INFINITY));
      assertEquals("\"NaN\"", data.toString(Float.NaN));
      assertEquals("\"Infinity\"", data.toString(Double.POSITIVE_INFINITY));
      assertEquals("\"-Infinity\"", data.toString(Double.NEGATIVE_INFINITY));
      assertEquals("\"NaN\"", data.toString(Double.NaN));
    }
  
    @Test
    void toStringConvertsDatesAsStrings() throws Exception {
      GenericData data = GenericData.get();
      assertEquals("\"1961-04-12T06:07:10Z\"", data.toString(Instant.parse("1961-04-12T06:07:10Z")));
      assertEquals("\"1961-04-12\"", data.toString(LocalDate.parse("1961-04-12")));
      assertEquals("\"1961-04-12T06:07:10\"", data.toString(LocalDateTime.parse("1961-04-12T06:07:10")));
      assertEquals("\"10:10:10\"", data.toString(LocalTime.parse("10:10:10")));
    }
  
    @Test
    void ToStringConvertsUuidsAsStrings() throws Exception {
      GenericData data = GenericData.get();
      assertEquals("\"abf2f1e8-cece-4fdc-290a-babaca09ec74\"",
          data.toString(UUID.fromString("abf2f1e8-cece-4fdc-290a-babaca09ec74")));
    }
  
    @Test
    void compare() {
      // Prepare a schema for testing.
      Field integerField = new Field("test", Schema.create(Type.INT), null, null);
      List<Field> fields = new ArrayList<>();
      fields.add(integerField);
      Schema record = Schema.createRecord("test", null, null, false);
      record.setFields(fields);
  
      ByteArrayOutputStream b1 = new ByteArrayOutputStream(5);
      ByteArrayOutputStream b2 = new ByteArrayOutputStream(5);
      BinaryEncoder b1Enc = EncoderFactory.get().binaryEncoder(b1, null);
      BinaryEncoder b2Enc = EncoderFactory.get().binaryEncoder(b2, null);
      // Prepare two different datums
      Record testDatum1 = new Record(record);
      testDatum1.put(0, 1);
      Record testDatum2 = new Record(record);
      testDatum2.put(0, 2);
      GenericDatumWriter<Record> gWriter = new GenericDatumWriter<>(record);
      Integer start1 = 0, start2 = 0;
      try {
        // Write two datums in each stream
        // and get the offset length after the first write in each.
        gWriter.write(testDatum1, b1Enc);
        b1Enc.flush();
        start1 = b1.size();
        gWriter.write(testDatum1, b1Enc);
        b1Enc.flush();
        b1.close();
        gWriter.write(testDatum2, b2Enc);
        b2Enc.flush();
        start2 = b2.size();
        gWriter.write(testDatum2, b2Enc);
        b2Enc.flush();
        b2.close();
        // Compare to check if offset-based compare works right.
        assertEquals(-1, BinaryData.compare(b1.toByteArray(), start1, b2.toByteArray(), start2, record));
      } catch (IOException e) {
        fail("IOException while writing records to output stream.");
      }
    }
  
    @Test
    void enumCompare() {
      Schema s = Schema.createEnum("Kind", null, null, Arrays.asList("Z", "Y", "X"));
      GenericEnumSymbol z = new GenericData.EnumSymbol(s, "Z");
      GenericEnumSymbol z2 = new GenericData.EnumSymbol(s, "Z");
      assertEquals(0, z.compareTo(z2));
      GenericEnumSymbol y = new GenericData.EnumSymbol(s, "Y");
      assertTrue(y.compareTo(z) > 0);
      assertTrue(z.compareTo(y) < 0);
    }
  
    @Test
    void byteBufferDeepCopy() {
      // Test that a deep copy of a byte buffer respects the byte buffer
      // limits and capacity.
      byte[] buffer_value = { 0, 1, 2, 3, 0, 0, 0 };
      ByteBuffer buffer = ByteBuffer.wrap(buffer_value, 1, 4);
      Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
      Field byte_field = new Field("bytes", Schema.create(Type.BYTES), null, null);
      schema.setFields(Collections.singletonList(byte_field));
  
      GenericRecord record = new GenericData.Record(schema);
      record.put(byte_field.name(), buffer);
  
      GenericRecord copy = GenericData.get().deepCopy(schema, record);
      ByteBuffer buffer_copy = (ByteBuffer) copy.get(byte_field.name());
  
      assertEquals(buffer, buffer_copy);
    }
  
    @Test
    void validateNullableEnum() {
      List<Schema> unionTypes = new ArrayList<>();
      Schema schema;
      Schema nullSchema = Schema.create(Type.NULL);
      Schema enumSchema = Schema.createEnum("AnEnum", null, null, Arrays.asList("X", "Y", "Z"));
      GenericEnumSymbol w = new GenericData.EnumSymbol(enumSchema, "W");
      GenericEnumSymbol x = new GenericData.EnumSymbol(enumSchema, "X");
      GenericEnumSymbol y = new GenericData.EnumSymbol(enumSchema, "Y");
      GenericEnumSymbol z = new GenericData.EnumSymbol(enumSchema, "Z");
  
      // null is first
      unionTypes.clear();
      unionTypes.add(nullSchema);
      unionTypes.add(enumSchema);
      schema = Schema.createUnion(unionTypes);
  
      assertTrue(GenericData.get().validate(schema, z));
      assertTrue(GenericData.get().validate(schema, y));
      assertTrue(GenericData.get().validate(schema, x));
      assertFalse(GenericData.get().validate(schema, w));
      assertTrue(GenericData.get().validate(schema, null));
  
      // null is last
      unionTypes.clear();
      unionTypes.add(enumSchema);
      unionTypes.add(nullSchema);
      schema = Schema.createUnion(unionTypes);
  
      assertTrue(GenericData.get().validate(schema, z));
      assertTrue(GenericData.get().validate(schema, y));
      assertTrue(GenericData.get().validate(schema, x));
      assertFalse(GenericData.get().validate(schema, w));
      assertTrue(GenericData.get().validate(schema, null));
    }
  
    private enum anEnum {
      ONE, TWO, THREE
    }
  
    @Test
    void validateRequiresGenericSymbolForEnumSchema() {
      final Schema schema = Schema.createEnum("my_enum", "doc", "namespace", Arrays.asList("ONE", "TWO", "THREE"));
      final GenericData gd = GenericData.get();
  
      /* positive cases */
      assertTrue(gd.validate(schema, new GenericData.EnumSymbol(schema, "ONE")));
      assertTrue(gd.validate(schema, new GenericData.EnumSymbol(schema, anEnum.ONE)));
  
      /* negative cases */
      assertFalse(gd.validate(schema, "ONE"), "We don't expect GenericData to allow a String datum for an enum schema");
      assertFalse(gd.validate(schema, anEnum.ONE), "We don't expect GenericData to allow a Java Enum for an enum schema");
    }
  
    @Test
    void validateUnion() {
      Schema type1Schema = SchemaBuilder.record("Type1").fields().requiredString("myString").requiredInt("myInt")
          .endRecord();
  
      Schema type2Schema = SchemaBuilder.record("Type2").fields().requiredString("myString").endRecord();
  
      Schema unionSchema = SchemaBuilder.unionOf().type(type1Schema).and().type(type2Schema).endUnion();
  
      GenericRecord record = new GenericData.Record(type2Schema);
      record.put("myString", "myValue");
      assertTrue(GenericData.get().validate(unionSchema, record));
    }
  
    /*
     * The toString has a detection for circular references to abort. This detection
     * has the risk of detecting that same value as being a circular reference. For
     * Record, Map and Array this is correct, for the rest is is not.
     */
    @Test
    void toStringSameValues() throws IOException {
      List<Field> fields = new ArrayList<>();
      fields.add(new Field("nullstring1", Schema.create(Type.STRING), null, null));
      fields.add(new Field("nullstring2", Schema.create(Type.STRING), null, null));
  
      fields.add(new Field("string1", Schema.create(Type.STRING), null, null));
      fields.add(new Field("string2", Schema.create(Type.STRING), null, null));
  
      fields.add(new Field("bytes1", Schema.create(Type.BYTES), null, null));
      fields.add(new Field("bytes2", Schema.create(Type.BYTES), null, null));
  
      fields.add(new Field("int1", Schema.create(Type.INT), null, null));
      fields.add(new Field("int2", Schema.create(Type.INT), null, null));
  
      fields.add(new Field("long1", Schema.create(Type.LONG), null, null));
      fields.add(new Field("long2", Schema.create(Type.LONG), null, null));
  
      fields.add(new Field("float1", Schema.create(Type.FLOAT), null, null));
      fields.add(new Field("float2", Schema.create(Type.FLOAT), null, null));
  
      fields.add(new Field("double1", Schema.create(Type.DOUBLE), null, null));
      fields.add(new Field("double2", Schema.create(Type.DOUBLE), null, null));
  
      fields.add(new Field("boolean1", Schema.create(Type.BOOLEAN), null, null));
      fields.add(new Field("boolean2", Schema.create(Type.BOOLEAN), null, null));
  
      List<String> enumValues = new ArrayList<>();
      enumValues.add("One");
      enumValues.add("Two");
      Schema enumSchema = Schema.createEnum("myEnum", null, null, enumValues);
      fields.add(new Field("enum1", enumSchema, null, null));
      fields.add(new Field("enum2", enumSchema, null, null));
  
      Schema recordSchema = SchemaBuilder.record("aRecord").fields().requiredString("myString").endRecord();
      fields.add(new Field("record1", recordSchema, null, null));
      fields.add(new Field("record2", recordSchema, null, null));
  
      Schema arraySchema = Schema.createArray(Schema.create(Type.STRING));
      fields.add(new Field("array1", arraySchema, null, null));
      fields.add(new Field("array2", arraySchema, null, null));
  
      Schema mapSchema = Schema.createMap(Schema.create(Type.STRING));
      fields.add(new Field("map1", mapSchema, null, null));
      fields.add(new Field("map2", mapSchema, null, null));
  
      Schema schema = Schema.createRecord("Foo", "test", "mytest", false);
      schema.setFields(fields);
  
      Record testRecord = new Record(schema);
  
      testRecord.put("nullstring1", null);
      testRecord.put("nullstring2", null);
  
      String fortyTwo = "42";
      testRecord.put("string1", fortyTwo);
      testRecord.put("string2", fortyTwo);
      testRecord.put("bytes1", 0x42);
      testRecord.put("bytes2", 0x42);
      testRecord.put("int1", 42);
      testRecord.put("int2", 42);
      testRecord.put("long1", 42L);
      testRecord.put("long2", 42L);
      testRecord.put("float1", 42F);
      testRecord.put("float2", 42F);
      testRecord.put("double1", 42D);
      testRecord.put("double2", 42D);
      testRecord.put("boolean1", true);
      testRecord.put("boolean2", true);
  
      testRecord.put("enum1", "One");
      testRecord.put("enum2", "One");
  
      GenericRecord record = new GenericData.Record(recordSchema);
      record.put("myString", "42");
      testRecord.put("record1", record);
      testRecord.put("record2", record);
  
      GenericArray<String> array = new GenericData.Array<>(1, arraySchema);
      array.clear();
      array.add("42");
      testRecord.put("array1", array);
      testRecord.put("array2", array);
  
      Map<String, String> map = new HashMap<>();
      map.put("42", "42");
      testRecord.put("map1", map);
      testRecord.put("map2", map);
  
      String testString = testRecord.toString();
      assertFalse(testString.contains("CIRCULAR REFERENCE"),
          "Record with duplicated values results in wrong 'toString()'");
    }
  
    // Test copied from Apache Parquet:
    // org.apache.parquet.avro.TestCircularReferences
    @Test
    void toStringRecursive() throws IOException {
      ReferenceManager manager = new ReferenceManager();
      GenericData model = new GenericData();
      model.addLogicalTypeConversion(manager.getTracker());
      model.addLogicalTypeConversion(manager.getHandler());
  
      Schema parentSchema = Schema.createRecord("Parent", null, null, false);
  
      Schema placeholderSchema = Schema.createRecord("Placeholder", null, null, false);
      List<Schema.Field> placeholderFields = new ArrayList<>();
      placeholderFields.add( // at least one field is needed to be a valid schema
          new Schema.Field("id", Schema.create(Schema.Type.LONG), null, null));
      placeholderSchema.setFields(placeholderFields);
  
      Referenceable idRef = new Referenceable("id");
  
      Schema parentRefSchema = Schema.createUnion(Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.LONG),
          idRef.addToSchema(placeholderSchema));
  
      Reference parentRef = new Reference("parent");
  
      List<Schema.Field> childFields = new ArrayList<>();
      childFields.add(new Schema.Field("c", Schema.create(Schema.Type.STRING), null, null));
      childFields.add(new Schema.Field("parent", parentRefSchema, null, null));
      Schema childSchema = parentRef.addToSchema(Schema.createRecord("Child", null, null, false, childFields));
  
      List<Schema.Field> parentFields = new ArrayList<>();
      parentFields.add(new Schema.Field("id", Schema.create(Schema.Type.LONG), null, null));
      parentFields.add(new Schema.Field("p", Schema.create(Schema.Type.STRING), null, null));
      parentFields.add(new Schema.Field("child", childSchema, null, null));
      parentSchema.setFields(parentFields);
  
      Schema schema = idRef.addToSchema(parentSchema);
  
      Record parent = new Record(schema);
      parent.put("id", 1L);
      parent.put("p", "parent data!");
  
      Record child = new Record(childSchema);
      child.put("c", "child data!");
      child.put("parent", parent);
  
      parent.put("child", child);
  
      try {
        assertNotNull(parent.toString()); // This should not fail with an infinite recursion (StackOverflowError)
      } catch (StackOverflowError e) {
        fail("StackOverflowError occurred");
      }
    }
  
    /**
     * check that GenericArray.reset() retains reusable elements and that
     * GenericArray.prune() cleans them up properly.
     */
    @Test
    void genericArrayPeek() {
      Schema elementSchema = SchemaBuilder.record("element").fields().requiredString("value").endRecord();
      Schema arraySchema = Schema.createArray(elementSchema);
  
      GenericRecord record = new GenericData.Record(elementSchema);
      record.put("value", "string");
  
      GenericArray<GenericRecord> list = new GenericData.Array<>(1, arraySchema);
      list.add(record);
  
      list.reset();
      assertTrue(record == list.peek());
  
      list.prune();
      assertNull(list.peek());
    }
  
  }
  