22
33import com .google .common .collect .MapDifference ;
44import com .google .common .collect .Maps ;
5- import java .util .ArrayDeque ;
65import java .util .ArrayList ;
76import java .util .Arrays ;
8- import java .util .Deque ;
9- import java .util .Iterator ;
7+ import java .util .HashMap ;
108import java .util .List ;
119import java .util .Map ;
1210import java .util .Objects ;
1311import java .util .Set ;
14- import java .util .Spliterator ;
15- import java .util .Spliterators ;
16- import java .util .function .Consumer ;
1712import java .util .function .Supplier ;
1813import java .util .stream .Stream ;
1914import java .util .stream .StreamSupport ;
5752 * <li>ConcurrentHashMap::new — for thread-safety across all levels
5853 * </ul>
5954 *
60- * Future possible extensions:
55+ * NOTE: neither keys nor values can be null.
56+ *
57+ * <p>Future possible extensions:
6158 *
6259 * <ul>
6360 * <li>The current implementation is limited to what CLDR needs. Addition methods like contains()
@@ -95,20 +92,23 @@ public class NestedMap {
9592 */
9693 @ SafeVarargs
9794 private NestedMap (int keyCount , Supplier <Map <Object , Object >>... mapFactories ) {
98- if (mapFactories .length == 0 ) {
99- throw new IllegalArgumentException ("Too few mapFactories" );
100- } else if (mapFactories .length > keyCount ) {
95+ if (mapFactories .length > keyCount ) {
10196 throw new IllegalArgumentException ("Too many mapFactories" );
10297 }
10398 this .keyCount = keyCount ;
10499 if (mapFactories .length == keyCount ) {
105100 this .mapFactories = List .of (mapFactories );
106101 } else { // if the list is too short, fill out with copies of the last
107102 List <Supplier <Map <Object , Object >>> temp = new ArrayList <>();
108- temp .addAll (Arrays .asList (mapFactories ));
109- Supplier <Map <Object , Object >> last = temp .get (temp .size () - 1 );
103+ Supplier <Map <Object , Object >> toDuplicate ;
104+ if (mapFactories .length == 0 ) {
105+ toDuplicate = HashMap ::new ;
106+ } else {
107+ temp .addAll (Arrays .asList (mapFactories ));
108+ toDuplicate = temp .get (temp .size () - 1 );
109+ }
110110 while (temp .size () < keyCount ) {
111- temp .add (last );
111+ temp .add (toDuplicate );
112112 }
113113 this .mapFactories = List .copyOf (temp );
114114 }
@@ -147,7 +147,7 @@ private Object getInternal(Object... keys) {
147147 current = ((Map <Object , Object >) current ).get (key );
148148 if (current == null ) return null ;
149149 }
150- return current ;
150+ return keys . length == keyCount ? current : (( Map < Object , Object >) current ). keySet () ;
151151 }
152152
153153 @ SuppressWarnings ("unchecked" )
@@ -161,7 +161,7 @@ private void putInternal(Object... keysAndValue) {
161161 }
162162 for (Object keyOrValue : keysAndValue ) {
163163 if (keyOrValue == null ) {
164- throw new IllegalArgumentException ("Cannot store null value" );
164+ throw new IllegalArgumentException ("Cannot store null key or value" );
165165 }
166166 }
167167
@@ -181,7 +181,7 @@ private void putInternal(Object... keysAndValue) {
181181 * Deeply removes a value at the end of a key chain and prunes empty parent maps. If the
182182 * keysAndPossibleValue is not a long as the keyCount, then prunes the end.
183183 */
184- public void removeInternal (Object ... keysAndPossibleValue ) {
184+ private void removeInternal (Object ... keysAndPossibleValue ) {
185185 if (this .mapFactories == null ) { // attempt to modify immutable
186186 throw new UnsupportedOperationException ("Cannot modify immutable object" );
187187 }
@@ -277,67 +277,14 @@ private NestedMap toImmutable() {
277277 }
278278
279279 /**
280- * Returns a LAZY stream of all terminal entries in the nested map. The stream is built
281- * on-the-fly and does not store all entries in memory. Each entry is a list of objects, where
282- * the last element is the value.
280+ * Returns a lazy stream of all logical entries in the nested map. Each entry is a list of
281+ * objects, where the last element is the value.
283282 */
284283 private Stream <List <Object >> stream () {
285- return StreamSupport .stream (new NestedMapSpliterator (this .root ), false );
286- }
287-
288- /**
289- * A custom Spliterator that traverses the nested map structure lazily. It uses a stack of
290- * iterators to perform a depth-first traversal.
291- */
292- private static class NestedMapSpliterator
293- extends Spliterators .AbstractSpliterator <List <Object >> {
294- private final Deque <Iterator <Map .Entry <Object , Object >>> iteratorStack = new ArrayDeque <>();
295- private final Deque <Object > keyPath = new ArrayDeque <>();
296-
297- NestedMapSpliterator (Map <Object , Object > root ) {
298- super (
299- Long .MAX_VALUE ,
300- Spliterator .ORDERED ); // Assumes ORDERED for TreeMap, harmless for others.
301- if (root != null && !root .isEmpty ()) {
302- iteratorStack .push (root .entrySet ().iterator ());
303- }
304- }
305-
306- @ SuppressWarnings ("unchecked" )
307- @ Override
308- public boolean tryAdvance (Consumer <? super List <Object >> action ) {
309- while (!iteratorStack .isEmpty ()) {
310- Iterator <Map .Entry <Object , Object >> currentIterator = iteratorStack .peek ();
311-
312- if (currentIterator .hasNext ()) {
313- Map .Entry <Object , Object > entry = currentIterator .next ();
314- Object key = entry .getKey ();
315- Object value = entry .getValue ();
316-
317- keyPath .addLast (key );
318-
319- if (value instanceof Map ) {
320- // Go deeper: push the new map's iterator onto the stack.
321- iteratorStack .push (((Map <Object , Object >) value ).entrySet ().iterator ());
322- } else {
323- // Terminal value found: build the result and consume it.
324- List <Object > result = new ArrayList <>(keyPath );
325- result .add (value );
326- action .accept (result );
327- keyPath .removeLast (); // Backtrack path after consuming.
328- return true ;
329- }
330- } else {
331- // This level is exhausted: backtrack.
332- iteratorStack .pop ();
333- if (!keyPath .isEmpty ()) {
334- keyPath .removeLast ();
335- }
336- }
337- }
338- // The entire map has been traversed.
339- return false ;
340- }
284+ // Note: the FlatArraySpliterator is much simpler to understand, but has some limitations.
285+ // Notably, it doesn't parallelize.
286+ return StreamSupport .stream (new FlatArraySpliterator (root , keyCount ), false )
287+ .map (array -> Arrays .asList (array ));
341288 }
342289
343290 @ SuppressWarnings ("unchecked" )
@@ -446,7 +393,17 @@ private Map2(NestedMap engine) {
446393 this .engine = engine ;
447394 }
448395
449- /** Takes Treemap::new, HashMap::new, ConcurrentHashMap::new, and other suppliers */
396+ /**
397+ * Create a nested map with 2 keys, and 0..2 suppliers
398+ *
399+ * @param suppliers Common ones are Treemap::new, HashMap::new, ConcurrentHashMap::new, etc.
400+ * The default is a HashMap::new. If the number of suppliers is not insufficient, the
401+ * last supplier is used to fill it out.
402+ * <p>Examples So Map2.create(TreeMap::new) is equivalent to Map2.create(TreeMap::new,
403+ * TreeMap::new)<br>
404+ * So Map2.create() is equivalent to Map2.create(HashMap::new, HashMap::new)
405+ * @return
406+ */
450407 @ SafeVarargs
451408 public static <K1 , K2 , V > Map2 <K1 , K2 , V > create (
452409 Supplier <Map <Object , Object >>... suppliers ) {
@@ -476,10 +433,22 @@ public V get(K1 key1, K2 key2) {
476433 return (V ) engine .getInternal (key1 , key2 );
477434 }
478435
436+ public Set <K2 > keySet2 (K1 key1 ) {
437+ return (Set <K2 >) engine .getInternal (key1 );
438+ }
439+
440+ public Set <K2 > keySet () {
441+ return (Set <K2 >) engine .root .keySet ();
442+ }
443+
479444 public void put (K1 key1 , K2 key2 , V value ) {
480445 engine .putInternal (key1 , key2 , value );
481446 }
482447
448+ public void put (Entry3 <K1 , K2 , V > entry3 ) {
449+ engine .putInternal (entry3 .getKey1 (), entry3 .getKey2 (), entry3 .getValue ());
450+ }
451+
483452 public void putAll (Map2 <K1 , K2 , V > other ) {
484453 // Might optimize later
485454 other .stream ().forEach (x -> put (x .getKey1 (), x .getKey2 (), x .getValue ()));
@@ -527,7 +496,17 @@ private Map3(NestedMap engine) {
527496 this .engine = engine ;
528497 }
529498
530- /** Takes Treemap::new, HashMap::new, ConcurrentHashMap::new, and other suppliers */
499+ /**
500+ * Create a nested map with 3 keys, and 0..3 Suppliers
501+ *
502+ * @param suppliers Common ones are Treemap::new, HashMap::new, ConcurrentHashMap::new, etc.
503+ * The default is a HashMap::new. If the number of suppliers is not insufficient, the
504+ * last supplier is used to fill it out.
505+ * <p>Examples So Map3.create(TreeMap::new) is equivalent to Map2.create(TreeMap::new,
506+ * TreeMap::new, TreeMap::new)<br>
507+ * So Map3.create() is equivalent to Map2.create(HashMap::new, HashMap::new,
508+ * HashMap::new)
509+ */
531510 @ SafeVarargs
532511 public static <K1 , K2 , K3 , V > Map3 <K1 , K2 , K3 , V > create (
533512 Supplier <Map <Object , Object >>... suppliers ) {
@@ -538,10 +517,27 @@ public V get(K1 key1, K2 key2, K3 key3) {
538517 return (V ) engine .getInternal (key1 , key2 , key3 );
539518 }
540519
520+ public Set <K3 > keySet3 (K1 key1 , K2 key2 ) {
521+ return (Set <K3 >) engine .getInternal (key1 , key2 );
522+ }
523+
524+ public Set <K2 > keySet2 (K1 key1 ) {
525+ return (Set <K2 >) engine .getInternal (key1 );
526+ }
527+
528+ public Set <K2 > keySet () {
529+ return (Set <K2 >) engine .root .keySet ();
530+ }
531+
541532 public void put (K1 key1 , K2 key2 , K3 key3 , V value ) {
542533 engine .putInternal (key1 , key2 , key3 , value );
543534 }
544535
536+ public void put (Entry4 <K1 , K2 , K3 , V > entry3 ) {
537+ engine .putInternal (
538+ entry3 .getKey1 (), entry3 .getKey2 (), entry3 .getKey3 (), entry3 .getValue ());
539+ }
540+
545541 public void remove (K1 key1 , K2 key2 , K3 key3 , V value ) {
546542 engine .removeInternal (key1 , key2 , key3 , value );
547543 }
@@ -615,6 +611,18 @@ private Multimap2(NestedMap engine) {
615611 * Takes Treemap::new, HashMap::new, ConcurrentHashMap::new, and other suppliers. Note: the
616612 * final supplier should be one suitable for producing Map<V, Boolean>.
617613 */
614+ /**
615+ * Create a multimap map with 2 keys, and 0..3 suppliers
616+ *
617+ * @param suppliers Common ones are Treemap::new, HashMap::new, ConcurrentHashMap::new, etc.
618+ * The default is a HashMap::new. If the number of suppliers is not insufficient, the
619+ * last supplier is used to fill it out.
620+ * <p>Examples So Multimap2.create(TreeMap::new) is equivalent to
621+ * Multimap2.create(TreeMap::new, TreeMap::new, TreeMap::new)<br>
622+ * So Multimap2.create() is equivalent to Map2.create(HashMap::new, HashMap::new,
623+ * HashMap::new)<br>
624+ * Note: the final supplier should be one suitable for producing Map<V, Boolean>
625+ */
618626 @ SafeVarargs
619627 public static <K1 , K2 , V > Multimap2 <K1 , K2 , V > create (
620628 Supplier <Map <Object , Object >>... suppliers ) {
@@ -625,14 +633,27 @@ public void put(K1 key1, K2 key2, V value) {
625633 engine .putInternal (key1 , key2 , value , Boolean .TRUE );
626634 }
627635
636+ public void put (Entry3 <K1 , K2 , V > entry3 ) {
637+ engine .putInternal (entry3 .getKey1 (), entry3 .getKey2 (), entry3 .getValue (), Boolean .TRUE );
638+ }
639+
628640 @ SuppressWarnings ("unchecked" )
629641 public Set <V > get (K1 key1 , K2 key2 ) {
630- Map <V , Boolean > map = (Map <V , Boolean >) engine .getInternal (key1 , key2 );
631- return map == null ? null : map .keySet ();
642+ return (Set <V >) engine .getInternal (key1 , key2 );
643+ }
644+
645+ @ SuppressWarnings ("unchecked" )
646+ public Set <K2 > keySet2 (K1 key1 ) {
647+ return (Set <K2 >) engine .getInternal (key1 );
648+ }
649+
650+ @ SuppressWarnings ("unchecked" )
651+ public Set <K1 > keySet () {
652+ return (Set <K1 >) engine .root .keySet ();
632653 }
633654
634655 public boolean contains (K1 key1 , K2 key2 , V value ) {
635- return engine .getInternal (key1 , key2 ) != null ;
656+ return engine .getInternal (key1 , key2 , value ) != null ;
636657 }
637658
638659 public void remove (K1 key1 , K2 key2 , V value ) {
0 commit comments