bba914f7ea42f4c4211b877726e8db7ffff56f03
[yangtools.git] / common / util / src / main / java / org / opendaylight / yangtools / util / ImmutableOffsetMap.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. 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 com.google.common.base.Verify.verifyNotNull;
12 import static java.util.Objects.requireNonNull;
13
14 import com.google.common.annotations.Beta;
15 import com.google.common.collect.ImmutableMap;
16 import com.google.common.collect.UnmodifiableIterator;
17 import java.io.IOException;
18 import java.io.ObjectInputStream;
19 import java.io.ObjectOutputStream;
20 import java.io.Serializable;
21 import java.lang.reflect.Field;
22 import java.util.AbstractMap.SimpleImmutableEntry;
23 import java.util.AbstractSet;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import org.eclipse.jdt.annotation.NonNull;
32 import org.eclipse.jdt.annotation.Nullable;
33
34 /**
35  * Implementation of the {@link Map} interface which stores a set of immutable mappings using a key-to-offset map and
36  * a backing array. This is useful for situations where the same key set is shared across a multitude of maps, as this
37  * class uses a global cache to share the key-to-offset mapping.
38  *
39  * <p>
40  * In case the set of keys is statically known, you can use {@link ImmutableOffsetMapTemplate} to efficiently create
41  * {@link ImmutableOffsetMap} instances.
42  *
43  * @param <K> the type of keys maintained by this map
44  * @param <V> the type of mapped values
45  */
46 @Beta
47 public abstract class ImmutableOffsetMap<K, V> implements UnmodifiableMapPhase<K, V>, Serializable {
48     static final class Ordered<K, V> extends ImmutableOffsetMap<K, V> {
49         private static final long serialVersionUID = 1L;
50
51         Ordered(final ImmutableMap<K, Integer> offsets, final V[] objects) {
52             super(offsets, objects);
53         }
54
55         @Override
56         public @NonNull MutableOffsetMap<K, V> toModifiableMap() {
57             return MutableOffsetMap.orderedCopyOf(this);
58         }
59
60         @Override
61         void setFields(final List<K> keys, final V[] values) throws IOException {
62             setField(this, OFFSETS_FIELD, OffsetMapCache.orderedOffsets(keys));
63             setField(this, ARRAY_FIELD, values);
64         }
65     }
66
67     static final class Unordered<K, V> extends ImmutableOffsetMap<K, V> {
68         private static final long serialVersionUID = 1L;
69
70         Unordered(final ImmutableMap<K, Integer> offsets, final V[] objects) {
71             super(offsets, objects);
72         }
73
74         @Override
75         public @NonNull MutableOffsetMap<K, V> toModifiableMap() {
76             return MutableOffsetMap.unorderedCopyOf(this);
77         }
78
79         @Override
80         void setFields(final List<K> keys, final V[] values) throws IOException {
81             final Map<K, Integer> newOffsets = OffsetMapCache.unorderedOffsets(keys);
82
83             setField(this, OFFSETS_FIELD, newOffsets);
84             setField(this, ARRAY_FIELD, OffsetMapCache.adjustedArray(newOffsets, keys, values));
85         }
86     }
87
88     private static final long serialVersionUID = 1L;
89
90     private final transient @NonNull ImmutableMap<K, Integer> offsets;
91     private final transient @NonNull V[] objects;
92     private transient int hashCode;
93
94     /**
95      * Construct a new instance backed by specified key-to-offset map and array of objects.
96      *
97      * @param offsets Key-to-offset map, may not be null
98      * @param objects Array of value object, may not be null. The array is stored as is, the caller
99      *              is responsible for ensuring its contents remain unmodified.
100      */
101     ImmutableOffsetMap(final ImmutableMap<K, Integer> offsets, final V[] objects) {
102         this.offsets = requireNonNull(offsets);
103         this.objects = requireNonNull(objects);
104         checkArgument(offsets.size() == objects.length);
105     }
106
107     @Override
108     public abstract @NonNull MutableOffsetMap<K, V> toModifiableMap();
109
110     abstract void setFields(List<K> keys, V[] values) throws IOException;
111
112     /**
113      * Create an {@link ImmutableOffsetMap} as a copy of an existing map. This is actually not completely true, as this
114      * method returns an {@link ImmutableMap} for empty and singleton inputs, as those are more memory-efficient. This
115      * method also recognizes {@link ImmutableOffsetMap} and {@link SharedSingletonMap} on input, and returns it back
116      * without doing anything else. It also recognizes {@link MutableOffsetMap} (as returned by
117      * {@link #toModifiableMap()}) and makes an efficient copy of its contents. All other maps are converted to an
118      * {@link ImmutableOffsetMap} with the same iteration order as input.
119      *
120      * @param <K> the type of keys maintained by the map
121      * @param <V> the type of mapped values
122      * @param map Input map, may not be null.
123      * @return An isolated, immutable copy of the input map
124      * @throws NullPointerException if {@code map} or any of its elements is null.
125      */
126     public static <K, V> @NonNull Map<K, V> orderedCopyOf(final @NonNull Map<K, V> map) {
127         final Map<K, V> common = commonCopy(map);
128         if (common != null) {
129             return common;
130         }
131
132         final int size = map.size();
133         if (size == 1) {
134             // Efficient single-entry implementation
135             final Entry<K, V> e = map.entrySet().iterator().next();
136             return SharedSingletonMap.orderedOf(e.getKey(), e.getValue());
137         }
138
139         final ImmutableMap<K, Integer> offsets = OffsetMapCache.orderedOffsets(map.keySet());
140         return new Ordered<>(offsets, createArray(offsets, map));
141     }
142
143     /**
144      * Create an {@link ImmutableOffsetMap} as a copy of an existing map. This is actually not completely true, as this
145      * method returns an {@link ImmutableMap} for empty and singleton inputs, as those are more memory-efficient. This
146      * method also recognizes {@link ImmutableOffsetMap} and {@link SharedSingletonMap} on input, and returns it back
147      * without doing anything else. It also recognizes {@link MutableOffsetMap} (as returned by
148      * {@link #toModifiableMap()}) and makes an efficient copy of its contents. All other maps are converted to an
149      * {@link ImmutableOffsetMap}. Iterator order is not guaranteed to be retained.
150      *
151      * @param <K> the type of keys maintained by the map
152      * @param <V> the type of mapped values
153      * @param map Input map, may not be null.
154      * @return An isolated, immutable copy of the input map
155      * @throws NullPointerException if {@code map} or any of its elements is null.
156      */
157     public static <K, V> @NonNull Map<K, V> unorderedCopyOf(final @NonNull Map<K, V> map) {
158         final Map<K, V> common = commonCopy(map);
159         if (common != null) {
160             return common;
161         }
162
163         if (map.size() == 1) {
164             // Efficient single-entry implementation
165             final Entry<K, V> e = map.entrySet().iterator().next();
166             return SharedSingletonMap.unorderedOf(e.getKey(), e.getValue());
167         }
168
169         final ImmutableMap<K, Integer> offsets = OffsetMapCache.unorderedOffsets(map.keySet());
170         return new Unordered<>(offsets, createArray(offsets, map));
171     }
172
173     private static <K, V> V[] createArray(final ImmutableMap<K, Integer> offsets, final Map<K, V> map) {
174         @SuppressWarnings("unchecked")
175         final V[] array = (V[]) new Object[offsets.size()];
176         for (Entry<K, V> e : map.entrySet()) {
177             array[verifyNotNull(offsets.get(e.getKey()))] = e.getValue();
178         }
179         return array;
180     }
181
182     private static <K, V> @Nullable Map<K, V> commonCopy(final @NonNull Map<K, V> map) {
183         // Prevent a copy. Note that ImmutableMap is not listed here because of its potentially larger keySet overhead.
184         if (map instanceof ImmutableOffsetMap || map instanceof SharedSingletonMap) {
185             return map;
186         }
187
188         // Familiar and efficient to copy
189         if (map instanceof MutableOffsetMap) {
190             return ((MutableOffsetMap<K, V>) map).toUnmodifiableMap();
191         }
192
193         if (map.isEmpty()) {
194             // Shares a single object
195             return ImmutableMap.of();
196         }
197
198         return null;
199     }
200
201     @Override
202     public final int size() {
203         return offsets.size();
204     }
205
206     @Override
207     public final boolean isEmpty() {
208         return offsets.isEmpty();
209     }
210
211     @Override
212     public final int hashCode() {
213         if (hashCode != 0) {
214             return hashCode;
215         }
216
217         int result = 0;
218         for (Entry<K, Integer> e : offsets.entrySet()) {
219             result += e.getKey().hashCode() ^ objects[e.getValue()].hashCode();
220         }
221
222         hashCode = result;
223         return result;
224     }
225
226     @Override
227     public final boolean equals(final Object obj) {
228         if (obj == this) {
229             return true;
230         }
231         if (!(obj instanceof Map)) {
232             return false;
233         }
234
235         if (obj instanceof ImmutableOffsetMap) {
236             final ImmutableOffsetMap<?, ?> om = (ImmutableOffsetMap<?, ?>) obj;
237
238             // If the offset match, the arrays have to match, too
239             if (offsets.equals(om.offsets)) {
240                 return Arrays.deepEquals(objects, om.objects);
241             }
242         } else if (obj instanceof MutableOffsetMap) {
243             // Let MutableOffsetMap do the actual work.
244             return obj.equals(this);
245         }
246
247         final Map<?, ?> other = (Map<?, ?>)obj;
248
249         // Size and key sets have to match
250         if (size() != other.size() || !keySet().equals(other.keySet())) {
251             return false;
252         }
253
254         try {
255             // Ensure all objects are present
256             for (Entry<K, Integer> e : offsets.entrySet()) {
257                 if (!objects[e.getValue()].equals(other.get(e.getKey()))) {
258                     return false;
259                 }
260             }
261         } catch (ClassCastException e) {
262             // Can be thrown by other.get() indicating we have incompatible key types
263             return false;
264         }
265
266         return true;
267     }
268
269     @Override
270     public final boolean containsKey(final Object key) {
271         return offsets.containsKey(key);
272     }
273
274     @Override
275     public final boolean containsValue(final Object value) {
276         for (Object o : objects) {
277             if (value.equals(o)) {
278                 return true;
279             }
280         }
281         return false;
282     }
283
284     @Override
285     public final V get(final Object key) {
286         Integer offset;
287         return (offset = offsets.get(key)) == null ? null : objects[offset];
288     }
289
290     @Override
291     public final V remove(final Object key) {
292         throw new UnsupportedOperationException();
293     }
294
295     @Override
296     public final V put(final K key, final V value) {
297         throw new UnsupportedOperationException();
298     }
299
300     @Override
301     @SuppressWarnings("checkstyle:parameterName")
302     public final void putAll(final Map<? extends K, ? extends V> m) {
303         throw new UnsupportedOperationException();
304     }
305
306     @Override
307     public final void clear() {
308         throw new UnsupportedOperationException();
309     }
310
311     @Override
312     public final Set<K> keySet() {
313         return offsets.keySet();
314     }
315
316     @Override
317     public final @NonNull Collection<V> values() {
318         return new ConstantArrayCollection<>(objects);
319     }
320
321     @Override
322     public final @NonNull Set<Entry<K, V>> entrySet() {
323         return new EntrySet();
324     }
325
326     @Override
327     public final String toString() {
328         final StringBuilder sb = new StringBuilder("{");
329         final Iterator<K> it = offsets.keySet().iterator();
330         int offset = 0;
331         while (it.hasNext()) {
332             sb.append(it.next()).append('=').append(objects[offset++]);
333
334             if (it.hasNext()) {
335                 sb.append(", ");
336             }
337         }
338
339         return sb.append('}').toString();
340     }
341
342     final @NonNull ImmutableMap<K, Integer> offsets() {
343         return offsets;
344     }
345
346     final @NonNull V[] objects() {
347         return objects;
348     }
349
350     private final class EntrySet extends AbstractSet<Entry<K, V>> {
351         @Override
352         public @NonNull Iterator<Entry<K, V>> iterator() {
353             final Iterator<Entry<K, Integer>> it = offsets.entrySet().iterator();
354             return new UnmodifiableIterator<>() {
355                 @Override
356                 public boolean hasNext() {
357                     return it.hasNext();
358                 }
359
360                 @Override
361                 public Entry<K, V> next() {
362                     final Entry<K, Integer> e = it.next();
363                     return new SimpleImmutableEntry<>(e.getKey(), objects[e.getValue()]);
364                 }
365             };
366         }
367
368         @Override
369         public int size() {
370             return offsets.size();
371         }
372     }
373
374     private void writeObject(final ObjectOutputStream out) throws IOException {
375         out.writeInt(offsets.size());
376         for (Entry<K, V> e : entrySet()) {
377             out.writeObject(e.getKey());
378             out.writeObject(e.getValue());
379         }
380     }
381
382     private static final Field OFFSETS_FIELD = fieldFor("offsets");
383     private static final Field ARRAY_FIELD = fieldFor("objects");
384
385     private static @NonNull Field fieldFor(final @NonNull String name) {
386         final Field f;
387         try {
388             f = ImmutableOffsetMap.class.getDeclaredField(name);
389         } catch (NoSuchFieldException | SecurityException e) {
390             throw new IllegalStateException("Failed to lookup field " + name, e);
391         }
392
393         f.setAccessible(true);
394         return f;
395     }
396
397     private static void setField(final @NonNull ImmutableOffsetMap<?, ?> map, final @NonNull Field field,
398             final Object value) throws IOException {
399         try {
400             field.set(map, value);
401         } catch (IllegalArgumentException | IllegalAccessException e) {
402             throw new IOException("Failed to set field " + field, e);
403         }
404     }
405
406     @SuppressWarnings("unchecked")
407     private void readObject(final @NonNull ObjectInputStream in) throws IOException, ClassNotFoundException {
408         final int s = in.readInt();
409
410         final List<K> keys = new ArrayList<>(s);
411         final V[] values = (V[]) new Object[s];
412
413         for (int i = 0; i < s; ++i) {
414             keys.add((K)in.readObject());
415             values[i] = (V)in.readObject();
416         }
417
418         setFields(keys, values);
419     }
420 }