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