Factor out {SharedSingleton,ImmutableOffset}MapTemplate
[yangtools.git] / common / util / src / main / java / org / opendaylight / yangtools / util / SharedSingletonMap.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 java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.Beta;
14 import com.google.common.cache.CacheBuilder;
15 import com.google.common.cache.CacheLoader;
16 import com.google.common.cache.LoadingCache;
17 import java.io.Serializable;
18 import java.util.AbstractMap.SimpleImmutableEntry;
19 import java.util.Iterator;
20 import java.util.Map;
21 import org.eclipse.jdt.annotation.NonNull;
22
23 /**
24  * Implementation of the {@link Map} interface which stores a single mapping. The key set is shared among all instances
25  * which contain the same key. This implementation does not support null keys or values.
26  *
27  * <p>
28  * In case the set of keys is statically known, you can use {@link SharedSingletonMapTemplate} to efficiently create
29  * {@link SharedSingletonMap} instances.
30  *
31  * @param <K> the type of keys maintained by this map
32  * @param <V> the type of mapped values
33  */
34 @Beta
35 public abstract class SharedSingletonMap<K, V> implements Serializable, UnmodifiableMapPhase<K, V> {
36     static final class Ordered<K, V> extends SharedSingletonMap<K, V> {
37         private static final long serialVersionUID = 1L;
38
39         Ordered(final K key, final V value) {
40             super(key, value);
41         }
42
43         Ordered(final SingletonSet<K> keySet, final V value) {
44             super(keySet, value);
45         }
46
47         @Override
48         public ModifiableMapPhase<K, V> toModifiableMap() {
49             return MutableOffsetMap.orderedCopyOf(this);
50         }
51     }
52
53     static final class Unordered<K, V> extends SharedSingletonMap<K, V> {
54         private static final long serialVersionUID = 1L;
55
56         Unordered(final K key, final V value) {
57             super(key, value);
58         }
59
60         Unordered(final SingletonSet<K> keySet, final V value) {
61             super(keySet, value);
62         }
63
64         @Override
65         public ModifiableMapPhase<K, V> toModifiableMap() {
66             return MutableOffsetMap.unorderedCopyOf(this);
67         }
68     }
69
70     private static final long serialVersionUID = 1L;
71     private static final LoadingCache<Object, SingletonSet<Object>> CACHE = CacheBuilder.newBuilder().weakValues()
72             .build(new CacheLoader<Object, SingletonSet<Object>>() {
73                 @Override
74                 public SingletonSet<Object> load(final Object key) {
75                     return SingletonSet.of(key);
76                 }
77             });
78
79     private final @NonNull SingletonSet<K> keySet;
80     private final @NonNull V value;
81     private int hashCode;
82
83     SharedSingletonMap(final SingletonSet<K> keySet, final V value) {
84         this.keySet = requireNonNull(keySet);
85         this.value = requireNonNull(value);
86     }
87
88     SharedSingletonMap(final K key, final V value) {
89         this(cachedSet(key), value);
90     }
91
92     /**
93      * Create a {@link SharedSingletonMap} of specified {@code key} and {@code value}, which retains insertion order
94      * when transformed via {@link #toModifiableMap()}.
95      *
96      * @param key key
97      * @param value value
98      * @return A SharedSingletonMap
99      * @throws NullPointerException if any of the arguments is null
100      */
101     public static <K, V> @NonNull SharedSingletonMap<K, V> orderedOf(final K key, final V value) {
102         return new Ordered<>(key, value);
103     }
104
105     /**
106      * Create a {@link SharedSingletonMap} of specified {@code key} and {@code value}, which does not retain insertion
107      * order when transformed via {@link #toModifiableMap()}.
108      *
109      * @param key key
110      * @param value value
111      * @return A SharedSingletonMap
112      * @throws NullPointerException if any of the arguments is null
113      */
114     public static <K, V> @NonNull SharedSingletonMap<K, V> unorderedOf(final K key, final V value) {
115         return new Unordered<>(key, value);
116     }
117
118     /**
119      * Create a {@link SharedSingletonMap} of specified {@code key} and {@code value}, which retains insertion order
120      * when transformed via {@link #toModifiableMap()}.
121      *
122      * @param map input map
123      * @return A SharedSingletonMap
124      * @throws NullPointerException if {@code map} is null
125      * @throws IllegalArgumentException if {@code map} does not have exactly one entry
126      */
127     public static <K, V> @NonNull SharedSingletonMap<K, V> orderedCopyOf(final Map<K, V> map) {
128         final Entry<K, V> e = singleEntry(map);
129         return new Ordered<>(e.getKey(), e.getValue());
130     }
131
132     /**
133      * Create a {@link SharedSingletonMap} from specified single-element map, which does not retain insertion order when
134      * transformed via {@link #toModifiableMap()}.
135      *
136      * @param map input map
137      * @return A SharedSingletonMap
138      * @throws NullPointerException if {@code map} is null
139      * @throws IllegalArgumentException if {@code map} does not have exactly one entry
140      */
141     public static <K, V> @NonNull SharedSingletonMap<K, V> unorderedCopyOf(final Map<K, V> map) {
142         final Entry<K, V> e = singleEntry(map);
143         return new Unordered<>(e.getKey(), e.getValue());
144     }
145
146     @Override
147     public final SingletonSet<Entry<K, V>> entrySet() {
148         return SingletonSet.of(new SimpleImmutableEntry<>(keySet.getElement(), value));
149     }
150
151     @Override
152     public final SingletonSet<K> keySet() {
153         return keySet;
154     }
155
156     @Override
157     public final SingletonSet<V> values() {
158         return SingletonSet.of(value);
159     }
160
161     @Override
162     public final boolean containsKey(final Object key) {
163         return keySet.contains(key);
164     }
165
166     @Override
167     @SuppressWarnings("checkstyle:hiddenField")
168     public final boolean containsValue(final Object value) {
169         return this.value.equals(value);
170     }
171
172     @Override
173     public final V get(final Object key) {
174         return keySet.contains(key) ? value : null;
175     }
176
177     @Override
178     public final int size() {
179         return 1;
180     }
181
182     @Override
183     public final boolean isEmpty() {
184         return false;
185     }
186
187     @Override
188     @SuppressWarnings("checkstyle:hiddenField")
189     public final V put(final K key, final V value) {
190         throw new UnsupportedOperationException();
191     }
192
193     @Override
194     public final V remove(final Object key) {
195         throw new UnsupportedOperationException();
196     }
197
198     @Override
199     @SuppressWarnings("checkstyle:parameterName")
200     public final void putAll(final Map<? extends K, ? extends V> m) {
201         throw new UnsupportedOperationException();
202     }
203
204     @Override
205     public final void clear() {
206         throw new UnsupportedOperationException();
207     }
208
209     @Override
210     public final int hashCode() {
211         if (hashCode == 0) {
212             hashCode = keySet.getElement().hashCode() ^ value.hashCode();
213         }
214         return hashCode;
215     }
216
217     @Override
218     public final boolean equals(final Object obj) {
219         if (this == obj) {
220             return true;
221         }
222         if (!(obj instanceof Map)) {
223             return false;
224         }
225
226         final Map<?, ?> m = (Map<?, ?>)obj;
227         return m.size() == 1 && value.equals(m.get(keySet.getElement()));
228     }
229
230     @Override
231     public final String toString() {
232         return "{" + keySet.getElement() + '=' + value + '}';
233     }
234
235     @SuppressWarnings("unchecked")
236     static <K> @NonNull SingletonSet<K> cachedSet(final K key) {
237         return (SingletonSet<K>) CACHE.getUnchecked(key);
238     }
239
240     private static <K, V> Entry<K, V> singleEntry(final Map<K, V> map) {
241         final Iterator<Entry<K, V>> it = map.entrySet().iterator();
242         checkArgument(it.hasNext(), "Input map is empty");
243         final Entry<K, V> ret = it.next();
244         checkArgument(!it.hasNext(), "Input map has more than one entry");
245         return ret;
246     }
247 }