1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.collections4.map;
18
19 import java.io.Serializable;
20 import java.util.AbstractSet;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.Iterator;
24 import java.util.Map;
25 import java.util.NoSuchElementException;
26 import java.util.Objects;
27 import java.util.Set;
28
29 import org.apache.commons.collections4.BoundedMap;
30 import org.apache.commons.collections4.KeyValue;
31 import org.apache.commons.collections4.OrderedMap;
32 import org.apache.commons.collections4.OrderedMapIterator;
33 import org.apache.commons.collections4.ResettableIterator;
34 import org.apache.commons.collections4.iterators.SingletonIterator;
35 import org.apache.commons.collections4.keyvalue.TiedMapEntry;
36
37 /**
38 * A {@code Map} implementation that holds a single item and is fixed size.
39 * <p>
40 * The single key/value pair is specified at creation.
41 * The map is fixed size so any action that would change the size is disallowed.
42 * However, the {@code put} or {@code setValue} methods can <em>change</em>
43 * the value associated with the key.
44 * </p>
45 * <p>
46 * If trying to remove or clear the map, an UnsupportedOperationException is thrown.
47 * If trying to put a new mapping into the map, an IllegalArgumentException is thrown.
48 * The put method will only succeed if the key specified is the same as the
49 * singleton key.
50 * </p>
51 * <p>
52 * The key and value can be obtained by:
53 * </p>
54 * <ul>
55 * <li>normal Map methods and views</li>
56 * <li>the {@code MapIterator}, see {@link #mapIterator()}</li>
57 * <li>the {@code KeyValue} interface (just cast - no object creation)</li>
58 * </ul>
59 *
60 * @param <K> The type of the keys in this map
61 * @param <V> The type of the values in this map
62 * @since 3.1
63 */
64 public class SingletonMap<K, V>
65 implements OrderedMap<K, V>, BoundedMap<K, V>, KeyValue<K, V>, Serializable, Cloneable {
66
67 /**
68 * SingletonMapIterator.
69 */
70 static class SingletonMapIterator<K, V> implements OrderedMapIterator<K, V>, ResettableIterator<K> {
71 private final SingletonMap<K, V> parent;
72 private boolean hasNext = true;
73 private boolean canGetSet;
74
75 SingletonMapIterator(final SingletonMap<K, V> parent) {
76 this.parent = parent;
77 }
78
79 @Override
80 public K getKey() {
81 if (!canGetSet) {
82 throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
83 }
84 return parent.getKey();
85 }
86
87 @Override
88 public V getValue() {
89 if (!canGetSet) {
90 throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
91 }
92 return parent.getValue();
93 }
94
95 @Override
96 public boolean hasNext() {
97 return hasNext;
98 }
99
100 @Override
101 public boolean hasPrevious() {
102 return !hasNext;
103 }
104
105 @Override
106 public K next() {
107 if (!hasNext) {
108 throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY);
109 }
110 hasNext = false;
111 canGetSet = true;
112 return parent.getKey();
113 }
114
115 @Override
116 public K previous() {
117 if (hasNext) {
118 throw new NoSuchElementException(AbstractHashedMap.NO_PREVIOUS_ENTRY);
119 }
120 hasNext = true;
121 return parent.getKey();
122 }
123
124 /**
125 * Always throws {@link UnsupportedOperationException}.
126 *
127 * @throws UnsupportedOperationException Always thrown.
128 */
129 @Override
130 public void remove() {
131 throw new UnsupportedOperationException();
132 }
133
134 @Override
135 public void reset() {
136 hasNext = true;
137 }
138
139 @Override
140 public V setValue(final V value) {
141 if (!canGetSet) {
142 throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
143 }
144 return parent.setValue(value);
145 }
146
147 @Override
148 public String toString() {
149 if (hasNext) {
150 return "Iterator[]";
151 }
152 return "Iterator[" + getKey() + "=" + getValue() + "]";
153 }
154 }
155
156 /**
157 * Values implementation for the SingletonMap.
158 * This class is needed as values is a view that must update as the map updates.
159 *
160 * @param <V> The type of the values in this set.
161 */
162 static class SingletonValues<V> extends AbstractSet<V> implements Serializable {
163 private static final long serialVersionUID = -3689524741863047872L;
164 private final SingletonMap<?, V> parent;
165
166 SingletonValues(final SingletonMap<?, V> parent) {
167 this.parent = parent;
168 }
169
170 /**
171 * Always throws {@link UnsupportedOperationException}.
172 *
173 * @throws UnsupportedOperationException Always thrown.
174 */
175 @Override
176 public void clear() {
177 throw new UnsupportedOperationException();
178 }
179 @Override
180 public boolean contains(final Object object) {
181 return parent.containsValue(object);
182 }
183 @Override
184 public boolean isEmpty() {
185 return false;
186 }
187 @Override
188 public Iterator<V> iterator() {
189 return new SingletonIterator<>(parent.getValue(), false);
190 }
191 @Override
192 public int size() {
193 return 1;
194 }
195 }
196
197 /** Serialization version */
198 private static final long serialVersionUID = -8931271118676803261L;
199
200 /** Singleton key */
201 private final K key;
202
203 /** Singleton value */
204 private V value;
205
206 /**
207 * Constructor that creates a map of {@code null} to {@code null}.
208 */
209 public SingletonMap() {
210 this.key = null;
211 }
212
213 /**
214 * Constructor specifying the key and value.
215 *
216 * @param key The key to use
217 * @param value The value to use
218 */
219 public SingletonMap(final K key, final V value) {
220 this.key = key;
221 this.value = value;
222 }
223
224 /**
225 * Constructor specifying the key and value as a {@code KeyValue}.
226 *
227 * @param keyValue The key value pair to use
228 */
229 public SingletonMap(final KeyValue<K, V> keyValue) {
230 this.key = keyValue.getKey();
231 this.value = keyValue.getValue();
232 }
233
234 /**
235 * Constructor specifying the key and value as a {@code MapEntry}.
236 *
237 * @param mapEntry The mapEntry to use
238 */
239 public SingletonMap(final Map.Entry<? extends K, ? extends V> mapEntry) {
240 this.key = mapEntry.getKey();
241 this.value = mapEntry.getValue();
242 }
243
244 /**
245 * Constructor copying elements from another map.
246 *
247 * @param map The map to copy, must be size 1
248 * @throws NullPointerException if the map is null
249 * @throws IllegalArgumentException if the size is not 1
250 */
251 public SingletonMap(final Map<? extends K, ? extends V> map) {
252 if (map.size() != 1) {
253 throw new IllegalArgumentException("The map size must be 1");
254 }
255 final Map.Entry<? extends K, ? extends V> entry = map.entrySet().iterator().next();
256 this.key = entry.getKey();
257 this.value = entry.getValue();
258 }
259
260 /**
261 * Always throws {@link UnsupportedOperationException}.
262 *
263 * @throws UnsupportedOperationException Always thrown.
264 */
265 @Override
266 public void clear() {
267 throw new UnsupportedOperationException();
268 }
269
270 /**
271 * Clones the map without cloning the key or value.
272 *
273 * @return A shallow clone
274 */
275 @Override
276 @SuppressWarnings("unchecked")
277 public SingletonMap<K, V> clone() {
278 try {
279 return (SingletonMap<K, V>) super.clone();
280 } catch (final CloneNotSupportedException ex) {
281 throw new UnsupportedOperationException(ex);
282 }
283 }
284
285 /**
286 * Checks whether the map contains the specified key.
287 *
288 * @param key The key to search for
289 * @return true if the map contains the key
290 */
291 @Override
292 public boolean containsKey(final Object key) {
293 return isEqualKey(key);
294 }
295
296 /**
297 * Checks whether the map contains the specified value.
298 *
299 * @param value The value to search for
300 * @return true if the map contains the key
301 */
302 @Override
303 public boolean containsValue(final Object value) {
304 return isEqualValue(value);
305 }
306
307 /**
308 * Gets the entrySet view of the map.
309 * Changes made via {@code setValue} affect this map.
310 * To simply iterate through the entries, use {@link #mapIterator()}.
311 *
312 * @return The entrySet view
313 */
314 @Override
315 public Set<Map.Entry<K, V>> entrySet() {
316 final Map.Entry<K, V> entry = new TiedMapEntry<>(this, getKey());
317 return Collections.singleton(entry);
318 }
319
320 /**
321 * Compares this map with another.
322 *
323 * @param obj The object to compare to
324 * @return true if equal
325 */
326 @Override
327 public boolean equals(final Object obj) {
328 if (obj == this) {
329 return true;
330 }
331 if (!(obj instanceof Map)) {
332 return false;
333 }
334 final Map<?, ?> other = (Map<?, ?>) obj;
335 if (other.size() != 1) {
336 return false;
337 }
338 final Map.Entry<?, ?> entry = other.entrySet().iterator().next();
339 return isEqualKey(entry.getKey()) && isEqualValue(entry.getValue());
340 }
341
342 /**
343 * Gets the first (and only) key in the map.
344 *
345 * @return The key
346 */
347 @Override
348 public K firstKey() {
349 return getKey();
350 }
351
352 /**
353 * Gets the value mapped to the key specified.
354 *
355 * @param key The key
356 * @return The mapped value, null if no match
357 */
358 @Override
359 public V get(final Object key) {
360 if (isEqualKey(key)) {
361 return value;
362 }
363 return null;
364 }
365
366 /**
367 * Gets the key.
368 *
369 * @return The key
370 */
371 @Override
372 public K getKey() {
373 return key;
374 }
375
376 /**
377 * Gets the value.
378 *
379 * @return The value
380 */
381 @Override
382 public V getValue() {
383 return value;
384 }
385
386 /**
387 * Gets the standard Map hashCode.
388 *
389 * @return The hash code defined in the Map interface
390 */
391 @Override
392 public int hashCode() {
393 return (getKey() == null ? 0 : getKey().hashCode()) ^
394 (getValue() == null ? 0 : getValue().hashCode());
395 }
396
397 /**
398 * Checks whether the map is currently empty, which it never is.
399 *
400 * @return false always
401 */
402 @Override
403 public boolean isEmpty() {
404 return false;
405 }
406
407 /**
408 * Compares the specified key to the stored key.
409 *
410 * @param key The key to compare
411 * @return true if equal
412 */
413 protected boolean isEqualKey(final Object key) {
414 return Objects.equals(key, getKey());
415 }
416
417 /**
418 * Compares the specified value to the stored value.
419 *
420 * @param value The value to compare
421 * @return true if equal
422 */
423 protected boolean isEqualValue(final Object value) {
424 return Objects.equals(value, getValue());
425 }
426
427 /**
428 * Is the map currently full, always true.
429 *
430 * @return true always
431 */
432 @Override
433 public boolean isFull() {
434 return true;
435 }
436
437 /**
438 * Gets the unmodifiable keySet view of the map.
439 * Changes made to the view affect this map.
440 * To simply iterate through the keys, use {@link #mapIterator()}.
441 *
442 * @return The keySet view
443 */
444 @Override
445 public Set<K> keySet() {
446 return Collections.singleton(key);
447 }
448
449 /**
450 * Gets the last (and only) key in the map.
451 *
452 * @return The key
453 */
454 @Override
455 public K lastKey() {
456 return getKey();
457 }
458
459 /**
460 * {@inheritDoc}
461 */
462 @Override
463 public OrderedMapIterator<K, V> mapIterator() {
464 return new SingletonMapIterator<>(this);
465 }
466
467 /**
468 * Gets the maximum size of the map, always 1.
469 *
470 * @return 1 always
471 */
472 @Override
473 public int maxSize() {
474 return 1;
475 }
476
477 /**
478 * Gets the next key after the key specified, always null.
479 *
480 * @param key The next key
481 * @return null always
482 */
483 @Override
484 public K nextKey(final K key) {
485 return null;
486 }
487
488 /**
489 * Gets the previous key before the key specified, always null.
490 *
491 * @param key The next key
492 * @return null always
493 */
494 @Override
495 public K previousKey(final K key) {
496 return null;
497 }
498
499 /**
500 * Puts a key-value mapping into this map where the key must match the existing key.
501 * <p>
502 * An IllegalArgumentException is thrown if the key does not match as the map
503 * is fixed size.
504 * </p>
505 *
506 * @param key The key to set, must be the key of the map
507 * @param value The value to set
508 * @return The value previously mapped to this key, null if none
509 * @throws IllegalArgumentException if the key does not match
510 */
511 @Override
512 public V put(final K key, final V value) {
513 if (isEqualKey(key)) {
514 return setValue(value);
515 }
516 throw new IllegalArgumentException("Cannot put new key/value pair - Map is fixed size singleton");
517 }
518
519 /**
520 * Puts the values from the specified map into this map.
521 * <p>
522 * The map must be of size 0 or size 1.
523 * If it is size 1, the key must match the key of this map otherwise an
524 * IllegalArgumentException is thrown.
525 * </p>
526 *
527 * @param map The map to add, must be size 0 or 1, and the key must match
528 * @throws NullPointerException if the map is null
529 * @throws IllegalArgumentException if the key does not match
530 */
531 @Override
532 public void putAll(final Map<? extends K, ? extends V> map) {
533 switch (map.size()) {
534 case 0:
535 return;
536
537 case 1:
538 final Map.Entry<? extends K, ? extends V> entry = map.entrySet().iterator().next();
539 put(entry.getKey(), entry.getValue());
540 return;
541
542 default:
543 throw new IllegalArgumentException("The map size must be 0 or 1");
544 }
545 }
546
547 /**
548 * Always throws {@link UnsupportedOperationException}.
549 *
550 * @param key Ignored.
551 * @throws UnsupportedOperationException Always thrown.
552 */
553 @Override
554 public V remove(final Object key) {
555 throw new UnsupportedOperationException();
556 }
557
558 /**
559 * Sets the value.
560 *
561 * @param value The new value to set
562 * @return The old value
563 */
564 public V setValue(final V value) {
565 final V old = this.value;
566 this.value = value;
567 return old;
568 }
569
570 /**
571 * Gets the size of the map, always 1.
572 *
573 * @return The size of 1
574 */
575 @Override
576 public int size() {
577 return 1;
578 }
579
580 /**
581 * Gets the map as a String.
582 *
583 * @return A string version of the map
584 */
585 @Override
586 public String toString() {
587 return new StringBuilder(128)
588 .append('{')
589 .append(getKey() == this ? "(this Map)" : getKey())
590 .append('=')
591 .append(getValue() == this ? "(this Map)" : getValue())
592 .append('}')
593 .toString();
594 }
595
596 /**
597 * Gets the unmodifiable values view of the map.
598 * Changes made to the view affect this map.
599 * To simply iterate through the values, use {@link #mapIterator()}.
600 *
601 * @return The values view
602 */
603 @Override
604 public Collection<V> values() {
605 return new SingletonValues<>(this);
606 }
607
608 }