1 package org.unicode.cldr.util; 2 3 import com.google.common.collect.ImmutableMap; 4 import com.google.common.collect.ImmutableSet; 5 import com.ibm.icu.text.Transform; 6 import java.util.Collections; 7 import java.util.LinkedHashMap; 8 import java.util.Map; 9 import org.unicode.cldr.test.CheckCLDR.CheckStatus.Subtype; 10 import org.unicode.cldr.util.RegexLookup.Merger; 11 12 public class PatternPlaceholders { 13 14 public enum PlaceholderStatus { 15 DISALLOWED("No placeholders allowed."), // 16 REQUIRED("Specific number of placeholders allowed."), // 17 LOCALE_DEPENDENT("Some placeholders may be omitted in certain locales"), // 18 MULTIPLE( 19 "May have multiple instances of the same placeholder, eg “{0} cats and {0} dogs”."), // 20 OPTIONAL( 21 "Any specific placeholder is optional (and non-numeric); there must be at least one.") // 22 ; 23 24 private final String message; 25 PlaceholderStatus(String message)26 private PlaceholderStatus(String message) { 27 this.message = message; 28 } 29 30 @Override toString()31 public String toString() { 32 return name() + ": " + message; 33 } 34 } 35 36 public static final ImmutableSet<Subtype> PLACEHOLDER_SUBTYPES = 37 ImmutableSet.of( 38 Subtype.gapsInPlaceholderNumbers, 39 Subtype.duplicatePlaceholders, 40 Subtype.missingPlaceholders, 41 Subtype.extraPlaceholders); 42 43 private static class PlaceholderData { 44 PlaceholderStatus status = PlaceholderStatus.REQUIRED; 45 Map<String, PlaceholderInfo> data = new LinkedHashMap<>(); 46 add(String id, String name, String example)47 public void add(String id, String name, String example) { 48 PlaceholderInfo row = new PlaceholderInfo(name, example); 49 data.put(id, row); 50 } 51 } 52 53 public static class PlaceholderInfo { 54 public String name; 55 public String example; 56 PlaceholderInfo(String name, String example)57 private PlaceholderInfo(String name, String example) { 58 this.name = name; 59 this.example = example; 60 } 61 62 @Override toString()63 public String toString() { 64 return "{" + name + "}, e.g. “" + example + "”"; 65 } 66 } 67 68 private static final class MyMerger implements Merger<PlaceholderData> { 69 @Override merge(PlaceholderData a, PlaceholderData into)70 public PlaceholderData merge(PlaceholderData a, PlaceholderData into) { 71 // check unique 72 for (String key : a.data.keySet()) { 73 if (into.data.containsKey(key)) { 74 throw new IllegalArgumentException("Duplicate placeholder: " + key); 75 } 76 } 77 into.data.putAll(a.data); 78 if (into.status != a.status) { 79 throw new IllegalArgumentException("Different optional status"); 80 } 81 return into; 82 } 83 } 84 85 private static final class MapTransform implements Transform<String, PlaceholderData> { 86 87 @Override transform(String source)88 public PlaceholderData transform(String source) { 89 PlaceholderData result = new PlaceholderData(); 90 try { 91 String[] parts = source.split("\\s*;\\s+"); 92 for (String part : parts) { 93 switch (part) { 94 case "locale": 95 result.status = PlaceholderStatus.LOCALE_DEPENDENT; 96 continue; 97 case "multiple": 98 result.status = PlaceholderStatus.MULTIPLE; 99 continue; 100 case "optional": 101 result.status = PlaceholderStatus.OPTIONAL; 102 continue; 103 default: 104 int equalsPos = part.indexOf('='); 105 String id = part.substring(0, equalsPos).trim(); 106 String name = part.substring(equalsPos + 1).trim(); 107 int spacePos = name.indexOf(' '); 108 String example; 109 if (spacePos >= 0) { 110 example = name.substring(spacePos + 1).trim(); 111 name = name.substring(0, spacePos).trim(); 112 } else { 113 example = ""; 114 } 115 // TODO Some normalization of personName namePattern placeholder ids. 116 // Hack for now, later do something maybe using 117 // PersonNameFormatter.ModifiedField 118 id = id.replace("-allCaps", ""); 119 id = id.replace("-initialCap", ""); 120 id = id.replace("-initial", ""); 121 id = id.replace("-monogram", ""); 122 123 PlaceholderInfo old = result.data.get(id); 124 if (old != null) { 125 throw new IllegalArgumentException( 126 "Key occurs twice: " + id + "=" + old + "!=" + name); 127 } 128 result.add(id, name, example); 129 } 130 } 131 } catch (Exception e) { 132 throw new IllegalArgumentException("Failed to parse " + source, e); 133 } 134 return result; 135 } 136 } 137 138 private final RegexLookup<PlaceholderData> patternPlaceholders; 139 PatternPlaceholders()140 private PatternPlaceholders() { 141 patternPlaceholders = 142 RegexLookup.of(new MapTransform()) 143 .setValueMerger(new MyMerger()) 144 .loadFromFile(PatternPlaceholders.class, "data/Placeholders.txt"); 145 } 146 147 private static final class PatternPlaceholdersHelper { 148 static final PatternPlaceholders SINGLETON = new PatternPlaceholders(); 149 } 150 getInstance()151 public static PatternPlaceholders getInstance() { 152 return PatternPlaceholdersHelper.SINGLETON; 153 } 154 get(String path)155 public Map<String, PlaceholderInfo> get(String path) { 156 // TODO change the original map to be unmodifiable, to avoid this step. Need to add a 157 // "finalize" to the lookup. 158 final PlaceholderData map = patternPlaceholders.get(path); 159 return map == null ? ImmutableMap.of() : Collections.unmodifiableMap(map.data); 160 } 161 getStatus(String path)162 public PlaceholderStatus getStatus(String path) { 163 // TODO change the original map to be unmodifiable, to avoid this step. Need to add a 164 // "finalize" to the lookup. 165 final PlaceholderData map = patternPlaceholders.get(path); 166 return map == null ? PlaceholderStatus.DISALLOWED : map.status; 167 } 168 } 169