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