Skip to content

Commit 9d406c2

Browse files
committed
CLDR-18683 Investigate more general nested maps
1 parent 70d7990 commit 9d406c2

File tree

2 files changed

+686
-0
lines changed

2 files changed

+686
-0
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import java.util.Collection;
2+
import java.util.EnumSet;
3+
import java.util.HashMap;
4+
import java.util.LinkedHashMap;
5+
import java.util.List;
6+
import java.util.Set;
7+
import java.util.TreeMap;
8+
import java.util.concurrent.ConcurrentHashMap;
9+
import java.util.function.Supplier;
10+
import java.util.stream.Collectors;
11+
import org.unicode.cldr.icu.dev.test.TestFmwk;
12+
import org.unicode.cldr.util.NestedMap.ImmutableNestedMap3;
13+
import org.unicode.cldr.util.NestedMap.NestedMap3;
14+
15+
public class NestedMapTest extends TestFmwk {
16+
17+
enum MapType {
18+
Tree(TreeMap::new),
19+
Hash(HashMap::new),
20+
LinkedHash(LinkedHashMap::new),
21+
ConcurrentHash(ConcurrentHashMap::new);
22+
23+
private final Supplier supplier;
24+
25+
private MapType(Supplier x) {
26+
this.supplier = x;
27+
}
28+
}
29+
30+
enum Mutability {
31+
mutable,
32+
immutable
33+
}
34+
35+
enum Order {
36+
az,
37+
za,
38+
na
39+
}
40+
41+
public static void main(String[] args) {
42+
new NestedMapTest().run(args);
43+
}
44+
45+
public void testCore() {
46+
// Create a 3-level map with type-safety:
47+
// Level 1: String (Region)
48+
// Level 2: String (Product)
49+
// Level 3: Integer (Year)
50+
// Value: Double (Sales Amount)
51+
for (MapType tt : MapType.values()) {
52+
NestedMap3<String, String, Integer, Double> salesData = NestedMap3.create(tt.supplier);
53+
54+
// --- Type-Safe PUT operations ---
55+
salesData.put("South America", "Widget", 2024, 150000.75);
56+
salesData.put("South America", "Widget", 2020, 0d);
57+
salesData.put("Europe", "Gadget", 2025, 95000.50);
58+
salesData.put("North Africa", "Widget", 2025, 175000.00);
59+
salesData.put("South Africa", "Gadget", 2025, 120000.00);
60+
salesData.put("Asia", "Gadget3", 2023, 666.00);
61+
62+
String streamTest = tryOperations(tt, Mutability.mutable, salesData, null);
63+
64+
// Immutable
65+
ImmutableNestedMap3<String, String, Integer, Double> salesDataIM =
66+
salesData.createImmutable();
67+
tryOperations(tt, Mutability.immutable, salesDataIM, streamTest);
68+
}
69+
}
70+
71+
private String tryOperations(
72+
MapType tt,
73+
Mutability mutability,
74+
NestedMap3<String, String, Integer, Double> salesData,
75+
String streamTest) {
76+
77+
logln(tt + ", " + mutability);
78+
79+
// The following line would cause a COMPILE ERROR due to incorrect type,
80+
// demonstrating the benefit of the shim.
81+
// salesData.put("Asia", "Widget", "2024", 99.99); //<- Compile Error! "2024" is not an
82+
// Integer.
83+
84+
logln(salesData.toString());
85+
86+
// --- Type-Safe GET operations ---
87+
// The result is automatically cast to Double, no manual cast needed.
88+
Double sales = salesData.get("Asia", "Gadget3", 2023);
89+
assertEquals("Sales for Asia, Gadget3, 2003", 666d, sales);
90+
91+
// --- Type-Safe REMOVE operations ---
92+
String result = null;
93+
try {
94+
Double removedValue = salesData.remove("Europe", "Gadget", 2025);
95+
result = removedValue.toString();
96+
} catch (Exception e) {
97+
result = e.getClass().getName();
98+
}
99+
assertEquals(
100+
"put",
101+
mutability == Mutability.mutable
102+
? "95000.5"
103+
: "java.lang.UnsupportedOperationException",
104+
result);
105+
106+
assertNull("Data after removal", salesData.get("Europe", "Gadget", 2025));
107+
108+
// check order
109+
List<String> list =
110+
salesData.stream().map(x -> x.getKey1()).collect(Collectors.toUnmodifiableList());
111+
Order order = getOrder(list);
112+
switch (tt) {
113+
case ConcurrentHash:
114+
case Hash:
115+
// should be unordered (normally)
116+
assertEquals(tt.name(), Order.na, order);
117+
break;
118+
case LinkedHash:
119+
// must be za (from the way we set up the data
120+
assertEquals(tt.name(), Order.za, order);
121+
break;
122+
case Tree:
123+
// must be az (because sorted)
124+
assertEquals(tt.name(), Order.az, order);
125+
break;
126+
}
127+
128+
// Streaming
129+
String data = salesData.stream().map(x -> x.toString()).collect(Collectors.joining(", "));
130+
if (streamTest != null) {
131+
assertEquals("StreamTest", streamTest, data);
132+
} else {
133+
logln("StreamTest: " + data);
134+
}
135+
return data;
136+
}
137+
138+
public void testOrder() {
139+
List<String> test =
140+
List.of("South America", "Asia", "Europe", "South Africa", "North Africa");
141+
assertEquals("Order ok", Order.na, getOrder(test));
142+
}
143+
144+
private <T extends Comparable<T>> Order getOrder(Collection<T> list) {
145+
Set<Order> options = EnumSet.allOf(Order.class);
146+
T lastItem = null;
147+
for (T item : list) {
148+
if (lastItem != null) {
149+
int intOrder = lastItem.compareTo(item);
150+
if (intOrder < 0) {
151+
options.remove(Order.za);
152+
options.remove(Order.na);
153+
} else if (intOrder > 0) {
154+
options.remove(Order.az);
155+
options.remove(Order.na);
156+
}
157+
}
158+
lastItem = item;
159+
}
160+
return options.isEmpty() ? Order.na : options.iterator().next();
161+
}
162+
}

0 commit comments

Comments
 (0)