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