CaselessMatches.java

/*
 * Copyright (C) 2009-2013 Steve Rowe <sarowe@gmail.com>
 * Copyright (C) 2019-2020 Google, LLC.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
package de.jflex.ucd_generator.ucd;

import static de.jflex.ucd_generator.util.HexaUtils.intFromHexa;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

public class CaselessMatches {
  /**
   * A set of code point space partitions, each containing at least two caselessly equivalent code
   * points.
   */
  public final Map<Integer, SortedSet<Integer>> caselessMatchPartitions = new HashMap<>();

  public int maxCaselessMatchPartitionSize() {
    return caselessMatchPartitions.values().stream()
        .map(Set::size)
        .max(Integer::compareTo)
        .orElse(0);
  }

  /**
   * Returns the {@link #caselessMatchPartitions} where the key is the first element from the
   * partition.
   */
  public ImmutableCollection<SortedSet<Integer>> uniqueCaselessMatchPartitions() {
    ArrayList<SortedSet<Integer>> partitions = new ArrayList<>();
    for (Map.Entry<Integer, SortedSet<Integer>> entry : caselessMatchPartitions.entrySet()) {
      if (entry.getKey().equals(entry.getValue().first())) {
        partitions.add(entry.getValue());
      }
    }
    Comparator<SortedSet<Integer>> comparator = Comparator.comparingInt(SortedSet::first);
    return ImmutableList.sortedCopyOf(comparator, partitions);
  }

  /**
   * Grows the partition containing the given codePoint and its caseless equivalents, if any, to
   * include all of them.
   *
   * @param codePoint The code point to include in a caselessly equivalent partition
   * @param uppercaseMapping A hex String representation of the uppercase mapping of codePoint, or
   *     {@code null} if there isn't one
   * @param lowercaseMapping A hex String representation of the lowercase mapping of codePoint, or
   *     {@code null} if there isn't one
   * @param titlecaseMapping A hex String representation of the titlecase mapping of codePoint, or
   *     {@code null} if there isn't one
   */
  public void addCaselessMatches(
      int codePoint, String uppercaseMapping, String lowercaseMapping, String titlecaseMapping) {
    if (Strings.isNullOrEmpty(uppercaseMapping)
        && Strings.isNullOrEmpty(lowercaseMapping)
        && Strings.isNullOrEmpty(titlecaseMapping)) {
      return;
    }

    List<Integer> codepoints =
        Arrays.asList(
            codePoint,
            intFromHexa(uppercaseMapping),
            intFromHexa(lowercaseMapping),
            intFromHexa(titlecaseMapping));
    SortedSet<Integer> partition =
        codepoints.stream()
            .filter(Objects::nonNull)
            .map(caselessMatchPartitions::get)
            .filter(Objects::nonNull)
            .findFirst()
            .orElse(new TreeSet<>());
    for (Integer cp : codepoints) {
      if (cp != null) {
        partition.add(cp);
        caselessMatchPartitions.put(cp, partition);
      }
    }
  }
}