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 java.io.IOException;
  
  import org.apache.avro.AvroRuntimeException;
  import org.apache.avro.Schema;
  import org.apache.avro.Schema.Field;
  import org.apache.avro.data.RecordBuilderBase;
  import org.apache.avro.generic.GenericData.Record;
  
  /**
   * A RecordBuilder for generic records. GenericRecordBuilder fills in default
   * values for fields if they are not specified.
   */
  public class GenericRecordBuilder extends RecordBuilderBase<Record> {
    private final GenericData.Record record;
  
    /**
     * Creates a GenericRecordBuilder for building Record instances.
     * 
     * @param schema the schema associated with the record class.
     */
    public GenericRecordBuilder(Schema schema) {
      super(schema, GenericData.get());
      record = new GenericData.Record(schema);
    }
  
    /**
     * Creates a GenericRecordBuilder by copying an existing GenericRecordBuilder.
     * 
     * @param other the GenericRecordBuilder to copy.
     */
    public GenericRecordBuilder(GenericRecordBuilder other) {
      super(other, GenericData.get());
      record = new GenericData.Record(other.record, /* deepCopy = */ true);
    }
  
    /**
     * Creates a GenericRecordBuilder by copying an existing record instance.
     * 
     * @param other the record instance to copy.
     */
    public GenericRecordBuilder(Record other) {
      super(other.getSchema(), GenericData.get());
      record = new GenericData.Record(other, /* deepCopy = */ true);
  
      // Set all fields in the RecordBuilder that are set in the record
      for (Field f : schema().getFields()) {
        Object value = other.get(f.pos());
        // Only set the value if it is not null, if the schema type is null,
        // or if the schema type is a union that accepts nulls.
        if (isValidValue(f, value)) {
          set(f, data().deepCopy(f.schema(), value));
        }
      }
    }
  
    /**
     * Gets the value of a field.
     * 
     * @param fieldName the name of the field to get.
     * @return the value of the field with the given name, or null if not set.
     */
    public Object get(String fieldName) {
      return get(schema().getField(fieldName));
    }
  
    /**
     * Gets the value of a field.
     * 
     * @param field the field to get.
     * @return the value of the given field, or null if not set.
     */
    public Object get(Field field) {
      return get(field.pos());
    }
  
    /**
     * Gets the value of a field.
     * 
     * @param pos the position of the field to get.
     * @return the value of the field with the given position, or null if not set.
     */
    protected Object get(int pos) {
      return record.get(pos);
    }
  
    /**
     * Sets the value of a field.
     * 
     * @param fieldName the name of the field to set.
     * @param value     the value to set.
     * @return a reference to the RecordBuilder.
     */
    public GenericRecordBuilder set(String fieldName, Object value) {
      return set(schema().getField(fieldName), value);
    }
  
    /**
     * Sets the value of a field.
     * 
     * @param field the field to set.
     * @param value the value to set.
     * @return a reference to the RecordBuilder.
     */
    public GenericRecordBuilder set(Field field, Object value) {
      return set(field, field.pos(), value);
    }
  
    /**
     * Sets the value of a field.
     * 
     * @param pos   the field to set.
     * @param value the value to set.
     * @return a reference to the RecordBuilder.
     */
    protected GenericRecordBuilder set(int pos, Object value) {
      return set(fields()[pos], pos, value);
    }
  
    /**
     * Sets the value of a field.
     * 
     * @param field the field to set.
     * @param pos   the position of the field.
     * @param value the value to set.
     * @return a reference to the RecordBuilder.
     */
    private GenericRecordBuilder set(Field field, int pos, Object value) {
      validate(field, value);
      record.put(pos, value);
      fieldSetFlags()[pos] = true;
      return this;
    }
  
    /**
     * Checks whether a field has been set.
     * 
     * @param fieldName the name of the field to check.
     * @return true if the given field is non-null; false otherwise.
     */
    public boolean has(String fieldName) {
      return has(schema().getField(fieldName));
    }
  
    /**
     * Checks whether a field has been set.
     * 
     * @param field the field to check.
     * @return true if the given field is non-null; false otherwise.
     */
    public boolean has(Field field) {
      return has(field.pos());
    }
  
    /**
     * Checks whether a field has been set.
     * 
     * @param pos the position of the field to check.
     * @return true if the given field is non-null; false otherwise.
     */
    protected boolean has(int pos) {
      return fieldSetFlags()[pos];
    }
  
    /**
     * Clears the value of the given field.
     * 
     * @param fieldName the name of the field to clear.
     * @return a reference to the RecordBuilder.
     */
    public GenericRecordBuilder clear(String fieldName) {
      return clear(schema().getField(fieldName));
    }
  
    /**
     * Clears the value of the given field.
     * 
     * @param field the field to clear.
     * @return a reference to the RecordBuilder.
     */
    public GenericRecordBuilder clear(Field field) {
      return clear(field.pos());
    }
  
    /**
     * Clears the value of the given field.
     * 
     * @param pos the position of the field to clear.
     * @return a reference to the RecordBuilder.
     */
    protected GenericRecordBuilder clear(int pos) {
      record.put(pos, null);
      fieldSetFlags()[pos] = false;
      return this;
    }
  
    @Override
    public Record build() {
      Record record;
      try {
        record = new GenericData.Record(schema());
      } catch (Exception e) {
        throw new AvroRuntimeException(e);
      }
  
      for (Field field : fields()) {
        Object value;
        try {
          value = getWithDefault(field);
        } catch (IOException e) {
          throw new AvroRuntimeException(e);
        }
        if (value != null) {
          record.put(field.pos(), value);
        }
      }
  
      return record;
    }
  
    /**
     * Gets the value of the given field. If the field has been set, the set value
     * is returned (even if it's null). If the field hasn't been set and has a
     * default value, the default value is returned.
     * 
     * @param field the field whose value should be retrieved.
     * @return the value set for the given field, the field's default value, or
     *         null.
     * @throws IOException
     */
    private Object getWithDefault(Field field) throws IOException {
      return fieldSetFlags()[field.pos()] ? record.get(field.pos()) : defaultValue(field);
    }
  
    @Override
    public int hashCode() {
      final int prime = 31;
      int result = super.hashCode();
      result = prime * result + ((record == null) ? 0 : record.hashCode());
      return result;
    }
  
    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (!super.equals(obj))
        return false;
      if (getClass() != obj.getClass())
        return false;
      GenericRecordBuilder other = (GenericRecordBuilder) obj;
      if (record == null) {
        return other.record == null;
      } else
        return record.equals(other.record);
    }
  }
  