Add ImmutableMapTemplate
[yangtools.git] / common / util / src / main / java / org / opendaylight / yangtools / util / ImmutableMapTemplate.java
1 /*
2  * Copyright (c) 2018 Pantheon Technologies, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.util;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.Beta;
14 import com.google.common.base.MoreObjects;
15 import com.google.common.collect.ImmutableMap;
16 import java.util.Arrays;
17 import java.util.Collection;
18 import java.util.Iterator;
19 import java.util.Map;
20 import java.util.Map.Entry;
21 import java.util.Objects;
22 import java.util.Set;
23 import java.util.function.BiFunction;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.opendaylight.yangtools.concepts.Immutable;
26
27 /**
28  * Template for instantiating {@link UnmodifiableMapPhase} instances with a fixed set of keys. The template can then be
29  * used as a factory for instances via using {@link #instantiateTransformed(Map, BiFunction)} or, more efficiently,
30  * using {@link #instantiateWithValues(Object[])} where the argument array has values ordered corresponding to the key
31  * order defined by {@link #keySet()}.
32  *
33  * @param <K> the type of keys maintained by this template
34  */
35 @Beta
36 public abstract class ImmutableMapTemplate<K> implements Immutable {
37     private abstract static class AbstractMultiple<K> extends ImmutableMapTemplate<K> {
38         private final @NonNull ImmutableMap<K, Integer> offsets;
39
40         AbstractMultiple(final ImmutableMap<K, Integer> offsets) {
41             this.offsets = requireNonNull(offsets);
42         }
43
44         @Override
45         public final Set<K> keySet() {
46             return offsets.keySet();
47         }
48
49         @Override
50         public final <T, V> @NonNull ImmutableOffsetMap<K, V> instantiateTransformed(final Map<K, T> fromMap,
51                 final BiFunction<K, T, V> valueTransformer) {
52             final int size = offsets.size();
53             checkArgument(fromMap.size() == size);
54
55             @SuppressWarnings("unchecked")
56             final V[] objects = (V[]) new Object[size];
57             for (Entry<K, T> entry : fromMap.entrySet()) {
58                 final K key = requireNonNull(entry.getKey());
59                 final Integer offset = offsets.get(key);
60                 checkArgument(offset != null, "Key %s present in input, but not in offsets %s", key, offsets);
61
62                 objects[offset.intValue()] = transformValue(key, entry.getValue(), valueTransformer);
63             }
64
65             return createMap(offsets, objects);
66         }
67
68         @Override
69         @SafeVarargs
70         public final <V> @NonNull UnmodifiableMapPhase<K, V> instantiateWithValues(final V... values) {
71             checkArgument(values.length == offsets.size());
72             final V[] copy = values.clone();
73             Arrays.stream(copy).forEach(Objects::requireNonNull);
74             return createMap(offsets, values);
75         }
76
77         @Override
78         public final String toString() {
79             return MoreObjects.toStringHelper(this).add("offsets", offsets).toString();
80         }
81
82         abstract <V> @NonNull ImmutableOffsetMap<K, V> createMap(ImmutableMap<K, Integer> offsets, V[] objects);
83     }
84
85     private static final class Ordered<K> extends AbstractMultiple<K> {
86         Ordered(final Collection<K> keys) {
87             super(OffsetMapCache.orderedOffsets(keys));
88         }
89
90         @Override
91         <V> @NonNull ImmutableOffsetMap<K, V> createMap(final ImmutableMap<K, Integer> offsets, final V[] objects) {
92             return new ImmutableOffsetMap.Ordered<>(offsets, objects);
93         }
94     }
95
96     private static final class Unordered<K> extends AbstractMultiple<K> {
97         Unordered(final Collection<K> keys) {
98             super(OffsetMapCache.unorderedOffsets(keys));
99         }
100
101         @Override
102         <V> @NonNull ImmutableOffsetMap<K, V> createMap(final ImmutableMap<K, Integer> offsets, final V[] objects) {
103             return new ImmutableOffsetMap.Unordered<>(offsets, objects);
104         }
105     }
106
107     private abstract static class AbstractSingle<K> extends ImmutableMapTemplate<K> {
108         private final @NonNull SingletonSet<K> keySet;
109
110         AbstractSingle(final K key) {
111             this.keySet = SharedSingletonMap.cachedSet(key);
112         }
113
114         @Override
115         public Set<K> keySet() {
116             return keySet;
117         }
118
119         @Override
120         public final <T, V> @NonNull SharedSingletonMap<K, V> instantiateTransformed(final Map<K, T> fromMap,
121                 final BiFunction<K, T, V> valueTransformer) {
122             final Iterator<Entry<K, T>> it = fromMap.entrySet().iterator();
123             checkArgument(it.hasNext(), "Input is empty while expecting 1 item");
124
125             final Entry<K, T> entry = it.next();
126             final K expected = keySet.getElement();
127             final K actual = entry.getKey();
128             checkArgument(expected.equals(actual), "Unexpected key %s, expecting %s", actual, expected);
129
130             final V value = transformValue(actual, entry.getValue(), valueTransformer);
131             checkArgument(!it.hasNext(), "Input has more than one item");
132
133             return createMap(keySet, value);
134         }
135
136         @Override
137         @SafeVarargs
138         public final <V> @NonNull UnmodifiableMapPhase<K, V> instantiateWithValues(final V... values) {
139             checkArgument(values.length == 1);
140             return createMap(keySet, values[0]);
141         }
142
143         @Override
144         public final String toString() {
145             return MoreObjects.toStringHelper(this).add("keySet", keySet).toString();
146         }
147
148         abstract <V> @NonNull SharedSingletonMap<K, V> createMap(SingletonSet<K> keySet, V value);
149     }
150
151     private static final class SingleOrdered<K> extends AbstractSingle<K> {
152         SingleOrdered(final K key) {
153             super(key);
154         }
155
156         @Override
157         <V> @NonNull SharedSingletonMap<K, V> createMap(final SingletonSet<K> keySet, final V value) {
158             return new SharedSingletonMap.Ordered<>(keySet, value);
159         }
160     }
161
162     private static final class SingleUnordered<K> extends AbstractSingle<K> {
163         SingleUnordered(final K key) {
164             super(key);
165         }
166
167         @Override
168         <V> @NonNull SharedSingletonMap<K, V> createMap(final SingletonSet<K> keySet, final V value) {
169             return new SharedSingletonMap.Unordered<>(keySet, value);
170         }
171     }
172
173     ImmutableMapTemplate() {
174         // Hidden on purpose
175     }
176
177     /**
178      * Create a template which produces Maps with specified keys, with iteration order matching the iteration order
179      * of {@code keys}. {@link #keySet()} will return these keys in exactly the same order. The resulting map will
180      * retain insertion order through {@link UnmodifiableMapPhase#toModifiableMap()} transformations.
181      *
182      * @param keys Keys in requested iteration order.
183      * @param <K> the type of keys maintained by resulting template
184      * @return A template object.
185      * @throws NullPointerException if {@code keys} or any of its elements is null
186      * @throws IllegalArgumentException if {@code keys} is empty
187      */
188     public static <K> @NonNull ImmutableMapTemplate<K> ordered(final Collection<K> keys) {
189         switch (keys.size()) {
190             case 0:
191                 throw new IllegalArgumentException("Proposed keyset must not be empty");
192             case 1:
193                 return new SingleOrdered<>(keys.iterator().next());
194             default:
195                 return new Ordered<>(keys);
196         }
197     }
198
199     /**
200      * Create a template which produces Maps with specified keys, with unconstrained iteration order. Produced maps
201      * will have the iteration order matching the order returned by {@link #keySet()}.  The resulting map will
202      * NOT retain ordering through {@link UnmodifiableMapPhase#toModifiableMap()} transformations.
203      *
204      * @param keys Keys in any iteration order.
205      * @param <K> the type of keys maintained by resulting template
206      * @return A template object.
207      * @throws NullPointerException if {@code keys} or any of its elements is null
208      * @throws IllegalArgumentException if {@code keys} is empty
209      */
210     public static <K> @NonNull ImmutableMapTemplate<K> unordered(final Collection<K> keys) {
211         switch (keys.size()) {
212             case 0:
213                 throw new IllegalArgumentException("Proposed keyset must not be empty");
214             case 1:
215                 return new SingleUnordered<>(keys.iterator().next());
216             default:
217                 return new Unordered<>(keys);
218         }
219     }
220
221     /**
222      * Instantiate an immutable map by applying specified {@code transformer} to values of {@code fromMap}.
223      *
224      * @param fromMap Input map
225      * @param keyValueTransformer Transformation to apply to values
226      * @param <T> the type of input values
227      * @param <V> the type of mapped values
228      * @return An immutable map
229      * @throws NullPointerException if any of the arguments is null or if the transformer produces a {@code null} value
230      * @throws IllegalArgumentException if {@code fromMap#keySet()} does not match this template's keys
231      */
232     public abstract <T, V> @NonNull UnmodifiableMapPhase<K, V> instantiateTransformed(Map<K, T> fromMap,
233             BiFunction<K, T, V> keyValueTransformer);
234
235     /**
236      * Instantiate an immutable map by filling values from provided array. The array MUST be ordered to match key order
237      * as returned by {@link #keySet()}.
238      *
239      * @param values Values to use
240      * @param <V> the type of mapped values
241      * @return An immutable map
242      * @throws NullPointerException if {@code values} or any of its elements is null
243      * @throws IllegalArgumentException if {@code values.lenght} does not match the number of keys in this template
244      */
245     @SuppressWarnings("unchecked")
246     public abstract <V> @NonNull UnmodifiableMapPhase<K, V> instantiateWithValues(V... values);
247
248     /**
249      * Returns the set of keys expected by this template, in the iteration order Maps resulting from instantiation
250      * will have.
251      *
252      * @return This template's key set
253      * @see Map#keySet()
254      */
255     public abstract Set<K> keySet();
256
257     final <T, V> @NonNull V transformValue(final K key, final T input, final BiFunction<K, T, V> transformer) {
258         final V value = transformer.apply(key, input);
259         checkArgument(value != null, "Transformer returned null for input %s at key %s", input, key);
260         return value;
261     }
262 }