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