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 */ 017package org.apache.commons.collections4.map; 018 019import java.io.Serializable; 020import java.util.AbstractSet; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.Iterator; 024import java.util.Map; 025import java.util.NoSuchElementException; 026import java.util.Set; 027 028import org.apache.commons.collections4.BoundedMap; 029import org.apache.commons.collections4.KeyValue; 030import org.apache.commons.collections4.OrderedMap; 031import org.apache.commons.collections4.OrderedMapIterator; 032import org.apache.commons.collections4.ResettableIterator; 033import org.apache.commons.collections4.iterators.SingletonIterator; 034import org.apache.commons.collections4.keyvalue.TiedMapEntry; 035 036/** 037 * A <code>Map</code> implementation that holds a single item and is fixed size. 038 * <p> 039 * The single key/value pair is specified at creation. 040 * The map is fixed size so any action that would change the size is disallowed. 041 * However, the <code>put</code> or <code>setValue</code> methods can <i>change</i> 042 * the value associated with the key. 043 * <p> 044 * If trying to remove or clear the map, an UnsupportedOperationException is thrown. 045 * If trying to put a new mapping into the map, an IllegalArgumentException is thrown. 046 * The put method will only suceed if the key specified is the same as the 047 * singleton key. 048 * <p> 049 * The key and value can be obtained by: 050 * <ul> 051 * <li>normal Map methods and views 052 * <li>the <code>MapIterator</code>, see {@link #mapIterator()} 053 * <li>the <code>KeyValue</code> interface (just cast - no object creation) 054 * </ul> 055 * 056 * @since 3.1 057 * @version $Id: SingletonMap.html 972421 2015-11-14 20:00:04Z tn $ 058 */ 059public class SingletonMap<K, V> 060 implements OrderedMap<K, V>, BoundedMap<K, V>, KeyValue<K, V>, Serializable, Cloneable { 061 062 /** Serialization version */ 063 private static final long serialVersionUID = -8931271118676803261L; 064 065 /** Singleton key */ 066 private final K key; 067 /** Singleton value */ 068 private V value; 069 070 /** 071 * Constructor that creates a map of <code>null</code> to <code>null</code>. 072 */ 073 public SingletonMap() { 074 super(); 075 this.key = null; 076 } 077 078 /** 079 * Constructor specifying the key and value. 080 * 081 * @param key the key to use 082 * @param value the value to use 083 */ 084 public SingletonMap(final K key, final V value) { 085 super(); 086 this.key = key; 087 this.value = value; 088 } 089 090 /** 091 * Constructor specifying the key and value as a <code>KeyValue</code>. 092 * 093 * @param keyValue the key value pair to use 094 */ 095 public SingletonMap(final KeyValue<K, V> keyValue) { 096 super(); 097 this.key = keyValue.getKey(); 098 this.value = keyValue.getValue(); 099 } 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<? extends K, ? extends 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<? extends K, ? extends V> map) { 120 super(); 121 if (map.size() != 1) { 122 throw new IllegalArgumentException("The map size must be 1"); 123 } 124 final Map.Entry<? extends K, ? extends 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}