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.cassandra.sidecar.common.response;
  
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.List;
  
  import org.jetbrains.annotations.NotNull;
  import org.jetbrains.annotations.Nullable;
  
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.DC;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.DISK_USAGE;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.GENERATION;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.HEARTBEAT;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.HOST_ID;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.INDEX_STATUS;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.INTERNAL_ADDRESS_AND_PORT;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.INTERNAL_IP;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.LOAD;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.NATIVE_ADDRESS_AND_PORT;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.NET_VERSION;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.RACK;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.RELEASE_VERSION;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.REMOVAL_COORDINATOR;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.RPC_ADDRESS;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.RPC_READY;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.SCHEMA;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.SEVERITY;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.SSTABLE_VERSIONS;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.STATUS;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.STATUS_WITH_PORT;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.TOKENS;
  import static org.apache.cassandra.sidecar.common.response.GossipInfoResponse.GossipField.read;
  
  /**
   * A class representing the response of the cassandra gossip endpoint
   */
  public class GossipInfoResponse extends HashMap<String, GossipInfoResponse.GossipInfo>
  {
      /**
       * Overrides the {@link #get(Object)} method. The gossip info keys usually start with the format
       * {@code /ip:port}. Some clients may be unaware of the preceding {@code slash}, and lookups can
       * fail. This method attempts to lookup the value by prepending the {@code slash} at the beginning.
       * If the lookup fails, it defaults to the original behavior.
       *
       * @param key the key whose associated value is to be returned
       * @return {@link GossipInfo}
       */
      @Override
      public GossipInfo get(Object key)
      {
          if (key instanceof String)
          {
              String keyAsString = (String) key;
              if (keyAsString.length() > 0 && keyAsString.charAt(0) != '/')
              {
                  GossipInfo value = super.get("/" + key);
                  if (value != null)
                  {
                      return value;
                  }
              }
          }
          return super.get(key);
      }
  
      /**
       * Data accessor for reading Gossip states
       */
      public static class GossipInfo extends HashMap<String, String>
      {
          /**
           * Converts the key, if it is using the UPPER UNDERSCORE format, into camel case.
           * Then, put the new key and value.
           *
           * @param key   the key to convert
           * @param value the value for the gossip info entry
           */
          public void camelizeKeyAndPut(String key, String value)
          {
              String lowerCamelCasedKey = GossipField.valueOf(key.toUpperCase()).toLowerCamelCase();
              super.put(lowerCamelCasedKey, value);
          }
  
          @NotNull
          public String generation()
          {
              return read(this, GENERATION);
          }
  
          @NotNull
          public String heartbeat()
          {
              return read(this, HEARTBEAT);
          }
  
          @Nullable
          public String status()
          {
              return read(this, STATUS);
          }
  
          @Nullable
          public String load()
          {
              return read(this, LOAD);
          }
  
          /**
           * @return schema version, uuid string
           */
          @Nullable
          public String schema()
          {
              return read(this, SCHEMA);
          }
  
          /**
           * @return datacenter name
           */
          @Nullable
          public String datacenter()
          {
              return read(this, DC);
          }
  
          /**
           * @return rack name
           */
          @Nullable
          public String rack()
          {
              return read(this, RACK);
          }
  
          @Nullable
          public String releaseVersion()
          {
              return read(this, RELEASE_VERSION);
          }
  
          @Nullable
          public String removalCoordinator()
          {
              return read(this, REMOVAL_COORDINATOR);
          }
  
          @Nullable
          public String internalIp()
          {
              return read(this, INTERNAL_IP);
          }
  
          @Nullable
          public String rpcAddress()
          {
              return read(this, RPC_ADDRESS);
          }
  
          @Nullable
          public String severity()
          {
              return read(this, SEVERITY);
          }
  
          @Nullable
          public String netVersion()
          {
              return read(this, NET_VERSION);
          }
  
          @Nullable
          public String hostId()
          {
              return read(this, HOST_ID);
          }
  
          @Nullable
          public String indexStatus()
          {
              return read(this, INDEX_STATUS);
          }
  
          @Nullable
          public String tokens()
          {
              return read(this, TOKENS);
          }
  
          @Nullable
          public Boolean rpcReady()
          {
              String value = read(this, RPC_READY);
              return value == null ? null : Boolean.valueOf(value);
          }
  
          @Nullable
          public String internalAddressAndPort()
          {
              return read(this, INTERNAL_ADDRESS_AND_PORT);
          }
  
          @Nullable
          public String nativeAddressAndPort()
          {
              return read(this, NATIVE_ADDRESS_AND_PORT);
          }
  
          @Nullable
          public String statusWithPort()
          {
              return read(this, STATUS_WITH_PORT);
          }
  
          @Nullable
          public List<String> sstableVersions()
          {
              String value = read(this, SSTABLE_VERSIONS);
              return value == null ? null : Arrays.asList(value.split(","));
          }
  
          @Nullable
          public String diskUsage()
          {
              return read(this, DISK_USAGE);
          }
      }
  
      /**
       * Declares all fields that gossip info can possibly contain.
       *
       * <p>Note: When adding a new field, make sure there is a pairing access method defined in {@link GossipInfo}.
       */
      protected enum GossipField
      {
          GENERATION,
          HEARTBEAT,
  
          // Below are copied from org.apache.cassandra.gms.ApplicationState
          // Note: that all padding fields are not included.
          @Deprecated STATUS, //Deprecated and unused in 4.0, stop publishing in 5.0, reclaim in 6.0
          LOAD,
          SCHEMA,
          DC,
          RACK,
          RELEASE_VERSION,
          REMOVAL_COORDINATOR,
          @Deprecated INTERNAL_IP, //Deprecated and unused in 4.0, stop publishing in 5.0, reclaim in 6.0
          @Deprecated RPC_ADDRESS, // ^ Same
          SEVERITY,
          NET_VERSION,
          HOST_ID,
          TOKENS,
          RPC_READY,
          // pad to allow adding new states to existing cluster
          INTERNAL_ADDRESS_AND_PORT, //Replacement for INTERNAL_IP with up to two ports
          NATIVE_ADDRESS_AND_PORT, //Replacement for RPC_ADDRESS
          STATUS_WITH_PORT, //Replacement for STATUS
          /**
           * The set of sstable versions on this node. This will usually be only the "current" sstable format (the one
           * with which new sstables are written), but may contain more on newly upgraded nodes before `upgradesstable`
           * has been run.
           *
           * <p>The value (a set of sstable {@code org.apache.cassandra.io.sstable.format.VersionAndType}) is serialized
           * as a comma-separated list.
           **/
          SSTABLE_VERSIONS,
          DISK_USAGE,
          INDEX_STATUS, // Introduced in Cassandra 5.0 for SAI 
          ;
  
          static String read(GossipInfo gossipInfo, GossipField field)
          {
              return gossipInfo.get(field.toLowerCamelCase());
          }
  
          /**
           * Returns the lower case camel version for the given {@code upperUnderscoreCase}. This method allows us not to
           * add a dependency to guava for the client, because guava can cause a lot of conflicts when used in libraries.
           *
           * @return the lower case camel version for the given {@code upperUnderscoreCase}
           */
          String toLowerCamelCase()
          {
              String upperUnderscoreCase = name();
  
              StringBuilder sb = new StringBuilder(upperUnderscoreCase.length());
              sb.append(Character.toLowerCase(upperUnderscoreCase.charAt(0)));
  
              int length = upperUnderscoreCase.length();
              for (int i = 1; i < length; i++)
              {
                  if (upperUnderscoreCase.charAt(i) == '_' && i + 1 < length)
                  {
                      i++;
                      sb.append(upperUnderscoreCase.charAt(i));
                  }
                  else
                  {
                      sb.append(Character.toLowerCase(upperUnderscoreCase.charAt(i)));
                  }
              }
  
              return sb.toString();
          }
      }
  }
  