record (int codePoint, long chars) { String charString() { return BitSet.valueOf(new long[]{chars}).stream().mapToObj(bit -> Character.toString((char)(bit + 'a'))) .collect(Collectors.joining()); } static Mapping merge(Mapping m1, Mapping m2) { if (m1.codePoint != m2.codePoint) throw new IllegalArgumentException(); return new Mapping(m1.codePoint, m1.chars | m2.chars); } static Mapping parseUniHan(String line) { if (line.startsWith("#")) return null; String[] parts = line.split("\\s+"); if (parts.length != 3) return null; if (!parts[0].startsWith("U+")) return null; int codePoint = Integer.parseInt(parts[0].substring(2), 16); if (codePoint < PinyinMatcher.BASE_CODE_POINT) return null; // Codepoints outside BMP are not supported for now if (codePoint > 0xA000) return null; String[] readings; switch (parts[1]) { case "kMandarin" -> readings = new String[]{parts[2]}; case "kHanyuPinyin" -> { int colonPos = parts[2].indexOf(':'); if (colonPos == -1) return null; readings = parts[2].substring(colonPos + 1).split(","); } default -> { return null; } } long encoded = 0; for (String reading : readings) { char initial = Normalizer.normalize(reading, Normalizer.Form.NFKD).charAt(0); encoded |= 1L << (initial - 'a'); } return new Mapping(codePoint, encoded); } @Override public String toString() { return String.format("%04X: %s", codePoint, charString()); } }