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