UsTIN.java

package com.maybeitssquid.tin.us;

import com.maybeitssquid.sensitive.Renderer;
import com.maybeitssquid.sensitive.Renderers;
import com.maybeitssquid.sensitive.Segmented;
import com.maybeitssquid.tin.NationalTIN;
import java.util.Locale;

/**
 * Abstract base class for United States Taxpayer Identification Numbers.
 *
 * <p>This class provides common functionality for US TINs including:
 *
 * <ul>
 *   <li>Segment-based validation and parsing
 *   <li>Masked rendering with delimiter preservation
 *   <li>Factory method for parsing ambiguous TIN formats
 * </ul>
 *
 * <p>Subclasses define the specific segment structure for their TIN type. Currently supported types
 * are {@link SSN} and {@link EIN}.
 *
 * <h2>Rendering</h2>
 *
 * <p>By default, US TINs are rendered with the leading digits masked (no delimiters). The alternate
 * form ({@code %#s}) renders with delimiters preserved:
 *
 * <pre>{@code
 * UsTIN tin = new SSN("123-45-6789");
 * String.format("%s", tin);   // "#####6789"
 * String.format("%#s", tin);  // "###-##-6789"
 * }</pre>
 *
 * @see SSN
 * @see EIN
 * @see NationalTIN
 */
public abstract class UsTIN extends Segmented<CharSequence> implements NationalTIN {

  /**
   * Create a named regex group that matches an exact number of digits.
   *
   * @param name the name of the regex group
   * @param length the number of digits to match
   * @return a named regex group that matches exactly {@code length} digits
   */
  public static String digits(final String name, final int length) {
    return "(?<%s>\\d{%d})".formatted(name, length);
  }

  /** The delimiter character used between TIN segments. */
  public static final char DELIMITER = '-';

  private static final Renderer<CharSequence[]> MASKED = Renderers.concatenate(Renderers.mask());
  private static final Renderer<CharSequence[]> MASKED_DELIMITED = Renderers.delimit(DELIMITER);

  /**
   * Creates a new US TIN with the specified segments.
   *
   * @param segments the validated segment strings
   */
  protected UsTIN(final CharSequence... segments) {
    super(segments);
  }

  @Override
  protected Renderer<CharSequence[]> getRenderer() {
    return MASKED;
  }

  @Override
  protected Renderer<CharSequence[]> getAltRenderer() {
    return MASKED_DELIMITED;
  }

  @Override
  public Locale issuer() {
    return Locale.US;
  }
}