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 * http://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
56 * <li>the {@code MapIterator}, see {@link #mapIterator()}
57 * <li>the {@code KeyValue} interface (just cast - no object creation)
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 @Override
125 public void remove() {
126 throw new UnsupportedOperationException();
127 }
128
129 @Override
130 public void reset() {
131 hasNext = true;
132 }
133
134 @Override
135 public V setValue(final V value) {
136 if (!canGetSet) {
137 throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
138 }
139 return parent.setValue(value);
140 }
141
142 @Override
143 public String toString() {
144 if (hasNext) {
145 return "Iterator[]";
146 }
147 return "Iterator[" + getKey() + "=" + getValue() + "]";
148 }
149 }
150
151 /**
152 * Values implementation for the SingletonMap.
153 * This class is needed as values is a view that must update as the map updates.
154 *
155 * @param <V> the type of the values in this set.
156 */
157 static class SingletonValues<V> extends AbstractSet<V> implements Serializable {
158 private static final long serialVersionUID = -3689524741863047872L;
159 private final SingletonMap<?, V> parent;
160
161 SingletonValues(final SingletonMap<?, V> parent) {
162 this.parent = parent;
163 }
164
165 @Override
166 public void clear() {
167 throw new UnsupportedOperationException();
168 }
169 @Override
170 public boolean contains(final Object object) {
171 return parent.containsValue(object);
172 }
173 @Override
174 public boolean isEmpty() {
175 return false;
176 }
177 @Override
178 public Iterator<V> iterator() {
179 return new SingletonIterator<>(parent.getValue(), false);
180 }
181 @Override
182 public int size() {
183 return 1;
184 }
185 }
186 /** Serialization version */
187 private static final long serialVersionUID = -8931271118676803261L;
188
189 /** Singleton key */
190 private final K key;
191
192 /** Singleton value */
193 private V value;
194
195 /**
196 * Constructor that creates a map of {@code null} to {@code null}.
197 */
198 public SingletonMap() {
199 this.key = null;
200 }
201
202 /**
203 * Constructor specifying the key and value.
204 *
205 * @param key the key to use
206 * @param value the value to use
207 */
208 public SingletonMap(final K key, final V value) {
209 this.key = key;
210 this.value = value;
211 }
212
213 /**
214 * Constructor specifying the key and value as a {@code KeyValue}.
215 *
216 * @param keyValue the key value pair to use
217 */
218 public SingletonMap(final KeyValue<K, V> keyValue) {
219 this.key = keyValue.getKey();
220 this.value = keyValue.getValue();
221 }
222
223 /**
224 * Constructor specifying the key and value as a {@code MapEntry}.
225 *
226 * @param mapEntry the mapEntry to use
227 */
228 public SingletonMap(final Map.Entry<? extends K, ? extends V> mapEntry) {
229 this.key = mapEntry.getKey();
230 this.value = mapEntry.getValue();
231 }
232
233 /**
234 * Constructor copying elements from another map.
235 *
236 * @param map the map to copy, must be size 1
237 * @throws NullPointerException if the map is null
238 * @throws IllegalArgumentException if the size is not 1
239 */
240 public SingletonMap(final Map<? extends K, ? extends V> map) {
241 if (map.size() != 1) {
242 throw new IllegalArgumentException("The map size must be 1");
243 }
244 final Map.Entry<? extends K, ? extends V> entry = map.entrySet().iterator().next();
245 this.key = entry.getKey();
246 this.value = entry.getValue();
247 }
248
249 /**
250 * Unsupported operation.
251 */
252 @Override
253 public void clear() {
254 throw new UnsupportedOperationException();
255 }
256
257 /**
258 * Clones the map without cloning the key or value.
259 *
260 * @return a shallow clone
261 */
262 @Override
263 @SuppressWarnings("unchecked")
264 public SingletonMap<K, V> clone() {
265 try {
266 return (SingletonMap<K, V>) super.clone();
267 } catch (final CloneNotSupportedException ex) {
268 throw new UnsupportedOperationException(ex);
269 }
270 }
271
272 /**
273 * Checks whether the map contains the specified key.
274 *
275 * @param key the key to search for
276 * @return true if the map contains the key
277 */
278 @Override
279 public boolean containsKey(final Object key) {
280 return isEqualKey(key);
281 }
282
283 /**
284 * Checks whether the map contains the specified value.
285 *
286 * @param value the value to search for
287 * @return true if the map contains the key
288 */
289 @Override
290 public boolean containsValue(final Object value) {
291 return isEqualValue(value);
292 }
293
294 /**
295 * Gets the entrySet view of the map.
296 * Changes made via {@code setValue} affect this map.
297 * To simply iterate through the entries, use {@link #mapIterator()}.
298 *
299 * @return the entrySet view
300 */
301 @Override
302 public Set<Map.Entry<K, V>> entrySet() {
303 final Map.Entry<K, V> entry = new TiedMapEntry<>(this, getKey());
304 return Collections.singleton(entry);
305 }
306
307 /**
308 * Compares this map with another.
309 *
310 * @param obj the object to compare to
311 * @return true if equal
312 */
313 @Override
314 public boolean equals(final Object obj) {
315 if (obj == this) {
316 return true;
317 }
318 if (!(obj instanceof Map)) {
319 return false;
320 }
321 final Map<?, ?> other = (Map<?, ?>) obj;
322 if (other.size() != 1) {
323 return false;
324 }
325 final Map.Entry<?, ?> entry = other.entrySet().iterator().next();
326 return isEqualKey(entry.getKey()) && isEqualValue(entry.getValue());
327 }
328
329 /**
330 * Gets the first (and only) key in the map.
331 *
332 * @return the key
333 */
334 @Override
335 public K firstKey() {
336 return getKey();
337 }
338
339 // Map
340 /**
341 * Gets the value mapped to the key specified.
342 *
343 * @param key the key
344 * @return the mapped value, null if no match
345 */
346 @Override
347 public V get(final Object key) {
348 if (isEqualKey(key)) {
349 return value;
350 }
351 return null;
352 }
353
354 // KeyValue
355 /**
356 * Gets the key.
357 *
358 * @return the key
359 */
360 @Override
361 public K getKey() {
362 return key;
363 }
364
365 /**
366 * Gets the value.
367 *
368 * @return the value
369 */
370 @Override
371 public V getValue() {
372 return value;
373 }
374
375 /**
376 * Gets the standard Map hashCode.
377 *
378 * @return the hash code defined in the Map interface
379 */
380 @Override
381 public int hashCode() {
382 return (getKey() == null ? 0 : getKey().hashCode()) ^
383 (getValue() == null ? 0 : getValue().hashCode());
384 }
385
386 /**
387 * Checks whether the map is currently empty, which it never is.
388 *
389 * @return false always
390 */
391 @Override
392 public boolean isEmpty() {
393 return false;
394 }
395
396 /**
397 * Compares the specified key to the stored key.
398 *
399 * @param key the key to compare
400 * @return true if equal
401 */
402 protected boolean isEqualKey(final Object key) {
403 return Objects.equals(key, getKey());
404 }
405
406 /**
407 * Compares the specified value to the stored value.
408 *
409 * @param value the value to compare
410 * @return true if equal
411 */
412 protected boolean isEqualValue(final Object value) {
413 return Objects.equals(value, getValue());
414 }
415
416 // BoundedMap
417 /**
418 * Is the map currently full, always true.
419 *
420 * @return true always
421 */
422 @Override
423 public boolean isFull() {
424 return true;
425 }
426
427 /**
428 * Gets the unmodifiable keySet view of the map.
429 * Changes made to the view affect this map.
430 * To simply iterate through the keys, use {@link #mapIterator()}.
431 *
432 * @return the keySet view
433 */
434 @Override
435 public Set<K> keySet() {
436 return Collections.singleton(key);
437 }
438
439 /**
440 * Gets the last (and only) key in the map.
441 *
442 * @return the key
443 */
444 @Override
445 public K lastKey() {
446 return getKey();
447 }
448
449 /**
450 * {@inheritDoc}
451 */
452 @Override
453 public OrderedMapIterator<K, V> mapIterator() {
454 return new SingletonMapIterator<>(this);
455 }
456
457 /**
458 * Gets the maximum size of the map, always 1.
459 *
460 * @return 1 always
461 */
462 @Override
463 public int maxSize() {
464 return 1;
465 }
466
467 /**
468 * Gets the next key after the key specified, always null.
469 *
470 * @param key the next key
471 * @return null always
472 */
473 @Override
474 public K nextKey(final K key) {
475 return null;
476 }
477
478 /**
479 * Gets the previous key before the key specified, always null.
480 *
481 * @param key the next key
482 * @return null always
483 */
484 @Override
485 public K previousKey(final K key) {
486 return null;
487 }
488
489 /**
490 * Puts a key-value mapping into this map where the key must match the existing key.
491 * <p>
492 * An IllegalArgumentException is thrown if the key does not match as the map
493 * is fixed size.
494 * </p>
495 *
496 * @param key the key to set, must be the key of the map
497 * @param value the value to set
498 * @return the value previously mapped to this key, null if none
499 * @throws IllegalArgumentException if the key does not match
500 */
501 @Override
502 public V put(final K key, final V value) {
503 if (isEqualKey(key)) {
504 return setValue(value);
505 }
506 throw new IllegalArgumentException("Cannot put new key/value pair - Map is fixed size singleton");
507 }
508
509 /**
510 * Puts the values from the specified map into this map.
511 * <p>
512 * The map must be of size 0 or size 1.
513 * If it is size 1, the key must match the key of this map otherwise an
514 * IllegalArgumentException is thrown.
515 * </p>
516 *
517 * @param map the map to add, must be size 0 or 1, and the key must match
518 * @throws NullPointerException if the map is null
519 * @throws IllegalArgumentException if the key does not match
520 */
521 @Override
522 public void putAll(final Map<? extends K, ? extends V> map) {
523 switch (map.size()) {
524 case 0:
525 return;
526
527 case 1:
528 final Map.Entry<? extends K, ? extends V> entry = map.entrySet().iterator().next();
529 put(entry.getKey(), entry.getValue());
530 return;
531
532 default:
533 throw new IllegalArgumentException("The map size must be 0 or 1");
534 }
535 }
536
537 /**
538 * Unsupported operation.
539 *
540 * @param key the mapping to remove
541 * @return the value mapped to the removed key, null if key not in map
542 * @throws UnsupportedOperationException always
543 */
544 @Override
545 public V remove(final Object key) {
546 throw new UnsupportedOperationException();
547 }
548
549 /**
550 * Sets the value.
551 *
552 * @param value the new value to set
553 * @return the old value
554 */
555 public V setValue(final V value) {
556 final V old = this.value;
557 this.value = value;
558 return old;
559 }
560
561 /**
562 * Gets the size of the map, always 1.
563 *
564 * @return the size of 1
565 */
566 @Override
567 public int size() {
568 return 1;
569 }
570
571 /**
572 * Gets the map as a String.
573 *
574 * @return a string version of the map
575 */
576 @Override
577 public String toString() {
578 return new StringBuilder(128)
579 .append('{')
580 .append(getKey() == this ? "(this Map)" : getKey())
581 .append('=')
582 .append(getValue() == this ? "(this Map)" : getValue())
583 .append('}')
584 .toString();
585 }
586
587 /**
588 * Gets the unmodifiable values view of the map.
589 * Changes made to the view affect this map.
590 * To simply iterate through the values, use {@link #mapIterator()}.
591 *
592 * @return the values view
593 */
594 @Override
595 public Collection<V> values() {
596 return new SingletonValues<>(this);
597 }
598
599 }