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