001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.collections.map;
018
019 import java.io.Serializable;
020 import java.util.AbstractSet;
021 import java.util.Collection;
022 import java.util.Collections;
023 import java.util.Iterator;
024 import java.util.Map;
025 import java.util.NoSuchElementException;
026 import java.util.Set;
027
028 import org.apache.commons.collections.BoundedMap;
029 import org.apache.commons.collections.KeyValue;
030 import org.apache.commons.collections.MapIterator;
031 import org.apache.commons.collections.OrderedMap;
032 import org.apache.commons.collections.OrderedMapIterator;
033 import org.apache.commons.collections.ResettableIterator;
034 import org.apache.commons.collections.iterators.SingletonIterator;
035 import org.apache.commons.collections.keyvalue.TiedMapEntry;
036
037 /**
038 * A <code>Map</code> implementation that holds a single item and is fixed size.
039 * <p>
040 * The single key/value pair is specified at creation.
041 * The map is fixed size so any action that would change the size is disallowed.
042 * However, the <code>put</code> or <code>setValue</code> methods can <i>change</i>
043 * the value associated with the key.
044 * <p>
045 * If trying to remove or clear the map, an UnsupportedOperationException is thrown.
046 * If trying to put a new mapping into the map, an IllegalArgumentException is thrown.
047 * The put method will only suceed if the key specified is the same as the
048 * singleton key.
049 * <p>
050 * The key and value can be obtained by:
051 * <ul>
052 * <li>normal Map methods and views
053 * <li>the <code>MapIterator</code>, see {@link #mapIterator()}
054 * <li>the <code>KeyValue</code> interface (just cast - no object creation)
055 * </ul>
056 *
057 * @since Commons Collections 3.1
058 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
059 *
060 * @author Stephen Colebourne
061 */
062 public class SingletonMap
063 implements OrderedMap, BoundedMap, KeyValue, Serializable, Cloneable {
064
065 /** Serialization version */
066 private static final long serialVersionUID = -8931271118676803261L;
067
068 /** Singleton key */
069 private final Object key;
070 /** Singleton value */
071 private Object value;
072
073 /**
074 * Constructor that creates a map of <code>null</code> to <code>null</code>.
075 */
076 public SingletonMap() {
077 super();
078 this.key = null;
079 }
080
081 /**
082 * Constructor specifying the key and value.
083 *
084 * @param key the key to use
085 * @param value the value to use
086 */
087 public SingletonMap(Object key, Object value) {
088 super();
089 this.key = key;
090 this.value = value;
091 }
092
093 /**
094 * Constructor specifying the key and value as a <code>KeyValue</code>.
095 *
096 * @param keyValue the key value pair to use
097 */
098 public SingletonMap(KeyValue keyValue) {
099 super();
100 this.key = keyValue.getKey();
101 this.value = keyValue.getValue();
102 }
103
104 /**
105 * Constructor specifying the key and value as a <code>MapEntry</code>.
106 *
107 * @param mapEntry the mapEntry to use
108 */
109 public SingletonMap(Map.Entry mapEntry) {
110 super();
111 this.key = mapEntry.getKey();
112 this.value = mapEntry.getValue();
113 }
114
115 /**
116 * Constructor copying elements from another map.
117 *
118 * @param map the map to copy, must be size 1
119 * @throws NullPointerException if the map is null
120 * @throws IllegalArgumentException if the size is not 1
121 */
122 public SingletonMap(Map map) {
123 super();
124 if (map.size() != 1) {
125 throw new IllegalArgumentException("The map size must be 1");
126 }
127 Map.Entry entry = (Map.Entry) map.entrySet().iterator().next();
128 this.key = entry.getKey();
129 this.value = entry.getValue();
130 }
131
132 // KeyValue
133 //-----------------------------------------------------------------------
134 /**
135 * Gets the key.
136 *
137 * @return the key
138 */
139 public Object getKey() {
140 return key;
141 }
142
143 /**
144 * Gets the value.
145 *
146 * @return the value
147 */
148 public Object getValue() {
149 return value;
150 }
151
152 /**
153 * Sets the value.
154 *
155 * @param value the new value to set
156 * @return the old value
157 */
158 public Object setValue(Object value) {
159 Object old = this.value;
160 this.value = value;
161 return old;
162 }
163
164 // BoundedMap
165 //-----------------------------------------------------------------------
166 /**
167 * Is the map currently full, always true.
168 *
169 * @return true always
170 */
171 public boolean isFull() {
172 return true;
173 }
174
175 /**
176 * Gets the maximum size of the map, always 1.
177 *
178 * @return 1 always
179 */
180 public int maxSize() {
181 return 1;
182 }
183
184 // Map
185 //-----------------------------------------------------------------------
186 /**
187 * Gets the value mapped to the key specified.
188 *
189 * @param key the key
190 * @return the mapped value, null if no match
191 */
192 public Object get(Object key) {
193 if (isEqualKey(key)) {
194 return value;
195 }
196 return null;
197 }
198
199 /**
200 * Gets the size of the map, always 1.
201 *
202 * @return the size of 1
203 */
204 public int size() {
205 return 1;
206 }
207
208 /**
209 * Checks whether the map is currently empty, which it never is.
210 *
211 * @return false always
212 */
213 public boolean isEmpty() {
214 return false;
215 }
216
217 //-----------------------------------------------------------------------
218 /**
219 * Checks whether the map contains the specified key.
220 *
221 * @param key the key to search for
222 * @return true if the map contains the key
223 */
224 public boolean containsKey(Object key) {
225 return (isEqualKey(key));
226 }
227
228 /**
229 * Checks whether the map contains the specified value.
230 *
231 * @param value the value to search for
232 * @return true if the map contains the key
233 */
234 public boolean containsValue(Object value) {
235 return (isEqualValue(value));
236 }
237
238 //-----------------------------------------------------------------------
239 /**
240 * Puts a key-value mapping into this map where the key must match the existing key.
241 * <p>
242 * An IllegalArgumentException is thrown if the key does not match as the map
243 * is fixed size.
244 *
245 * @param key the key to set, must be the key of the map
246 * @param value the value to set
247 * @return the value previously mapped to this key, null if none
248 * @throws IllegalArgumentException if the key does not match
249 */
250 public Object put(Object key, Object value) {
251 if (isEqualKey(key)) {
252 return setValue(value);
253 }
254 throw new IllegalArgumentException("Cannot put new key/value pair - Map is fixed size singleton");
255 }
256
257 /**
258 * Puts the values from the specified map into this map.
259 * <p>
260 * The map must be of size 0 or size 1.
261 * If it is size 1, the key must match the key of this map otherwise an
262 * IllegalArgumentException is thrown.
263 *
264 * @param map the map to add, must be size 0 or 1, and the key must match
265 * @throws NullPointerException if the map is null
266 * @throws IllegalArgumentException if the key does not match
267 */
268 public void putAll(Map map) {
269 switch (map.size()) {
270 case 0:
271 return;
272
273 case 1:
274 Map.Entry entry = (Map.Entry) map.entrySet().iterator().next();
275 put(entry.getKey(), entry.getValue());
276 return;
277
278 default:
279 throw new IllegalArgumentException("The map size must be 0 or 1");
280 }
281 }
282
283 /**
284 * Unsupported operation.
285 *
286 * @param key the mapping to remove
287 * @return the value mapped to the removed key, null if key not in map
288 * @throws UnsupportedOperationException always
289 */
290 public Object remove(Object key) {
291 throw new UnsupportedOperationException();
292 }
293
294 /**
295 * Unsupported operation.
296 */
297 public void clear() {
298 throw new UnsupportedOperationException();
299 }
300
301 //-----------------------------------------------------------------------
302 /**
303 * Gets the entrySet view of the map.
304 * Changes made via <code>setValue</code> affect this map.
305 * To simply iterate through the entries, use {@link #mapIterator()}.
306 *
307 * @return the entrySet view
308 */
309 public Set entrySet() {
310 Map.Entry entry = new TiedMapEntry(this, getKey());
311 return Collections.singleton(entry);
312 }
313
314 /**
315 * Gets the unmodifiable keySet view of the map.
316 * Changes made to the view affect this map.
317 * To simply iterate through the keys, use {@link #mapIterator()}.
318 *
319 * @return the keySet view
320 */
321 public Set keySet() {
322 return Collections.singleton(key);
323 }
324
325 /**
326 * Gets the unmodifiable values view of the map.
327 * Changes made to the view affect this map.
328 * To simply iterate through the values, use {@link #mapIterator()}.
329 *
330 * @return the values view
331 */
332 public Collection values() {
333 return new SingletonValues(this);
334 }
335
336 /**
337 * Gets an iterator over the map.
338 * Changes made to the iterator using <code>setValue</code> affect this map.
339 * The <code>remove</code> method is unsupported.
340 * <p>
341 * A MapIterator returns the keys in the map. It also provides convenient
342 * methods to get the key and value, and set the value.
343 * It avoids the need to create an entrySet/keySet/values object.
344 * It also avoids creating the Map Entry object.
345 *
346 * @return the map iterator
347 */
348 public MapIterator mapIterator() {
349 return new SingletonMapIterator(this);
350 }
351
352 // OrderedMap
353 //-----------------------------------------------------------------------
354 /**
355 * Obtains an <code>OrderedMapIterator</code> over the map.
356 * <p>
357 * A ordered map iterator is an efficient way of iterating over maps
358 * in both directions.
359 *
360 * @return an ordered map iterator
361 */
362 public OrderedMapIterator orderedMapIterator() {
363 return new SingletonMapIterator(this);
364 }
365
366 /**
367 * Gets the first (and only) key in the map.
368 *
369 * @return the key
370 */
371 public Object firstKey() {
372 return getKey();
373 }
374
375 /**
376 * Gets the last (and only) key in the map.
377 *
378 * @return the key
379 */
380 public Object lastKey() {
381 return getKey();
382 }
383
384 /**
385 * Gets the next key after the key specified, always null.
386 *
387 * @param key the next key
388 * @return null always
389 */
390 public Object nextKey(Object key) {
391 return null;
392 }
393
394 /**
395 * Gets the previous key before the key specified, always null.
396 *
397 * @param key the next key
398 * @return null always
399 */
400 public Object previousKey(Object key) {
401 return null;
402 }
403
404 //-----------------------------------------------------------------------
405 /**
406 * Compares the specified key to the stored key.
407 *
408 * @param key the key to compare
409 * @return true if equal
410 */
411 protected boolean isEqualKey(Object key) {
412 return (key == null ? getKey() == null : key.equals(getKey()));
413 }
414
415 /**
416 * Compares the specified value to the stored value.
417 *
418 * @param value the value to compare
419 * @return true if equal
420 */
421 protected boolean isEqualValue(Object value) {
422 return (value == null ? getValue() == null : value.equals(getValue()));
423 }
424
425 //-----------------------------------------------------------------------
426 /**
427 * SingletonMapIterator.
428 */
429 static class SingletonMapIterator implements OrderedMapIterator, ResettableIterator {
430 private final SingletonMap parent;
431 private boolean hasNext = true;
432 private boolean canGetSet = false;
433
434 SingletonMapIterator(SingletonMap parent) {
435 super();
436 this.parent = parent;
437 }
438
439 public boolean hasNext() {
440 return hasNext;
441 }
442
443 public Object next() {
444 if (hasNext == false) {
445 throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY);
446 }
447 hasNext = false;
448 canGetSet = true;
449 return parent.getKey();
450 }
451
452 public boolean hasPrevious() {
453 return (hasNext == false);
454 }
455
456 public Object previous() {
457 if (hasNext == true) {
458 throw new NoSuchElementException(AbstractHashedMap.NO_PREVIOUS_ENTRY);
459 }
460 hasNext = true;
461 return parent.getKey();
462 }
463
464 public void remove() {
465 throw new UnsupportedOperationException();
466 }
467
468 public Object getKey() {
469 if (canGetSet == false) {
470 throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID);
471 }
472 return parent.getKey();
473 }
474
475 public Object getValue() {
476 if (canGetSet == false) {
477 throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID);
478 }
479 return parent.getValue();
480 }
481
482 public Object setValue(Object value) {
483 if (canGetSet == false) {
484 throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID);
485 }
486 return parent.setValue(value);
487 }
488
489 public void reset() {
490 hasNext = true;
491 }
492
493 public String toString() {
494 if (hasNext) {
495 return "Iterator[]";
496 } else {
497 return "Iterator[" + getKey() + "=" + getValue() + "]";
498 }
499 }
500 }
501
502 /**
503 * Values implementation for the SingletonMap.
504 * This class is needed as values is a view that must update as the map updates.
505 */
506 static class SingletonValues extends AbstractSet implements Serializable {
507 private static final long serialVersionUID = -3689524741863047872L;
508 private final SingletonMap parent;
509
510 SingletonValues(SingletonMap parent) {
511 super();
512 this.parent = parent;
513 }
514
515 public int size() {
516 return 1;
517 }
518 public boolean isEmpty() {
519 return false;
520 }
521 public boolean contains(Object object) {
522 return parent.containsValue(object);
523 }
524 public void clear() {
525 throw new UnsupportedOperationException();
526 }
527 public Iterator iterator() {
528 return new SingletonIterator(parent.getValue(), false);
529 }
530 }
531
532 //-----------------------------------------------------------------------
533 /**
534 * Clones the map without cloning the key or value.
535 *
536 * @return a shallow clone
537 */
538 public Object clone() {
539 try {
540 SingletonMap cloned = (SingletonMap) super.clone();
541 return cloned;
542 } catch (CloneNotSupportedException ex) {
543 throw new InternalError();
544 }
545 }
546
547 /**
548 * Compares this map with another.
549 *
550 * @param obj the object to compare to
551 * @return true if equal
552 */
553 public boolean equals(Object obj) {
554 if (obj == this) {
555 return true;
556 }
557 if (obj instanceof Map == false) {
558 return false;
559 }
560 Map other = (Map) obj;
561 if (other.size() != 1) {
562 return false;
563 }
564 Map.Entry entry = (Map.Entry) other.entrySet().iterator().next();
565 return isEqualKey(entry.getKey()) && isEqualValue(entry.getValue());
566 }
567
568 /**
569 * Gets the standard Map hashCode.
570 *
571 * @return the hash code defined in the Map interface
572 */
573 public int hashCode() {
574 return (getKey() == null ? 0 : getKey().hashCode()) ^
575 (getValue() == null ? 0 : getValue().hashCode());
576 }
577
578 /**
579 * Gets the map as a String.
580 *
581 * @return a string version of the map
582 */
583 public String toString() {
584 return new StringBuffer(128)
585 .append('{')
586 .append((getKey() == this ? "(this Map)" : getKey()))
587 .append('=')
588 .append((getValue() == this ? "(this Map)" : getValue()))
589 .append('}')
590 .toString();
591 }
592
593 }