1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.metapath.item.function;
7   
8   import java.util.Collections;
9   import java.util.LinkedHashMap;
10  import java.util.Map;
11  import java.util.Set;
12  import java.util.function.BiConsumer;
13  import java.util.function.BinaryOperator;
14  import java.util.function.Function;
15  import java.util.function.Supplier;
16  import java.util.stream.Collector;
17  
18  import dev.metaschema.core.metapath.function.IFunction;
19  import dev.metaschema.core.metapath.item.ICollectionValue;
20  import dev.metaschema.core.metapath.item.IItemVisitor;
21  import dev.metaschema.core.metapath.item.ISequence;
22  import dev.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
23  import dev.metaschema.core.metapath.item.function.impl.AbstractMapItem;
24  import dev.metaschema.core.metapath.item.function.impl.MapItemN;
25  import dev.metaschema.core.metapath.type.IItemType;
26  import dev.metaschema.core.util.ObjectUtils;
27  import edu.umd.cs.findbugs.annotations.NonNull;
28  
29  /**
30   * Represents a mapping of {@link IMapKey} keys to values.
31   *
32   * @param <VALUE>
33   *          the value type
34   */
35  public interface IMapItem<VALUE extends ICollectionValue>
36      extends IFunction, Map<IMapKey, VALUE> {
37    /**
38     * Get the type information for this item.
39     *
40     * @return the type information
41     */
42    @NonNull
43    static IItemType type() {
44      return IItemType.map();
45    }
46  
47    @Override
48    default IItemType getType() {
49      return type();
50    }
51  
52    /**
53     * Get an empty, immutable map item.
54     *
55     * @param <V>
56     *          the value Java type
57     * @return an immutable map item
58     */
59    @NonNull
60    static <V extends ICollectionValue> IMapItem<V> empty() {
61      return AbstractMapItem.empty();
62    }
63  
64    @Override
65    Map<IMapKey, VALUE> getValue();
66  
67    @Override
68    default boolean hasValue() {
69      return true;
70    }
71  
72    @Override
73    default ISequence<?> contentsAsSequence() {
74      return ISequence.of(ObjectUtils.notNull(values().stream()
75          .flatMap(ICollectionValue::normalizeAsItems)));
76    }
77  
78    /**
79     * Determine if this sequence is empty.
80     *
81     * @return {@code true} if the sequence contains no items, or {@code false}
82     *         otherwise
83     */
84    @Override
85    default boolean isEmpty() {
86      return getValue().isEmpty();
87    }
88  
89    /**
90     * Get the count of items in this sequence.
91     *
92     * @return the count of items
93     */
94    @Override
95    default int size() {
96      return getValue().size();
97  
98    }
99  
100   /**
101    * A {@link Collector} implementation to generates a sequence from a stream of
102    * Metapath items.
103    *
104    * @param <T>
105    *          the Java type of the items
106    * @return a collector that will generate a sequence
107    */
108   @NonNull
109   static <T extends ICollectionValue> Collector<Map.Entry<IMapKey, T>, ?, IMapItem<T>> toMapItem() {
110     return new Collector<Map.Entry<IMapKey, T>, Map<IMapKey, T>, IMapItem<T>>() {
111 
112       @Override
113       public Supplier<Map<IMapKey, T>> supplier() {
114         return LinkedHashMap::new;
115       }
116 
117       @Override
118       public BiConsumer<Map<IMapKey, T>, Map.Entry<IMapKey, T>> accumulator() {
119         return (map, entry) -> map.putIfAbsent(entry.getKey(), entry.getValue());
120       }
121 
122       @Override
123       public BinaryOperator<Map<IMapKey, T>> combiner() {
124         return (map1, map2) -> {
125           map1.putAll(map2);
126           return map1;
127         };
128       }
129 
130       @Override
131       public Function<Map<IMapKey, T>, IMapItem<T>> finisher() {
132         return map -> ofCollection(ObjectUtils.notNull(map));
133       }
134 
135       @Override
136       public Set<Characteristics> characteristics() {
137         return Collections.emptySet();
138       }
139     };
140   }
141 
142   @Override
143   default ISequence<IMapItem<VALUE>> toSequence() {
144     return ISequence.of(this);
145   }
146 
147   /**
148    * Get a new, immutable map item that contains the items in the provided map.
149    *
150    * @param <V>
151    *          the value Java type
152    * @param map
153    *          the map whose items are to be added to the new map
154    * @return a map item containing the specified entries
155    */
156   @NonNull
157   static <V extends ICollectionValue> IMapItem<V> ofCollection(
158       @NonNull Map<IMapKey, V> map) {
159     return map.isEmpty() ? empty() : new MapItemN<>(map);
160   }
161 
162   /**
163    * Returns an unmodifiable map item containing zero mappings.
164    *
165    * @param <V>
166    *          the value Java type
167    * @return an empty {@code IMapItem}
168    */
169   @NonNull
170   static <V extends ICollectionValue> IMapItem<V> of() {
171     return AbstractMapItem.empty();
172   }
173 
174   /**
175    * Returns an unmodifiable map item containing a single mapping.
176    *
177    * @param <K>
178    *          the map item's key type
179    * @param <V>
180    *          the map item's value type
181    * @param k1
182    *          the mapping's key
183    * @param v1
184    *          the mapping's value
185    * @return a map item containing the specified mapping
186    * @throws NullPointerException
187    *           if the key or the value is {@code null}
188    */
189   @NonNull
190   static <K extends IAnyAtomicItem, V extends ICollectionValue> IMapItem<V> of(@NonNull K k1, @NonNull V v1) {
191     return new MapItemN<>(entry(k1, v1));
192   }
193 
194   /**
195    * Returns an unmodifiable map item containing two mappings.
196    *
197    * @param <K>
198    *          the map item's key type
199    * @param <V>
200    *          the map item's value type
201    * @param k1
202    *          the first mapping's key
203    * @param v1
204    *          the first mapping's value
205    * @param k2
206    *          the second mapping's key
207    * @param v2
208    *          the second mapping's value
209    * @return a map item containing the specified mappings
210    * @throws IllegalArgumentException
211    *           if the keys are duplicates
212    * @throws NullPointerException
213    *           if any key or value is {@code null}
214    */
215   @NonNull
216   static <K extends IAnyAtomicItem, V extends ICollectionValue> IMapItem<V> of(
217       @NonNull K k1, @NonNull V v1,
218       @NonNull K k2, @NonNull V v2) {
219     return new MapItemN<>(
220         entry(k1, v1),
221         entry(k2, v2));
222   }
223 
224   /**
225    * Returns an unmodifiable map item containing three mappings.
226    *
227    * @param <K>
228    *          the map item's key type
229    * @param <V>
230    *          the map item's value type
231    * @param k1
232    *          the first mapping's key
233    * @param v1
234    *          the first mapping's value
235    * @param k2
236    *          the second mapping's key
237    * @param v2
238    *          the second mapping's value
239    * @param k3
240    *          the third mapping's key
241    * @param v3
242    *          the third mapping's value
243    * @return a map item containing the specified mappings
244    * @throws IllegalArgumentException
245    *           if there are any duplicate keys
246    * @throws NullPointerException
247    *           if any key or value is {@code null}
248    */
249   @NonNull
250   static <K extends IAnyAtomicItem, V extends ICollectionValue>
251       IMapItem<V> of(
252           @NonNull K k1, @NonNull V v1,
253           @NonNull K k2, @NonNull V v2,
254           @NonNull K k3, @NonNull V v3) {
255     return new MapItemN<>(
256         entry(k1, v1),
257         entry(k2, v2),
258         entry(k3, v3));
259   }
260 
261   /**
262    * Returns an unmodifiable map item containing four mappings.
263    *
264    * @param <K>
265    *          the map item's key type
266    * @param <V>
267    *          the map item's value type
268    * @param k1
269    *          the first mapping's key
270    * @param v1
271    *          the first mapping's value
272    * @param k2
273    *          the second mapping's key
274    * @param v2
275    *          the second mapping's value
276    * @param k3
277    *          the third mapping's key
278    * @param v3
279    *          the third mapping's value
280    * @param k4
281    *          the fourth mapping's key
282    * @param v4
283    *          the fourth mapping's value
284    * @return a map item containing the specified mappings
285    * @throws IllegalArgumentException
286    *           if there are any duplicate keys
287    * @throws NullPointerException
288    *           if any key or value is {@code null}
289    */
290   @NonNull
291   static <K extends IAnyAtomicItem, V extends ICollectionValue>
292       IMapItem<V> of(
293           @NonNull K k1, @NonNull V v1,
294           @NonNull K k2, @NonNull V v2,
295           @NonNull K k3, @NonNull V v3,
296           @NonNull K k4, @NonNull V v4) {
297     return new MapItemN<>(
298         entry(k1, v1),
299         entry(k2, v2),
300         entry(k3, v3),
301         entry(k4, v4));
302   }
303 
304   /**
305    * Returns an unmodifiable map item containing five mappings.
306    *
307    * @param <K>
308    *          the map item's key type
309    * @param <V>
310    *          the map item's value type
311    * @param k1
312    *          the first mapping's key
313    * @param v1
314    *          the first mapping's value
315    * @param k2
316    *          the second mapping's key
317    * @param v2
318    *          the second mapping's value
319    * @param k3
320    *          the third mapping's key
321    * @param v3
322    *          the third mapping's value
323    * @param k4
324    *          the fourth mapping's key
325    * @param v4
326    *          the fourth mapping's value
327    * @param k5
328    *          the fifth mapping's key
329    * @param v5
330    *          the fifth mapping's value
331    * @return a map item containing the specified mappings
332    * @throws IllegalArgumentException
333    *           if there are any duplicate keys
334    * @throws NullPointerException
335    *           if any key or value is {@code null}
336    */
337   @NonNull
338   static <K extends IAnyAtomicItem, V extends ICollectionValue>
339       IMapItem<V> of(
340           @NonNull K k1, @NonNull V v1,
341           @NonNull K k2, @NonNull V v2,
342           @NonNull K k3, @NonNull V v3,
343           @NonNull K k4, @NonNull V v4,
344           @NonNull K k5, @NonNull V v5) {
345     return new MapItemN<>(
346         entry(k1, v1),
347         entry(k2, v2),
348         entry(k3, v3),
349         entry(k4, v4),
350         entry(k5, v5));
351   }
352 
353   /**
354    * Returns an unmodifiable map item containing six mappings.
355    *
356    * @param <K>
357    *          the map item's key type
358    * @param <V>
359    *          the map item's value type
360    * @param k1
361    *          the first mapping's key
362    * @param v1
363    *          the first mapping's value
364    * @param k2
365    *          the second mapping's key
366    * @param v2
367    *          the second mapping's value
368    * @param k3
369    *          the third mapping's key
370    * @param v3
371    *          the third mapping's value
372    * @param k4
373    *          the fourth mapping's key
374    * @param v4
375    *          the fourth mapping's value
376    * @param k5
377    *          the fifth mapping's key
378    * @param v5
379    *          the fifth mapping's value
380    * @param k6
381    *          the sixth mapping's key
382    * @param v6
383    *          the sixth mapping's value
384    * @return a map item containing the specified mappings
385    * @throws IllegalArgumentException
386    *           if there are any duplicate keys
387    * @throws NullPointerException
388    *           if any key or value is {@code null}
389    */
390   @SuppressWarnings("PMD.ExcessiveParameterList")
391   @NonNull
392   static <K extends IAnyAtomicItem, V extends ICollectionValue>
393       IMapItem<V> of(
394           @NonNull K k1, @NonNull V v1,
395           @NonNull K k2, @NonNull V v2,
396           @NonNull K k3, @NonNull V v3,
397           @NonNull K k4, @NonNull V v4,
398           @NonNull K k5, @NonNull V v5,
399           @NonNull K k6, @NonNull V v6) {
400     return new MapItemN<>(
401         entry(k1, v1),
402         entry(k2, v2),
403         entry(k3, v3),
404         entry(k4, v4),
405         entry(k5, v5),
406         entry(k6, v6));
407   }
408 
409   // CPD-OFF
410   /**
411    * Returns an unmodifiable map item containing seven mappings.
412    *
413    * @param <K>
414    *          the map item's key type
415    * @param <V>
416    *          the map item's value type
417    * @param k1
418    *          the first mapping's key
419    * @param v1
420    *          the first mapping's value
421    * @param k2
422    *          the second mapping's key
423    * @param v2
424    *          the second mapping's value
425    * @param k3
426    *          the third mapping's key
427    * @param v3
428    *          the third mapping's value
429    * @param k4
430    *          the fourth mapping's key
431    * @param v4
432    *          the fourth mapping's value
433    * @param k5
434    *          the fifth mapping's key
435    * @param v5
436    *          the fifth mapping's value
437    * @param k6
438    *          the sixth mapping's key
439    * @param v6
440    *          the sixth mapping's value
441    * @param k7
442    *          the seventh mapping's key
443    * @param v7
444    *          the seventh mapping's value
445    * @return a map item containing the specified mappings
446    * @throws IllegalArgumentException
447    *           if there are any duplicate keys
448    * @throws NullPointerException
449    *           if any key or value is {@code null}
450    */
451   @SuppressWarnings("PMD.ExcessiveParameterList")
452   @NonNull
453   static <K extends IAnyAtomicItem, V extends ICollectionValue> IMapItem<V> of(
454       @NonNull K k1, @NonNull V v1,
455       @NonNull K k2, @NonNull V v2,
456       @NonNull K k3, @NonNull V v3,
457       @NonNull K k4, @NonNull V v4,
458       @NonNull K k5, @NonNull V v5,
459       @NonNull K k6, @NonNull V v6,
460       @NonNull K k7, @NonNull V v7) {
461     return new MapItemN<>(
462         entry(k1, v1),
463         entry(k2, v2),
464         entry(k3, v3),
465         entry(k4, v4),
466         entry(k5, v5),
467         entry(k6, v6),
468         entry(k7, v7));
469   }
470 
471   /**
472    * Returns an unmodifiable map item containing eight mappings. See
473    * <a href="#unmodifiable">Unmodifiable Maps</a> for details.
474    *
475    * @param <K>
476    *          the map item's key type
477    * @param <V>
478    *          the map item's value type
479    * @param k1
480    *          the first mapping's key
481    * @param v1
482    *          the first mapping's value
483    * @param k2
484    *          the second mapping's key
485    * @param v2
486    *          the second mapping's value
487    * @param k3
488    *          the third mapping's key
489    * @param v3
490    *          the third mapping's value
491    * @param k4
492    *          the fourth mapping's key
493    * @param v4
494    *          the fourth mapping's value
495    * @param k5
496    *          the fifth mapping's key
497    * @param v5
498    *          the fifth mapping's value
499    * @param k6
500    *          the sixth mapping's key
501    * @param v6
502    *          the sixth mapping's value
503    * @param k7
504    *          the seventh mapping's key
505    * @param v7
506    *          the seventh mapping's value
507    * @param k8
508    *          the eighth mapping's key
509    * @param v8
510    *          the eighth mapping's value
511    * @return a map item containing the specified mappings
512    * @throws IllegalArgumentException
513    *           if there are any duplicate keys
514    * @throws NullPointerException
515    *           if any key or value is {@code null}
516    */
517   @SuppressWarnings("PMD.ExcessiveParameterList")
518   @NonNull
519   static <K extends IAnyAtomicItem, V extends ICollectionValue>
520       IMapItem<V> of(
521           @NonNull K k1, @NonNull V v1,
522           @NonNull K k2, @NonNull V v2,
523           @NonNull K k3, @NonNull V v3,
524           @NonNull K k4, @NonNull V v4,
525           @NonNull K k5, @NonNull V v5,
526           @NonNull K k6, @NonNull V v6,
527           @NonNull K k7, @NonNull V v7,
528           @NonNull K k8, @NonNull V v8) {
529     return new MapItemN<>(
530         entry(k1, v1),
531         entry(k2, v2),
532         entry(k3, v3),
533         entry(k4, v4),
534         entry(k5, v5),
535         entry(k6, v6),
536         entry(k7, v7),
537         entry(k8, v8));
538   }
539 
540   /**
541    * Returns an unmodifiable map item containing nine mappings.
542    *
543    * @param <K>
544    *          the map item's key type
545    * @param <V>
546    *          the map item's value type
547    * @param k1
548    *          the first mapping's key
549    * @param v1
550    *          the first mapping's value
551    * @param k2
552    *          the second mapping's key
553    * @param v2
554    *          the second mapping's value
555    * @param k3
556    *          the third mapping's key
557    * @param v3
558    *          the third mapping's value
559    * @param k4
560    *          the fourth mapping's key
561    * @param v4
562    *          the fourth mapping's value
563    * @param k5
564    *          the fifth mapping's key
565    * @param v5
566    *          the fifth mapping's value
567    * @param k6
568    *          the sixth mapping's key
569    * @param v6
570    *          the sixth mapping's value
571    * @param k7
572    *          the seventh mapping's key
573    * @param v7
574    *          the seventh mapping's value
575    * @param k8
576    *          the eighth mapping's key
577    * @param v8
578    *          the eighth mapping's value
579    * @param k9
580    *          the ninth mapping's key
581    * @param v9
582    *          the ninth mapping's value
583    * @return a map item containing the specified mappings
584    * @throws IllegalArgumentException
585    *           if there are any duplicate keys
586    * @throws NullPointerException
587    *           if any key or value is {@code null}
588    */
589   @SuppressWarnings("PMD.ExcessiveParameterList")
590   @NonNull
591   static <K extends IAnyAtomicItem, V extends ICollectionValue>
592       IMapItem<V> of(
593           @NonNull K k1, @NonNull V v1,
594           @NonNull K k2, @NonNull V v2,
595           @NonNull K k3, @NonNull V v3,
596           @NonNull K k4, @NonNull V v4,
597           @NonNull K k5, @NonNull V v5,
598           @NonNull K k6, @NonNull V v6,
599           @NonNull K k7, @NonNull V v7,
600           @NonNull K k8, @NonNull V v8,
601           @NonNull K k9, @NonNull V v9) {
602     return new MapItemN<>(entry(k1, v1), entry(k2, v2), entry(k3, v3), entry(k4, v4), entry(k5, v5), entry(k6, v6),
603         entry(k7, v7), entry(k8, v8), entry(k9, v9));
604   }
605 
606   /**
607    * Returns an unmodifiable map item containing ten mappings.
608    *
609    * @param <K>
610    *          the map item's key type
611    * @param <V>
612    *          the map item's value type
613    * @param k1
614    *          the first mapping's key
615    * @param v1
616    *          the first mapping's value
617    * @param k2
618    *          the second mapping's key
619    * @param v2
620    *          the second mapping's value
621    * @param k3
622    *          the third mapping's key
623    * @param v3
624    *          the third mapping's value
625    * @param k4
626    *          the fourth mapping's key
627    * @param v4
628    *          the fourth mapping's value
629    * @param k5
630    *          the fifth mapping's key
631    * @param v5
632    *          the fifth mapping's value
633    * @param k6
634    *          the sixth mapping's key
635    * @param v6
636    *          the sixth mapping's value
637    * @param k7
638    *          the seventh mapping's key
639    * @param v7
640    *          the seventh mapping's value
641    * @param k8
642    *          the eighth mapping's key
643    * @param v8
644    *          the eighth mapping's value
645    * @param k9
646    *          the ninth mapping's key
647    * @param v9
648    *          the ninth mapping's value
649    * @param k10
650    *          the tenth mapping's key
651    * @param v10
652    *          the tenth mapping's value
653    * @return a map item containing the specified mappings
654    * @throws IllegalArgumentException
655    *           if there are any duplicate keys
656    * @throws NullPointerException
657    *           if any key or value is {@code null}
658    */
659   @SuppressWarnings("PMD.ExcessiveParameterList")
660   @NonNull
661   static <K extends IAnyAtomicItem, V extends ICollectionValue> IMapItem<V> of(
662       @NonNull K k1, @NonNull V v1,
663       @NonNull K k2, @NonNull V v2,
664       @NonNull K k3, @NonNull V v3,
665       @NonNull K k4, @NonNull V v4,
666       @NonNull K k5, @NonNull V v5,
667       @NonNull K k6, @NonNull V v6,
668       @NonNull K k7, @NonNull V v7,
669       @NonNull K k8, @NonNull V v8,
670       @NonNull K k9, @NonNull V v9,
671       @NonNull K k10, @NonNull V v10) {
672     return new MapItemN<>(
673         entry(k1, v1),
674         entry(k2, v2),
675         entry(k3, v3),
676         entry(k4, v4),
677         entry(k5, v5),
678         entry(k6, v6),
679         entry(k7, v7),
680         entry(k8, v8),
681         entry(k9, v9),
682         entry(k10, v10));
683     // CPD-ON
684   }
685 
686   /**
687    * Returns an unmodifiable map item containing keys and values extracted from
688    * the given entries. The entries themselves are not stored in the map.
689    *
690    * @param <K>
691    *          the map item's key type
692    * @param <V>
693    *          the map item's value type
694    * @param entries
695    *          {@code Map.Entry}s containing the keys and values from which the map
696    *          is populated
697    * @return a map item containing the specified mappings
698    * @throws IllegalArgumentException
699    *           if there are any duplicate keys
700    * @throws NullPointerException
701    *           if any entry, key, or value is {@code null}, or if the
702    *           {@code entries} array is {@code null}
703    */
704   @SafeVarargs
705   @SuppressWarnings("varargs")
706   @NonNull
707   static <K extends IAnyAtomicItem, V extends ICollectionValue>
708       IMapItem<V> ofEntries(Map.Entry<IMapKey, ? extends V>... entries) {
709     return entries.length == 0 ? empty() : new MapItemN<>(entries);
710   }
711 
712   /**
713    * Returns an unmodifiable {@link java.util.Map.Entry} containing the given key
714    * and value.
715    *
716    * @param <V>
717    *          the value's type
718    * @param key
719    *          the key
720    * @param value
721    *          the value
722    * @return an {@code Map.Entry} containing the specified key and value
723    * @throws NullPointerException
724    *           if the key or value is {@code null}
725    */
726   @NonNull
727   static <V extends ICollectionValue> Map.Entry<IMapKey, V> entry(@NonNull IAnyAtomicItem key, @NonNull V value) {
728     return entry(key.asMapKey(), value);
729   }
730 
731   /**
732    * Returns an unmodifiable {@link java.util.Map.Entry} containing the given key
733    * and value.
734    *
735    * @param <V>
736    *          the value's type
737    * @param key
738    *          the key
739    * @param value
740    *          the value
741    * @return an {@code Map.Entry} containing the specified key and value
742    * @throws NullPointerException
743    *           if the key or value is {@code null}
744    */
745   @SuppressWarnings("null")
746   @NonNull
747   static <V extends ICollectionValue> Map.Entry<IMapKey, V> entry(@NonNull IMapKey key, @NonNull V value) {
748     return Map.entry(key, value);
749   }
750 
751   /**
752    * Returns an unmodifiable Map item containing the entries of the given Map. The
753    * given Map must not be null, and it must not contain any null keys or values.
754    * If the given Map is subsequently modified, the returned Map will not reflect
755    * such modifications.
756    *
757    * @param <K>
758    *          the map item's key type
759    * @param <V>
760    *          the map item's value type
761    * @param map
762    *          a map item from which entries are drawn, must be non-null
763    * @return a map item containing the entries of the given {@code Map}
764    * @throws NullPointerException
765    *           if map is null, or if it contains any null keys or values
766    */
767   @SuppressWarnings("unchecked")
768   @NonNull
769   static <K extends IAnyAtomicItem, V extends ICollectionValue>
770       IMapItem<V> copyOf(Map<? extends IMapKey, ? extends V> map) {
771     return map instanceof IMapItem
772         ? (IMapItem<V>) map
773         : map.isEmpty()
774             ? empty()
775             : new MapItemN<>(new LinkedHashMap<>(map));
776   }
777 
778   @Override
779   default void accept(IItemVisitor visitor) {
780     visitor.visit(this);
781   }
782 }