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.kafka.common.feature;
  
  import org.apache.kafka.common.utils.Utils;
  
  import java.util.Map;
  import java.util.Objects;
  
  import static java.util.stream.Collectors.joining;
  
  /**
   * Represents an immutable basic version range using 2 attributes: min and max, each of type short.
   * The min and max attributes need to satisfy 2 rules:
   *  - they are each expected to be >= 0, as we only consider non-negative version values to be valid.
   *  - max should be >= min.
   *
   * The class also provides API to convert the version range to a map.
   * The class allows for configurable labels for the min/max attributes, which can be specialized by
   * sub-classes (if needed).
   */
  class BaseVersionRange {
      // Non-empty label for the min version key, that's used only to convert to/from a map.
      private final String minKeyLabel;
  
      // The value of the minimum version.
      private final short minValue;
  
      // Non-empty label for the max version key, that's used only to convert to/from a map.
      private final String maxKeyLabel;
  
      // The value of the maximum version.
      private final short maxValue;
  
      /**
       * Raises an exception unless the following condition is met:
       * minValue >= 0 and maxValue >= 0 and maxValue >= minValue.
       *
       * @param minKeyLabel   Label for the min version key, that's used only to convert to/from a map.
       * @param minValue      The minimum version value.
       * @param maxKeyLabel   Label for the max version key, that's used only to convert to/from a map.
       * @param maxValue      The maximum version value.
       *
       * @throws IllegalArgumentException   If any of the following conditions are true:
       *                                     - (minValue < 0) OR (maxValue < 0) OR (maxValue < minValue).
       *                                     - minKeyLabel is empty, OR, minKeyLabel is empty.
       */
      protected BaseVersionRange(String minKeyLabel, short minValue, String maxKeyLabel, short maxValue) {
          if (minValue < 0 || maxValue < 0 || maxValue < minValue) {
              throw new IllegalArgumentException(
                  String.format(
                      "Expected minValue >= 0, maxValue >= 0 and maxValue >= minValue, but received" +
                      " minValue: %d, maxValue: %d", minValue, maxValue));
          }
          if (minKeyLabel.isEmpty()) {
              throw new IllegalArgumentException("Expected minKeyLabel to be non-empty.");
          }
          if (maxKeyLabel.isEmpty()) {
              throw new IllegalArgumentException("Expected maxKeyLabel to be non-empty.");
          }
          this.minKeyLabel = minKeyLabel;
          this.minValue = minValue;
          this.maxKeyLabel = maxKeyLabel;
          this.maxValue = maxValue;
      }
  
      public short min() {
          return minValue;
      }
  
      public short max() {
          return maxValue;
      }
  
      @Override
      public String toString() {
          return String.format(
              "%s[%s]",
              this.getClass().getSimpleName(),
              mapToString(toMap()));
      }
  
      public Map<String, Short> toMap() {
          return Utils.mkMap(Utils.mkEntry(minKeyLabel, min()), Utils.mkEntry(maxKeyLabel, max()));
      }
  
      private static String mapToString(final Map<String, Short> map) {
          return map
              .entrySet()
              .stream()
              .map(entry -> String.format("%s:%d", entry.getKey(), entry.getValue()))
              .collect(joining(", "));
      }
  
      @Override
      public boolean equals(Object other) {
          if (this == other) {
              return true;
          }
  
          if (other == null || getClass() != other.getClass()) {
              return false;
          }
  
          final BaseVersionRange that = (BaseVersionRange) other;
          return Objects.equals(this.minKeyLabel, that.minKeyLabel) &&
              this.minValue == that.minValue &&
              Objects.equals(this.maxKeyLabel, that.maxKeyLabel) &&
              this.maxValue == that.maxValue;
      }
  
      @Override
      public int hashCode() {
          return Objects.hash(minKeyLabel, minValue, maxKeyLabel, maxValue);
      }
  
      public static short valueOrThrow(String key, Map<String, Short> versionRangeMap) {
          final Short value = versionRangeMap.get(key);
          if (value == null) {
              throw new IllegalArgumentException(String.format("%s absent in [%s]", key, mapToString(versionRangeMap)));
          }
          return value;
      }
  }
  