/**
 * Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com)
 *
 * Licensed 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 com.linkedin.pinot.common.data;

import com.google.common.base.Preconditions;
import com.linkedin.pinot.common.utils.EqualityUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;


/**
 * The <code>MetricFieldSpec</code> class contains all specs related to any metric field (column) in {@link Schema}.
 * <p>Different with {@link DimensionFieldSpec}, inside <code>MetricFieldSpec</code> we allow user defined
 * {@link DerivedMetricType} and <code>fieldSize</code>.
 * <p>{@link DerivedMetricType} is used when the metric field is derived from some other fields (e.g. HLL).
 * <p><code>fieldSize</code> is used to mark the size of the value when the size is not constant (e.g. STRING).
 */
@JsonIgnoreProperties(ignoreUnknown = true)
public final class MetricFieldSpec extends FieldSpec {
  private static final int UNDEFINED_FIELD_SIZE = -1;

  // These two fields are for derived metric fields.
  private int _fieldSize = UNDEFINED_FIELD_SIZE;
  private DerivedMetricType _derivedMetricType = null;

  // Default constructor required by JSON de-serializer. DO NOT REMOVE.
  public MetricFieldSpec() {
    super();
  }

  public MetricFieldSpec(@Nonnull String name, @Nonnull DataType dataType) {
    super(name, dataType, true);
  }

  public MetricFieldSpec(@Nonnull String name, @Nonnull DataType dataType, @Nonnull Object defaultNullValue) {
    super(name, dataType, true, defaultNullValue);
  }

  // For derived metric fields.
  public MetricFieldSpec(@Nonnull String name, @Nonnull DataType dataType, int fieldSize,
      @Nonnull DerivedMetricType derivedMetricType) {
    super(name, dataType, true);
    Preconditions.checkArgument(fieldSize > 0, "Field size must be a positive number.");
    _fieldSize = fieldSize;
    _derivedMetricType = derivedMetricType;
  }

  // For derived metric fields.
  public MetricFieldSpec(@Nonnull String name, @Nonnull DataType dataType, int fieldSize,
      @Nonnull DerivedMetricType derivedMetricType, @Nonnull Object defaultNullValue) {
    super(name, dataType, true, defaultNullValue);
    Preconditions.checkArgument(fieldSize > 0, "Field size must be a positive number.");
    _fieldSize = fieldSize;
    _derivedMetricType = derivedMetricType;
  }

  public int getFieldSize() {
    if (_fieldSize == UNDEFINED_FIELD_SIZE) {
      return getDataType().size();
    } else {
      return _fieldSize;
    }
  }

  // Required by JSON de-serializer. DO NOT REMOVE.
  public void setFieldSize(int fieldSize) {
    _fieldSize = fieldSize;
  }

  @Nullable
  public DerivedMetricType getDerivedMetricType() {
    return _derivedMetricType;
  }

  // Required by JSON de-serializer. DO NOT REMOVE.
  public void setDerivedMetricType(@Nonnull DerivedMetricType derivedMetricType) {
    _derivedMetricType = derivedMetricType;
  }

  @JsonIgnore
  @Nonnull
  @Override
  public FieldType getFieldType() {
    return FieldType.METRIC;
  }

  @Override
  public void setSingleValueField(boolean isSingleValueField) {
    Preconditions.checkArgument(isSingleValueField, "Unsupported multi-value for metric field.");
  }

  @JsonIgnore
  public boolean isDerivedMetric() {
    return _derivedMetricType != null;
  }

  /**
   * The <code>DerivedMetricType</code> enum is assigned for all metric fields to allow derived metric field, a
   * customized type which is not included in DataType.
   * <p>It is currently used for derived field recognition in star tree <code>MetricBuffer</code>, may have other use
   * cases later.
   * <p>Generally, a customized type value should be converted to a standard
   * {@link com.linkedin.pinot.common.data.FieldSpec.DataType} for storage, and converted back when needed.
   */
  public enum DerivedMetricType {
    // HLL derived metric type.
    HLL
  }

  @Override
  public String toString() {
    return "< field type: METRIC, field name: " + getName() + ", data type: " + getDataType() + ", default null value: "
        + getDefaultNullValue() + ", field size: " + getFieldSize() + ", derived metric type: " + _derivedMetricType
        + " >";
  }

  @Override
  public boolean equals(Object object) {
    if (this == object) {
      return true;
    }
    if (object instanceof MetricFieldSpec) {
      MetricFieldSpec that = (MetricFieldSpec) object;

      return getName().equals(that.getName())
          && getDataType() == that.getDataType()
          && getDefaultNullValue().equals(that.getDefaultNullValue())
          && getFieldSize() == that.getFieldSize()
          && _derivedMetricType == that._derivedMetricType;
    }
    return false;
  }

  @Override
  public int hashCode() {
    int result = getName().hashCode();
    result = EqualityUtils.hashCodeOf(result, getDataType());
    result = EqualityUtils.hashCodeOf(result, getDefaultNullValue());
    result = EqualityUtils.hashCodeOf(result, getFieldSize());
    result = EqualityUtils.hashCodeOf(result, _derivedMetricType);
    return result;
  }
}

@@ -3450,22 +3450,23 @@
 cType(@N
-onn
 ull
+able
  Derived

Fix the setter argument @Nonnull annotation for derivedMetricType (#1428)