/* GENERATED SOURCE. DO NOT MODIFY. */ // © 2022 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html package android.icu.impl.personname; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import android.icu.text.PersonName; /** * A single name formatting pattern, corresponding to a single namePattern element in CLDR. */ class PersonNamePattern { private String patternText; // for debugging private Element[] patternElements; public static PersonNamePattern[] makePatterns(String[] patternText, PersonNameFormatterImpl formatterImpl) { PersonNamePattern[] result = new PersonNamePattern[patternText.length]; for (int i = 0; i < patternText.length; i++) { result[i] = new PersonNamePattern(patternText[i], formatterImpl); } return result; } @Override public String toString() { return patternText; } private PersonNamePattern(String patternText, PersonNameFormatterImpl formatterImpl) { this.patternText = patternText; List elements = new ArrayList<>(); boolean inField = false; boolean inEscape = false; StringBuilder workingString = new StringBuilder(); for (int i = 0; i < patternText.length(); i++) { char c = patternText.charAt(i); if (inEscape) { workingString.append(c); inEscape = false; } else { switch (c) { case '\\': inEscape = true; break; case '{': if (!inField) { if (workingString.length() > 0) { elements.add(new LiteralText(workingString.toString())); workingString = new StringBuilder(); } inField = true; } else { throw new IllegalArgumentException("Nested braces are not allowed in name patterns"); } break; case '}': if (inField) { if (workingString.length() > 0) { elements.add(new NameFieldImpl(workingString.toString(), formatterImpl)); workingString = new StringBuilder(); } else { throw new IllegalArgumentException("No field name inside braces"); } inField = false; } else { throw new IllegalArgumentException("Unmatched closing brace in literal text"); } break; default: workingString.append(c); } } } if (workingString.length() > 0) { elements.add(new LiteralText(workingString.toString())); } this.patternElements = elements.toArray(new Element[0]); } public String format(PersonName name) { StringBuilder result = new StringBuilder(); boolean seenLeadingField = false; boolean seenEmptyLeadingField = false; boolean seenEmptyField = false; StringBuilder textBefore = new StringBuilder(); StringBuilder textAfter = new StringBuilder(); // if the name doesn't have a surname field and the pattern doesn't have a given-name field, // we actually format a modified version of the name object where the contents of the // given-name field has been copied into the surname field name = hackNameForEmptyFields(name); // the logic below attempts to implement the following algorithm: // - If one or more fields at the beginning of the name are empty, also skip all literal text // from the beginning of the name up to the first populated field. // - If one or more fields at the end of the name are empty, also skip all literal text from // the last populated field to the end of the name. // - If one or more contiguous fields in the middle of the name are empty, skip the literal text // between them, omit characters from the literal text on either side of the empty fields up to // the first space on either side, and make sure that the resulting literal text doesn't end up // with two spaces in a row. for (Element element : patternElements) { if (element.isLiteral()) { if (seenEmptyLeadingField) { // do nothing; throw away the literal text } else if (seenEmptyField) { textAfter.append(element.format(name)); } else { textBefore.append(element.format(name)); } } else { String fieldText = element.format(name); if (fieldText == null || fieldText.isEmpty()) { if (!seenLeadingField) { seenEmptyLeadingField = true; textBefore.setLength(0); } else { seenEmptyField = true; textAfter.setLength(0); } } else { seenLeadingField = true; seenEmptyLeadingField = false; if (seenEmptyField) { result.append(coalesce(textBefore, textAfter)); result.append(fieldText); seenEmptyField = false; } else { result.append(textBefore); textBefore.setLength(0); result.append(element.format(name)); } } } } if (!seenEmptyField) { result.append(textBefore); } return result.toString(); } public int numPopulatedFields(PersonName name) { int result = 0; for (Element element : patternElements) { result += element.isPopulated(name) ? 1 : 0; } return result; } public int numEmptyFields(PersonName name) { int result = 0; for (Element element : patternElements) { result += (!element.isLiteral() && !element.isPopulated(name)) ? 1 : 0; } return result; } /** * Stitches together the literal text on either side of an omitted field by deleting any * non-whitespace characters immediately neighboring the omitted field and coalescing any * adjacent spaces at the join point down to one. * @param s1 The literal text before the omitted field. * @param s2 The literal text after the omitted field. */ private String coalesce(StringBuilder s1, StringBuilder s2) { // if the contents of s2 occur at the end of s1, we just use s1 if (endsWith(s1, s2)) { s2.setLength(0); } // get the range of non-whitespace characters at the beginning of s1 int p1 = 0; while (p1 < s1.length() && !Character.isWhitespace(s1.charAt(p1))) { ++p1; } // get the range of non-whitespace characters at the end of s2 int p2 = s2.length() - 1; while (p2 >= 0 && !Character.isWhitespace(s2.charAt(p2))) { --p2; } // also include one whitespace character from s1 or, if there aren't // any, one whitespace character from s2 if (p1 < s1.length()) { ++p1; } else if (p2 >= 0) { --p2; } // concatenate those two ranges to get the coalesced literal text String result = s1.substring(0, p1) + s2.substring(p2 + 1); // clear out s1 and s2 (done here to improve readability in format() above)) s1.setLength(0); s2.setLength(0); return result; } /** * Returns true if s1 ends with s2. */ private boolean endsWith(StringBuilder s1, StringBuilder s2) { int p1 = s1.length() - 1; int p2 = s2.length() - 1; while (p1 >= 0 && p2 >= 0 && s1.charAt(p1) == s2.charAt(p2)) { --p1; --p2; } return p2 < 0; } private PersonName hackNameForEmptyFields(PersonName originalName) { // this is a hack to deal with mononyms (name objects that don't have both a given name and a surname)-- // if the name object has a given-name field but not a surname field and the pattern either doesn't // have a given-name field or only has "{given-initial}", we return a PersonName object that will // return the value of the given-name field when asked for the value of the surname field and that // will return null when asked for the value of the given-name field (all other field values and // properties of the underlying object are returned unchanged) PersonName result = originalName; if (originalName.getFieldValue(PersonName.NameField.SURNAME, Collections.emptySet()) == null) { boolean patternHasNonInitialGivenName = false; for (PersonNamePattern.Element element : patternElements) { if (!element.isLiteral() && ((NameFieldImpl)element).fieldID == PersonName.NameField.GIVEN && !((NameFieldImpl)element).modifiers.containsKey(PersonName.FieldModifier.INITIAL)) { patternHasNonInitialGivenName = true; break; } } if (!patternHasNonInitialGivenName) { return new GivenToSurnamePersonName(originalName); } } return result; } /** * A single element in a NamePattern. This is either a name field or a range of literal text. */ private interface Element { boolean isLiteral(); String format(PersonName name); boolean isPopulated(PersonName name); } /** * Literal text from a name pattern. */ private static class LiteralText implements Element { private String text; public LiteralText(String text) { this.text = text; } @Override public String toString() { return text; } public boolean isLiteral() { return true; } public String format(PersonName name) { return text; } public boolean isPopulated(PersonName name) { return false; } } /** * An actual name field in a NamePattern (i.e., the stuff represented in the pattern by text * in braces). This class actually handles fetching the value for the field out of a * PersonName object and applying any modifiers to it. */ private static class NameFieldImpl implements Element { private PersonName.NameField fieldID; private Map modifiers; public NameFieldImpl(String fieldNameAndModifiers, PersonNameFormatterImpl formatterImpl) { List modifierIDs = new ArrayList<>(); StringTokenizer tok = new StringTokenizer(fieldNameAndModifiers, "-"); this.fieldID = PersonName.NameField.forString(tok.nextToken()); while (tok.hasMoreTokens()) { modifierIDs.add(PersonName.FieldModifier.forString(tok.nextToken())); } if (this.fieldID == PersonName.NameField.SURNAME && formatterImpl.shouldCapitalizeSurname()) { modifierIDs.add(PersonName.FieldModifier.ALL_CAPS); } this.modifiers = new HashMap<>(); for (PersonName.FieldModifier modifierID : modifierIDs) { this.modifiers.put(modifierID, FieldModifierImpl.forName(modifierID, formatterImpl)); } if (this.modifiers.containsKey(PersonName.FieldModifier.RETAIN) && this.modifiers.containsKey(PersonName.FieldModifier.INITIAL)) { FieldModifierImpl.InitialModifier initialModifier = (FieldModifierImpl.InitialModifier) this.modifiers.get(PersonName.FieldModifier.INITIAL); initialModifier.setRetainPunctuation(true); } } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("{"); sb.append(fieldID); for (PersonName.FieldModifier modifier : modifiers.keySet()) { sb.append("-"); sb.append(modifier.toString()); } sb.append("}"); return sb.toString(); } public boolean isLiteral() { return false; } public String format(PersonName name) { Set modifierIDs = new HashSet<>(modifiers.keySet()); String result = name.getFieldValue(fieldID, modifierIDs); if (result != null) { for (PersonName.FieldModifier modifierID : modifierIDs) { result = modifiers.get(modifierID).modifyField(result); } } return result; } public boolean isPopulated(PersonName name) { String result = this.format(name); return result != null && ! result.isEmpty(); } } /** * Internal class used when formatting a mononym (a PersonName object that only has * a given-name field). If the name doesn't have a surname field and the pattern * doesn't have a given-name field (or only has one that produces an initial), we * use this class to behave as though the value supplied in the given-name field * had instead been supplied in the surname field. */ private static class GivenToSurnamePersonName implements PersonName { private PersonName underlyingPersonName; public GivenToSurnamePersonName(PersonName underlyingPersonName) { this.underlyingPersonName = underlyingPersonName; } @Override public String toString() { return "Inverted version of " + underlyingPersonName.toString(); } @Override public Locale getNameLocale() { return underlyingPersonName.getNameLocale(); } @Override public PreferredOrder getPreferredOrder() { return underlyingPersonName.getPreferredOrder(); } @Override public String getFieldValue(NameField identifier, Set modifiers) { if (identifier == NameField.SURNAME) { return underlyingPersonName.getFieldValue(NameField.GIVEN, modifiers); } else if (identifier == NameField.GIVEN) { return null; } else { return underlyingPersonName.getFieldValue(identifier, modifiers); } } } }