Segmented.java

package com.maybeitssquid.sensitive;

import java.util.Arrays;

/**
 * A {@link Sensitive} value that backed by multiple segments of the same type.
 *
 * @param <T> the type of the individual segments. This type should have well-defined {@link
 *     Object#equals(Object)} and {@link Object#hashCode()} methods so that the corresponding
 *     methods on this class meets the contract.
 */
public class Segmented<T> extends Sensitive<T[]> {

  /**
   * Creates a new Segmented container with the specified array. The array is cloned to prevent
   * external mutation.
   *
   * @param value the array of sensitive values; must not be {@code null}
   * @throws NullPointerException if value is {@code null}
   */
  public Segmented(final T[] value) {
    super(value.clone());
  }

  /**
   * Returns a clone of the contained array to prevent external mutation. This override provides
   * additional protection for array-backed sensitive data.
   *
   * @return a clone of the contained array, or {@code null} if the contained value is null
   */
  @Override
  protected final T[] getValue() {
    final T[] value = this.supplier.get();
    return value == null ? null : value.clone();
  }

  /**
   * Returns the value of the contained array at the specified index. Equivalent to {@code
   * getValue()[index]}, adding null and bounds checking and avoiding the clone.
   *
   * @param index the index into the contained array.
   * @return the value of the contained array at the specified index, or {@code null} if the
   *     contained value is null or the index is out of range.
   */
  protected final T getValue(final int index) {
    final T[] value = this.supplier.get();
    return value == null || index < 0 || index >= value.length ? null : value[index];
  }

  /**
   * Returns the hash of the enclosed {@code raw} data as generated by invoking {@link
   * Arrays#hashCode(Object[])} on the contained raw data.
   *
   * @return {@inheritDoc}
   */
  @Override
  public int hashCode() {
    return Arrays.hashCode(this.supplier.get());
  }

  /**
   * Returns true if the types match and the enclosed raw data are equal as indicated by invoking
   * {@link Arrays#equals(Object[], Object[])}.
   *
   * @param o {@inheritDoc}
   * @return {@inheritDoc}
   */
  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Segmented<?> other = (Segmented<?>) o;

    return Arrays.equals(this.supplier.get(), other.supplier.get());
  }
}