Define a feature-parent
[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.cache.CacheBuilder;
14 import com.google.common.cache.CacheLoader;
15 import com.google.common.cache.LoadingCache;
16 import java.io.Serializable;
17 import java.util.AbstractMap.SimpleImmutableEntry;
18 import java.util.Map;
19 import org.eclipse.jdt.annotation.NonNull;
20
21 /**
22  * Implementation of the {@link Map} interface which stores a single mapping. The key set is shared among all instances
23  * which contain the same key. This implementation does not support null keys or values.
24  *
25  * <p>
26  * In case the set of keys is statically known, you can use {@link SharedSingletonMapTemplate} to efficiently create
27  * {@link SharedSingletonMap} instances.
28  *
29  * @param <K> the type of keys maintained by this map
30  * @param <V> the type of mapped values
31  */
32 public abstract sealed class SharedSingletonMap<K, V> implements Serializable, UnmodifiableMapPhase<K, V> {
33     static final class Ordered<K, V> extends SharedSingletonMap<K, V> {
34         @java.io.Serial
35         private static final long serialVersionUID = 1L;
36
37         Ordered(final K key, final V value) {
38             super(key, value);
39         }
40
41         Ordered(final SingletonSet<K> keySet, final V value) {
42             super(keySet, value);
43         }
44
45         @Override
46         public @NonNull ModifiableMapPhase<K, V> toModifiableMap() {
47             return MutableOffsetMap.orderedCopyOf(this);
48         }
49     }
50
51     static final class Unordered<K, V> extends SharedSingletonMap<K, V> {
52         @java.io.Serial
53         private static final long serialVersionUID = 1L;
54
55         Unordered(final K key, final V value) {
56             super(key, value);
57         }
58
59         Unordered(final SingletonSet<K> keySet, final V value) {
60             super(keySet, value);
61         }
62
63         @Override
64         public @NonNull ModifiableMapPhase<K, V> toModifiableMap() {
65             return MutableOffsetMap.unorderedCopyOf(this);
66         }
67     }
68
69     @java.io.Serial
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     private SharedSingletonMap(final SingletonSet<K> keySet, final V value) {
84         this.keySet = requireNonNull(keySet);
85         this.value = requireNonNull(value);
86     }
87
88     private 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     public final Entry<K, V> getEntry() {
147         return new SimpleImmutableEntry<>(keySet.getElement(), value);
148     }
149
150     @Override
151     public final @NonNull SingletonSet<Entry<K, V>> entrySet() {
152         return SingletonSet.of(getEntry());
153     }
154
155     @Override
156     public final @NonNull SingletonSet<K> keySet() {
157         return keySet;
158     }
159
160     @Override
161     public final @NonNull SingletonSet<V> values() {
162         return SingletonSet.of(value);
163     }
164
165     @Override
166     public final boolean containsKey(final Object key) {
167         return keySet.contains(key);
168     }
169
170     @Override
171     @SuppressWarnings("checkstyle:hiddenField")
172     public final boolean containsValue(final Object value) {
173         return this.value.equals(value);
174     }
175
176     @Override
177     public final V get(final Object key) {
178         return keySet.contains(key) ? value : null;
179     }
180
181     @Override
182     public final int size() {
183         return 1;
184     }
185
186     @Override
187     public final boolean isEmpty() {
188         return false;
189     }
190
191     @Override
192     @SuppressWarnings("checkstyle:hiddenField")
193     public final V put(final K key, final V value) {
194         throw new UnsupportedOperationException();
195     }
196
197     @Override
198     public final V remove(final Object key) {
199         throw new UnsupportedOperationException();
200     }
201
202     @Override
203     @SuppressWarnings("checkstyle:parameterName")
204     public final void putAll(final Map<? extends K, ? extends V> m) {
205         throw new UnsupportedOperationException();
206     }
207
208     @Override
209     public final void clear() {
210         throw new UnsupportedOperationException();
211     }
212
213     @Override
214     public final int hashCode() {
215         if (hashCode == 0) {
216             hashCode = keySet.getElement().hashCode() ^ value.hashCode();
217         }
218         return hashCode;
219     }
220
221     @Override
222     public final boolean equals(final Object obj) {
223         return this == obj || obj instanceof Map<?, ?> other && other.size() == 1
224             && value.equals(other.get(keySet.getElement()));
225     }
226
227     @Override
228     public final String toString() {
229         return "{" + keySet.getElement() + '=' + value + '}';
230     }
231
232     @SuppressWarnings("unchecked")
233     static <K> @NonNull SingletonSet<K> cachedSet(final K key) {
234         return (SingletonSet<K>) CACHE.getUnchecked(key);
235     }
236
237     private static <K, V> Entry<K, V> singleEntry(final Map<K, V> map) {
238         final var it = map.entrySet().iterator();
239         checkArgument(it.hasNext(), "Input map is empty");
240         final var ret = it.next();
241         checkArgument(!it.hasNext(), "Input map has more than one entry");
242         return ret;
243     }
244 }