All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.snowflake.common.core.SFBinary Maven / Gradle / Ivy

There is a newer version: 5.1.4
Show newest version
package net.snowflake.common.core;

import com.amazonaws.util.Base16;
import com.amazonaws.util.Base64;

import java.util.Arrays;

/**
 * Represents binary values.
 *
 * Just a wrapper around a byte array.
 *
 * Instances of this class are immutable.
 *
 * @author mkember
 */
public class SFBinary
{
  /**
   * An empty SFBinary.
   */
  public static final SFBinary EMPTY = new SFBinary(new byte[]{});

  /**
   * Special SFBinary that is greater than all others.
   *
   * Specifically, MAXIMUM.compareTo(x) > 0 for all x != MAXIMUM. This value
   * has no byte array representation, so calling getBytes, toBase64, or concat
   * on it is NOT allowed. It does, however, have a special representation "Z",
   * so SFBinary.fromHex("Z") returns MAXIMUM, and MAXIMUM.toHex() returns "Z".
   */
  public static final SFBinary MAXIMUM = new SFBinary(null);

  private static final String MAXIMUM_HEX = "Z";

  // Used for validating hex-encoded strings.
  private static final byte[] HEX_TABLE;
  private static final byte INVALID = 0;
  private static final byte HEX_DIGIT = 1;
  private static final byte WHITESPACE = 2;

  static
  {
    // Initialize HEX_TABLE with 0-9, a-f, A-F, and whitespace.
    byte[] temp = new byte['f'+1];
    for (char c = '0'; c <= '9'; c++) {
      temp[c] = HEX_DIGIT;
    }
    for (char c = 'A'; c <= 'F'; c++) {
      temp[c] = HEX_DIGIT;
    }
    for (char c = 'a'; c <= 'f'; c++) {
      temp[c] = HEX_DIGIT;
    }
    temp[' '] = WHITESPACE;
    temp['\n'] = WHITESPACE;
    temp['\r'] = WHITESPACE;
    HEX_TABLE = temp;
  }

  private final byte[] bytes;

  /**
   * Constructs an SFBinary from a byte array.
   * @param bytes an byte array
   */
  public SFBinary(byte[] bytes)
  {
    this.bytes = bytes;
  }

  /**
   * Returns true if it's safe to call SFBinary.fromHex(str).
   *
   * This is meant for checking user input, so it allows spaces, newlines, and
   * carriage returns. It returns false for the special value "Z".
   * @param str a string
   * @return true if a string is a hexadecimal value otherwise false
   */
  public static boolean validHex(String str)
  {
    int count = 0;
    for (int i = 0; i < str.length(); i++) {
      char c = str.charAt(i);
      int type = c < HEX_TABLE.length ? HEX_TABLE[c] : INVALID;
      if (type == INVALID)
      {
        return false;
      }
      if (type == HEX_DIGIT)
      {
        count++;
      }
    }

    return count % 2 == 0;
  }

  /**
   * Creates an SFBinary by decoding a hex-encoded string (uppercase letters).
   *
   * Handles the special value "Z" by returning MAXIMUM.
   *
   * @param str a string
   * @return SFBinary
   * @throws IllegalArgumentException if the string is not hex-encoded.
   */
  public static SFBinary fromHex(String str)
  {
    if (str.equals(MAXIMUM_HEX))
    {
      return MAXIMUM;
    }
    if (!validHex(str))
    {
      throw new IllegalArgumentException("Invalid hex in '" + str + "'");
    }

    return new SFBinary(Base16.decode(str));
  }

  /**
   * Creates an SFBinary by decoding a Base64-encoded string (RFC 4648).
   *
   * @param str a string.
   * @return SFBinary
   * @throws IllegalArgumentException if the string is not Base64-encoded.
   */
  public static SFBinary fromBase64(String str)
  {
    return new SFBinary(Base64.decode(str));
  }

  /**
   * Returns the underlying byte array.
   * @return a byte array
   */
  public byte[] getBytes()
  {
    assert this != MAXIMUM;
    return bytes;
  }

  /**
   * Returns the length of the SFBinary in bytes.
   * @return byte length
   */
  public int length()
  {
    return bytes.length;
  }

  /**
   * Encodes the binary value as a hex string (uppercase letters).
   *
   * Handles MAXIMUM by returning the special value "Z".
   * @return a hexdecimal string
   */
  public String toHex()
  {
    if (this == MAXIMUM)
    {
      return MAXIMUM_HEX;
    }

    return Base16.encodeAsString(bytes);
  }

  /**
   * Encodes the binary value as a Base64 string (RFC 4648).
   * @return a base64 string
   */
  public String toBase64()
  {
    assert this != MAXIMUM;
    return Base64.encodeAsString(bytes);
  }

  /**
   * Returns a new SFBinary that is a substring of this SFBinary.
   *
   * Same semantics as String.substring: 'start' is inclusive, 'end' is
   * exclusive, and 'start' cannot be greater than 'end'.
   * @param start the starting index of byte array
   * @param end the ending index of byte array
   * @return SFBinary
   */
  public SFBinary substring(int start, int end)
  {
    if (start == end)
    {
      return EMPTY;
    }

    return new SFBinary(Arrays.copyOfRange(bytes, start, end));
  }

  /**
   * Concatenates two binary values.
   *
   * Concatenates the bytes of this SFBinary with the bytes of the other
   * SFBinary, returning a new SFBinary instance.
   * @param other SFBinary to append
   * @return concatenated SFBinary
   */
  public SFBinary concat(SFBinary other)
  {
    assert this != MAXIMUM;
    assert other != MAXIMUM;

    byte[] result = Arrays.copyOf(bytes, bytes.length + other.bytes.length);
    System.arraycopy(other.bytes, 0,        // source
                     result, bytes.length,  // destination
                     other.bytes.length);   // length
    return new SFBinary(result);
  }

  /**
   * Compares SFBinary
   * @param other the target SFBinary
   * @return 1 if this SFBinary is larger, 0 if identical otherwise -1
   */
  public int compareTo(SFBinary other)
  {
    if (this == MAXIMUM && other == MAXIMUM)
    {
      // This logic is correct for most cases. For example, when choosing the
      // larger of two EP maxes, and both are Z, it doesn't matter what this
      // returns. It *would* be a problem if min and max were both Z and this
      // returned 0 (implying the expression is constant), but that comparison
      // will never happen, because XP never produces Z for the min.
      return 0;
    }
    if (this == MAXIMUM)
    {
      return 1;
    }
    else if (other == MAXIMUM)
    {
      return -1;
    }

    // Compare the byte arrays lexicographically.
    for (int i = 0; i < bytes.length && i < other.bytes.length; i++)
    {
      int a = bytes[i] & 0xFF;
      int b = other.bytes[i] & 0xFF;
      if (a > b)
      {
        return 1;
      }
      else if (a < b)
      {
        return -1;
      }
    }

    return bytes.length - other.bytes.length;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int hashCode()
  {
    if (this == MAXIMUM)
    {
      return 0;
    }

    return Arrays.hashCode(bytes);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean equals(Object other)
  {
    if (this == MAXIMUM || other == MAXIMUM)
    {
      return this == MAXIMUM && other == MAXIMUM;
    }

    return other instanceof SFBinary
        && Arrays.equals(bytes, ((SFBinary)other).bytes);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString()
  {
    return "SFBinary(hex=" + toHex() + ")";
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy