ASCIIFilter.java

package com.maybeitssquid.safeascii.internal;

import static com.maybeitssquid.safeascii.internal.Chainable.ASCII;

import java.util.HashSet;
import java.util.Set;
import java.util.function.IntFunction;

/**
 * A filter function that selectively preserves ASCII characters.
 *
 * <p>This class processes Unicode codepoints, retaining them only if they are within the ASCII
 * range (codepoints less than 128) and do not belong to any of the blocked Unicode categories
 * defined at construction (see {@link Character#getType(int)}). Codepoints that do not meet these
 * criteria are transformed into an empty string.
 */
public class ASCIIFilter implements IntFunction<CharSequence> {
  private final Set<Integer> blockTypes;

  /**
   * Create an ASCII filter that blocks characters of the specified Unicode categories.
   *
   * <p>To create an instance that allows all ASCII characters, use:
   *
   * {@snippet :
   *   ASCIIFilter ascii = new ASCIIFilter();
   * }
   *
   * <p>To create an instance that disallows ASCII control characters, use:
   *
   * {@snippet :
   *   ASCIIFilter blockControls = new ASCIIFilter(Character.CONTROL);
   * }
   *
   * @param block Unicode categories to block
   * @see Character#getType(int)
   */
  public ASCIIFilter(final byte... block) {
    if (block == null || block.length == 0) {
      blockTypes = Set.of();
    } else {
      final Set<Integer> tmp = new HashSet<>();
      for (byte b : block) {
        tmp.add((int) b);
      }
      blockTypes = Set.copyOf(tmp);
    }
  }

  /**
   * Processes a Unicode codepoint.
   *
   * @param value the codepoint to process.
   * @return a string containing the character if it is a permitted ASCII character; otherwise, an
   *     empty string.
   */
  @Override
  public CharSequence apply(final int value) {
    return value < ASCII && !blockTypes.contains(Character.getType(value))
        ? Character.toString(value)
        : "";
  }
}