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