1 // © 2019 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 package org.unicode.icu.tool.cldrtoicu.mapper;
4 
5 import static com.google.common.truth.Truth.assertThat;
6 import static org.unicode.icu.tool.cldrtoicu.testing.AssertUtils.assertThrows;
7 import static org.unicode.icu.tool.cldrtoicu.testing.IcuDataSubjectFactory.assertThat;
8 
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.List;
12 
13 import org.junit.Test;
14 import org.junit.runner.RunWith;
15 import org.junit.runners.JUnit4;
16 import org.unicode.cldr.api.CldrData;
17 import org.unicode.cldr.api.CldrPath;
18 import org.unicode.cldr.api.CldrValue;
19 import org.unicode.icu.tool.cldrtoicu.IcuData;
20 import org.unicode.icu.tool.cldrtoicu.PathValueTransformer.Result;
21 import org.unicode.icu.tool.cldrtoicu.RbValue;
22 import org.unicode.icu.tool.cldrtoicu.testing.FakeResult;
23 import org.unicode.icu.tool.cldrtoicu.testing.FakeTransformer;
24 
25 @RunWith(JUnit4.class)
26 public class AbstractPathValueMapperTest {
27     @Test
testUngroupedConcatenation()28     public void testUngroupedConcatenation() {
29         FakeMapper mapper = new FakeMapper();
30         mapper.addUngroupedResult("foo/bar", "one", "two");
31         mapper.addUngroupedResult("foo/baz", "other", "path");
32         mapper.addUngroupedResult("foo/bar", "three", "four");
33         IcuData icuData = mapper.addIcuData("foo");
34 
35         assertThat(icuData).getPaths().hasSize(2);
36         assertThat(icuData).hasValuesFor("foo/bar", singletonValues("one", "two", "three", "four"));
37         assertThat(icuData).hasValuesFor("foo/baz", singletonValues("other", "path"));
38     }
39 
40     @Test
testGrouping()41     public void testGrouping() {
42         FakeMapper mapper = new FakeMapper();
43         mapper.addGroupedResult("foo/bar", "one", "two");
44         mapper.addGroupedResult("foo/baz", "other", "path");
45         mapper.addGroupedResult("foo/bar", "three", "four");
46         IcuData icuData = mapper.addIcuData("foo");
47 
48         assertThat(icuData).getPaths().hasSize(2);
49         assertThat(icuData)
50             .hasValuesFor("foo/bar", RbValue.of("one", "two"), RbValue.of("three", "four"));
51         assertThat(icuData)
52             .hasValuesFor("foo/baz", RbValue.of("other", "path"));
53     }
54 
55     @Test
testFallbackResults()56     public void testFallbackResults() {
57         // The indices are important in matching up the results and their respective fallbacks.
58         Result explicit1 = FakeResult.of("foo/bar", 1, false, "one");
59         Result explicit2 = FakeResult.of("foo/bar", 2, false, "two");
60         Result explicit3 = FakeResult.of("foo/bar", 3, false, "three");
61 
62         Result fallback1 = FakeResult.fallback("foo/bar", 1, "<ONE>");
63         Result fallback2 = FakeResult.fallback("foo/bar", 2, "<TWO>");
64         Result fallback3 = FakeResult.fallback("foo/bar", 3, "<THREE>");
65 
66         FakeTransformer transformer = new FakeTransformer();
67         transformer.addFallbacks("foo/bar", fallback1, fallback2, fallback3);
68 
69         // When all results are explicitly present, no fallbacks are used.
70         IcuData noFallback = new FakeMapper(transformer)
71             .addResult(explicit1)
72             .addResult(explicit2)
73             .addResult(explicit3)
74             .addIcuData("foo");
75         assertThat(noFallback).hasValuesFor("foo/bar", singletonValues("one", "two", "three"));
76 
77         // Missing explicit results trigger fallbacks.
78         IcuData firstFallback = new FakeMapper(transformer)
79             .addResult(explicit2)
80             .addResult(explicit3)
81             .addIcuData("foo");
82         assertThat(firstFallback).hasValuesFor("foo/bar", singletonValues("<ONE>", "two", "three"));
83 
84         // Fallbacks can appear in any part of the result sequence.
85         IcuData lastFallbacks = new FakeMapper(transformer)
86             .addResult(explicit1)
87             .addIcuData("foo");
88         assertThat(lastFallbacks)
89             .hasValuesFor("foo/bar", singletonValues("one", "<TWO>", "<THREE>"));
90 
91         // Without a single result to "seed" the fallback group, nothing is emitted.
92         IcuData allFallbacks = new FakeMapper(transformer).addIcuData("foo");
93         assertThat(allFallbacks).getPaths().isEmpty();
94     }
95 
96     @Test
testAliases_ungrouped()97     public void testAliases_ungrouped() {
98         FakeMapper mapper = new FakeMapper();
99         mapper.addUngroupedResult("foo/default", "start", "/alias/target", "end");
100         mapper.addUngroupedResult("foo/alias-0", "start", "/alias/target[0]", "end");
101         mapper.addUngroupedResult("foo/alias-1", "start", "/alias/target[1]", "end");
102         mapper.addUngroupedResult("foo/alias-2", "start", "/alias/target[2]", "end");
103         mapper.addUngroupedResult("alias/target", "first", "second", "third");
104         IcuData icuData = mapper.addIcuData("foo");
105 
106         assertThat(icuData).getPaths().hasSize(5);
107         assertThat(icuData)
108             .hasValuesFor("foo/default", singletonValues("start", "first", "end"));
109         assertThat(icuData)
110             .hasValuesFor("foo/alias-0", singletonValues("start", "first", "end"));
111         assertThat(icuData)
112             .hasValuesFor("foo/alias-1", singletonValues("start", "second", "end"));
113         assertThat(icuData)
114             .hasValuesFor("foo/alias-2", singletonValues("start", "third", "end"));
115         assertThat(icuData)
116             .hasValuesFor("alias/target", singletonValues("first", "second", "third"));
117     }
118 
119     // Grouping ignores aliases.
120     @Test
testAliases_grouped()121     public void testAliases_grouped() {
122         FakeMapper mapper = new FakeMapper();
123         mapper.addGroupedResult("foo/bar", "grouped", "/alias/target");
124         mapper.addGroupedResult("foo/bar", "/alias/target[1]");
125         mapper.addUngroupedResult("alias/target", "first", "second");
126 
127         IcuData icuData = mapper.addIcuData("foo");
128         assertThat(icuData).getPaths().hasSize(2);
129         assertThat(icuData)
130             .hasValuesFor("foo/bar",
131                 RbValue.of("grouped", "/alias/target"),
132                 RbValue.of("/alias/target[1]"));
133         assertThat(icuData).hasValuesFor("alias/target", singletonValues("first", "second"));
134     }
135 
136     @Test
testAliases_explicit()137     public void testAliases_explicit() {
138         FakeMapper mapper = new FakeMapper();
139         mapper.addUngroupedResult("foo/bar:alias", "/alias/target");
140         mapper.addUngroupedResult("foo/bar", "/alias/target");
141         mapper.addUngroupedResult("alias/target", "alias-value");
142         IcuData icuData = mapper.addIcuData("foo");
143 
144         assertThat(icuData).getPaths().hasSize(3);
145         assertThat(icuData).hasValuesFor("foo/bar:alias", singletonValues("/alias/target"));
146         assertThat(icuData).hasValuesFor("foo/bar", singletonValues("alias-value"));
147         assertThat(icuData).hasValuesFor("alias/target", singletonValues("alias-value"));
148     }
149 
150     @Test
testAliases_ordering()151     public void testAliases_ordering() {
152         // It doesn't matter where an alias is in the order of results.
153         FakeMapper mapper = new FakeMapper();
154         mapper.addUngroupedResult("first/alias", "hello");
155         mapper.addUngroupedResult("foo/bar", "/first/alias", "/last/alias");
156         mapper.addUngroupedResult("last/alias", "world");
157         IcuData icuData = mapper.addIcuData("foo");
158 
159         assertThat(icuData).hasValuesFor("foo/bar", singletonValues("hello", "world"));
160     }
161 
162     @Test
testAliases_concatenation()163     public void testAliases_concatenation() {
164         // It doesn't matter where an alias is in the order of results.
165         FakeMapper mapper = new FakeMapper();
166         mapper.addUngroupedResult("alias/target", "hello");
167         mapper.addUngroupedResult("foo/bar", "/alias/target[0]", "/alias/target[1]");
168         mapper.addUngroupedResult("alias/target", "world");
169         IcuData icuData = mapper.addIcuData("foo");
170 
171         assertThat(icuData).hasValuesFor("foo/bar", singletonValues("hello", "world"));
172     }
173 
174     @Test
testAliases_missing()175     public void testAliases_missing() {
176         FakeMapper mapper = new FakeMapper();
177         mapper.addUngroupedResult("alias/target", "value");
178         mapper.addUngroupedResult("foo/bar", "/no-such-alias/target");
179         IllegalArgumentException e =
180             assertThrows(IllegalArgumentException.class, () -> mapper.addIcuData("foo"));
181         assertThat(e).hasMessageThat().contains("no such alias value");
182         assertThat(e).hasMessageThat().contains("/no-such-alias/target");
183     }
184 
185     @Test
testAliases_badIndex()186     public void testAliases_badIndex() {
187         FakeMapper mapper = new FakeMapper();
188         mapper.addUngroupedResult("alias/target", "value");
189         mapper.addUngroupedResult("foo/bar", "/alias/target[1]");
190         IllegalArgumentException e =
191             assertThrows(IllegalArgumentException.class, () -> mapper.addIcuData("foo"));
192         assertThat(e).hasMessageThat().contains("out of bounds");
193         assertThat(e).hasMessageThat().contains("/alias/target[1]");
194     }
195 
196     @Test
testAliases_noRecursion()197     public void testAliases_noRecursion() {
198         FakeMapper mapper = new FakeMapper();
199         mapper.addUngroupedResult("alias/target", "/other/alias");
200         mapper.addUngroupedResult("other/alias", "/other/alias");
201         mapper.addUngroupedResult("foo/bar", "/alias/target");
202         IllegalStateException e =
203             assertThrows(IllegalStateException.class, () -> mapper.addIcuData("foo"));
204         assertThat(e).hasMessageThat().contains("recursive alias resolution is not supported");
205     }
206 
207     @Test
testAliases_explicitAliasesAreSingletonOnly()208     public void testAliases_explicitAliasesAreSingletonOnly() {
209         FakeMapper mapper = new FakeMapper();
210         mapper.addUngroupedResult("foo/bar:alias", "first", "second");
211         IllegalArgumentException e =
212             assertThrows(IllegalArgumentException.class, () -> mapper.addIcuData("foo"));
213         assertThat(e).hasMessageThat().contains("explicit aliases must be singleton values");
214         assertThat(e).hasMessageThat().contains("foo/bar:alias");
215     }
216 
217     private static final class FakeMapper extends AbstractPathValueMapper {
218         private final static CldrData EXPLODING_DATA =
219             new CldrData() {
220                 @Override public void accept(PathOrder pathOrder, ValueVisitor valueVisitor) {
221                     throw new UnsupportedOperationException("should not be called by test");
222                 }
223 
224                 @Override public void accept(PathOrder pathOrder, PrefixVisitor prefixVisitor) {
225                     throw new UnsupportedOperationException("should not be called by test");
226                 }
227 
228                 @Override public CldrValue get(CldrPath cldrPath) {
229                     throw new UnsupportedOperationException("should not be called by test");
230                 }
231             };
232 
233         // This preserves insertion order in a well defined way (good for testing alias order).
234         private final List<Result> fakeResults = new ArrayList<>();
235 
FakeMapper()236         FakeMapper() {
237             this(new FakeTransformer());
238         }
239 
FakeMapper(FakeTransformer transformer)240         FakeMapper(FakeTransformer transformer) {
241             super(EXPLODING_DATA, transformer);
242         }
243 
244         // Helper method to neaten up the tests a bit.
addIcuData(String localeId)245         IcuData addIcuData(String localeId) {
246             IcuData icuData = new IcuData(localeId, true);
247             addIcuData(icuData);
248             return icuData;
249         }
250 
addUngroupedResult(String path, String... values)251         FakeMapper addUngroupedResult(String path, String... values) {
252             int index = fakeResults.size() + 1;
253             return addResult(FakeResult.of(path, index, false, values));
254         }
255 
addGroupedResult(String path, String... values)256         FakeMapper addGroupedResult(String path, String... values) {
257             int index = fakeResults.size() + 1;
258             return addResult(FakeResult.of(path, index, true, values));
259         }
260 
addResult(Result r)261         FakeMapper addResult(Result r) {
262             fakeResults.add(r);
263             return this;
264         }
265 
addResults()266         @Override void addResults() {
267             fakeResults.forEach(result -> addResult(result.getKey(), result));
268         }
269     }
270 
singletonValues(String... values)271     private static RbValue[] singletonValues(String... values) {
272         return Arrays.stream(values).map(RbValue::of).toArray(RbValue[]::new);
273     }
274 }