001 /* 002 * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. 003 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 004 * 005 * 006 * 007 * 008 * 009 * 010 * 011 * 012 * 013 * 014 * 015 * 016 * 017 * 018 * 019 * 020 * 021 * 022 * 023 * 024 */ 025 026 package railo.commons.util.mod; 027 028 import java.io.Externalizable; 029 import java.util.Collection; 030 import java.util.HashMap; 031 import java.util.Iterator; 032 import java.util.Map; 033 import java.util.Set; 034 035 import railo.runtime.exp.ExpressionException; 036 import railo.runtime.exp.PageException; 037 038 public abstract class AbstractMapPro<K,V> implements MapPro<K,V>,Externalizable { 039 /** 040 * Sole constructor. (For invocation by subclass constructors, typically 041 * implicit.) 042 */ 043 protected AbstractMapPro() { 044 } 045 046 // TODO better implementation 047 @Override 048 public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException { 049 out.writeObject(new HashMap(this)); 050 } 051 @Override 052 public void readExternal(java.io.ObjectInput in) throws java.io.IOException, ClassNotFoundException { 053 this.putAll((Map)in.readObject()); 054 } 055 056 057 058 // Query Operations 059 060 /** 061 * {@inheritDoc} 062 * 063 * <p>This implementation returns <tt>entrySet().size()</tt>. 064 */ 065 public int size() { 066 return entrySet().size(); 067 } 068 069 /** 070 * {@inheritDoc} 071 * 072 * <p>This implementation returns <tt>size() == 0</tt>. 073 */ 074 public boolean isEmpty() { 075 return size() == 0; 076 } 077 078 /** 079 * {@inheritDoc} 080 * 081 * <p>This implementation iterates over <tt>entrySet()</tt> searching 082 * for an entry with the specified value. If such an entry is found, 083 * <tt>true</tt> is returned. If the iteration terminates without 084 * finding such an entry, <tt>false</tt> is returned. Note that this 085 * implementation requires linear time in the size of the map. 086 * 087 * @throws ClassCastException {@inheritDoc} 088 * @throws NullPointerException {@inheritDoc} 089 */ 090 public boolean containsValue(Object value) { 091 Iterator<Entry<K,V>> i = entrySet().iterator(); 092 if (value==null) { 093 while (i.hasNext()) { 094 Entry<K,V> e = i.next(); 095 if (e.getValue()==null) 096 return true; 097 } 098 } else { 099 while (i.hasNext()) { 100 Entry<K,V> e = i.next(); 101 if (value.equals(e.getValue())) 102 return true; 103 } 104 } 105 return false; 106 } 107 108 /** 109 * {@inheritDoc} 110 * 111 * <p>This implementation iterates over <tt>entrySet()</tt> searching 112 * for an entry with the specified key. If such an entry is found, 113 * <tt>true</tt> is returned. If the iteration terminates without 114 * finding such an entry, <tt>false</tt> is returned. Note that this 115 * implementation requires linear time in the size of the map; many 116 * implementations will override this method. 117 * 118 * @throws ClassCastException {@inheritDoc} 119 * @throws NullPointerException {@inheritDoc} 120 */ 121 public boolean containsKey(Object key) { 122 Iterator<Map.Entry<K,V>> i = entrySet().iterator(); 123 if (key==null) { 124 while (i.hasNext()) { 125 Entry<K,V> e = i.next(); 126 if (e.getKey()==null) 127 return true; 128 } 129 } else { 130 while (i.hasNext()) { 131 Entry<K,V> e = i.next(); 132 if (key.equals(e.getKey())) 133 return true; 134 } 135 } 136 return false; 137 } 138 139 /** 140 * {@inheritDoc} 141 * 142 * <p>This implementation iterates over <tt>entrySet()</tt> searching 143 * for an entry with the specified key. If such an entry is found, 144 * the entry's value is returned. If the iteration terminates without 145 * finding such an entry, <tt>null</tt> is returned. Note that this 146 * implementation requires linear time in the size of the map; many 147 * implementations will override this method. 148 * 149 * @throws ClassCastException {@inheritDoc} 150 * @throws NullPointerException {@inheritDoc} 151 */ 152 public V get(Object key) { 153 Iterator<Entry<K,V>> i = entrySet().iterator(); 154 if (key==null) { 155 while (i.hasNext()) { 156 Entry<K,V> e = i.next(); 157 if (e.getKey()==null) 158 return e.getValue(); 159 } 160 } else { 161 while (i.hasNext()) { 162 Entry<K,V> e = i.next(); 163 if (key.equals(e.getKey())) 164 return e.getValue(); 165 } 166 } 167 return null; 168 } 169 170 171 // Modification Operations 172 173 /** 174 * {@inheritDoc} 175 * 176 * <p>This implementation always throws an 177 * <tt>UnsupportedOperationException</tt>. 178 * 179 * @throws UnsupportedOperationException {@inheritDoc} 180 * @throws ClassCastException {@inheritDoc} 181 * @throws NullPointerException {@inheritDoc} 182 * @throws IllegalArgumentException {@inheritDoc} 183 */ 184 public V put(K key, V value) { 185 throw new UnsupportedOperationException(); 186 } 187 188 /** 189 * {@inheritDoc} 190 * 191 * <p>This implementation iterates over <tt>entrySet()</tt> searching for an 192 * entry with the specified key. If such an entry is found, its value is 193 * obtained with its <tt>getValue</tt> operation, the entry is removed 194 * from the collection (and the backing map) with the iterator's 195 * <tt>remove</tt> operation, and the saved value is returned. If the 196 * iteration terminates without finding such an entry, <tt>null</tt> is 197 * returned. Note that this implementation requires linear time in the 198 * size of the map; many implementations will override this method. 199 * 200 * <p>Note that this implementation throws an 201 * <tt>UnsupportedOperationException</tt> if the <tt>entrySet</tt> 202 * iterator does not support the <tt>remove</tt> method and this map 203 * contains a mapping for the specified key. 204 * 205 * @throws UnsupportedOperationException {@inheritDoc} 206 * @throws ClassCastException {@inheritDoc} 207 * @throws NullPointerException {@inheritDoc} 208 */ 209 public V remove(Object key) { 210 Iterator<Entry<K,V>> i = entrySet().iterator(); 211 Entry<K,V> correctEntry = null; 212 if (key==null) { 213 while (correctEntry==null && i.hasNext()) { 214 Entry<K,V> e = i.next(); 215 if (e.getKey()==null) 216 correctEntry = e; 217 } 218 } else { 219 while (correctEntry==null && i.hasNext()) { 220 Entry<K,V> e = i.next(); 221 if (key.equals(e.getKey())) 222 correctEntry = e; 223 } 224 } 225 226 V oldValue = null; 227 if (correctEntry !=null) { 228 oldValue = correctEntry.getValue(); 229 i.remove(); 230 } 231 return oldValue; 232 } 233 234 235 // Bulk Operations 236 237 /** 238 * {@inheritDoc} 239 * 240 * <p>This implementation iterates over the specified map's 241 * <tt>entrySet()</tt> collection, and calls this map's <tt>put</tt> 242 * operation once for each entry returned by the iteration. 243 * 244 * <p>Note that this implementation throws an 245 * <tt>UnsupportedOperationException</tt> if this map does not support 246 * the <tt>put</tt> operation and the specified map is nonempty. 247 * 248 * @throws UnsupportedOperationException {@inheritDoc} 249 * @throws ClassCastException {@inheritDoc} 250 * @throws NullPointerException {@inheritDoc} 251 * @throws IllegalArgumentException {@inheritDoc} 252 */ 253 public void putAll(Map<? extends K, ? extends V> m) { 254 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) 255 put(e.getKey(), e.getValue()); 256 } 257 258 /** 259 * {@inheritDoc} 260 * 261 * <p>This implementation calls <tt>entrySet().clear()</tt>. 262 * 263 * <p>Note that this implementation throws an 264 * <tt>UnsupportedOperationException</tt> if the <tt>entrySet</tt> 265 * does not support the <tt>clear</tt> operation. 266 * 267 * @throws UnsupportedOperationException {@inheritDoc} 268 */ 269 public void clear() { 270 entrySet().clear(); 271 } 272 273 274 // Views 275 276 /** 277 * Each of these fields are initialized to contain an instance of the 278 * appropriate view the first time this view is requested. The views are 279 * stateless, so there's no reason to create more than one of each. 280 */ 281 transient volatile Set<K> keySet = null; 282 transient volatile Collection<V> values = null; 283 284 /** 285 * {@inheritDoc} 286 * 287 * <p>This implementation returns a set that subclasses {@link AbstractSet}. 288 * The subclass's iterator method returns a "wrapper object" over this 289 * map's <tt>entrySet()</tt> iterator. The <tt>size</tt> method 290 * delegates to this map's <tt>size</tt> method and the 291 * <tt>contains</tt> method delegates to this map's 292 * <tt>containsKey</tt> method. 293 * 294 * <p>The set is created the first time this method is called, 295 * and returned in response to all subsequent calls. No synchronization 296 * is performed, so there is a slight chance that multiple calls to this 297 * method will not all return the same set. 298 */ 299 public Set<K> keySet() { 300 if (keySet == null) { 301 keySet = new AbstractSet<K>() { 302 public Iterator<K> iterator() { 303 return new Iterator<K>() { 304 private Iterator<Entry<K,V>> i = entrySet().iterator(); 305 306 public boolean hasNext() { 307 return i.hasNext(); 308 } 309 310 public K next() { 311 return i.next().getKey(); 312 } 313 314 public void remove() { 315 i.remove(); 316 } 317 }; 318 } 319 320 public int size() { 321 return AbstractMapPro.this.size(); 322 } 323 324 public boolean isEmpty() { 325 return AbstractMapPro.this.isEmpty(); 326 } 327 328 public void clear() { 329 AbstractMapPro.this.clear(); 330 } 331 332 public boolean contains(Object k) { 333 return AbstractMapPro.this.containsKey(k); 334 } 335 }; 336 } 337 return keySet; 338 } 339 340 /** 341 * {@inheritDoc} 342 * 343 * <p>This implementation returns a collection that subclasses {@link 344 * AbstractCollection}. The subclass's iterator method returns a 345 * "wrapper object" over this map's <tt>entrySet()</tt> iterator. 346 * The <tt>size</tt> method delegates to this map's <tt>size</tt> 347 * method and the <tt>contains</tt> method delegates to this map's 348 * <tt>containsValue</tt> method. 349 * 350 * <p>The collection is created the first time this method is called, and 351 * returned in response to all subsequent calls. No synchronization is 352 * performed, so there is a slight chance that multiple calls to this 353 * method will not all return the same collection. 354 */ 355 public Collection<V> values() { 356 if (values == null) { 357 values = new AbstractCollection<V>() { 358 public Iterator<V> iterator() { 359 return new Iterator<V>() { 360 private Iterator<Entry<K,V>> i = entrySet().iterator(); 361 362 public boolean hasNext() { 363 return i.hasNext(); 364 } 365 366 public V next() { 367 return i.next().getValue(); 368 } 369 370 public void remove() { 371 i.remove(); 372 } 373 }; 374 } 375 376 public int size() { 377 return AbstractMapPro.this.size(); 378 } 379 380 public boolean isEmpty() { 381 return AbstractMapPro.this.isEmpty(); 382 } 383 384 public void clear() { 385 AbstractMapPro.this.clear(); 386 } 387 388 public boolean contains(Object v) { 389 return AbstractMapPro.this.containsValue(v); 390 } 391 }; 392 } 393 return values; 394 } 395 396 public abstract Set<Entry<K,V>> entrySet(); 397 398 399 // Comparison and hashing 400 401 /** 402 * Compares the specified object with this map for equality. Returns 403 * <tt>true</tt> if the given object is also a map and the two maps 404 * represent the same mappings. More formally, two maps <tt>m1</tt> and 405 * <tt>m2</tt> represent the same mappings if 406 * <tt>m1.entrySet().equals(m2.entrySet())</tt>. This ensures that the 407 * <tt>equals</tt> method works properly across different implementations 408 * of the <tt>Map</tt> interface. 409 * 410 * <p>This implementation first checks if the specified object is this map; 411 * if so it returns <tt>true</tt>. Then, it checks if the specified 412 * object is a map whose size is identical to the size of this map; if 413 * not, it returns <tt>false</tt>. If so, it iterates over this map's 414 * <tt>entrySet</tt> collection, and checks that the specified map 415 * contains each mapping that this map contains. If the specified map 416 * fails to contain such a mapping, <tt>false</tt> is returned. If the 417 * iteration completes, <tt>true</tt> is returned. 418 * 419 * @param o object to be compared for equality with this map 420 * @return <tt>true</tt> if the specified object is equal to this map 421 */ 422 public boolean equals(Object o) { 423 if (o == this) 424 return true; 425 426 if (!(o instanceof Map)) 427 return false; 428 Map<K,V> m = (Map<K,V>) o; 429 if (m.size() != size()) 430 return false; 431 432 try { 433 Iterator<Entry<K,V>> i = entrySet().iterator(); 434 while (i.hasNext()) { 435 Entry<K,V> e = i.next(); 436 K key = e.getKey(); 437 V value = e.getValue(); 438 if (value == null) { 439 if (!(m.get(key)==null && m.containsKey(key))) 440 return false; 441 } else { 442 if (!value.equals(m.get(key))) 443 return false; 444 } 445 } 446 } catch (ClassCastException unused) { 447 return false; 448 } catch (NullPointerException unused) { 449 return false; 450 } 451 452 return true; 453 } 454 455 // TODO add again current implementation creates a infinti loop when child=this 456 /*public int hashCode() { 457 int h = 0; 458 Iterator<Entry<K,V>> i = entrySet().iterator(); 459 while (i.hasNext()) 460 h += i.next().hashCode(); 461 return h; 462 }*/ 463 464 /** 465 * Returns a string representation of this map. The string representation 466 * consists of a list of key-value mappings in the order returned by the 467 * map's <tt>entrySet</tt> view's iterator, enclosed in braces 468 * (<tt>"{}"</tt>). Adjacent mappings are separated by the characters 469 * <tt>", "</tt> (comma and space). Each key-value mapping is rendered as 470 * the key followed by an equals sign (<tt>"="</tt>) followed by the 471 * associated value. Keys and values are converted to strings as by 472 * {@link String#valueOf(Object)}. 473 * 474 * @return a string representation of this map 475 */ 476 public String toString() { 477 Iterator<Entry<K,V>> i = entrySet().iterator(); 478 if (! i.hasNext()) 479 return "{}"; 480 481 StringBuilder sb = new StringBuilder(); 482 sb.append('{'); 483 for (;;) { 484 Entry<K,V> e = i.next(); 485 K key = e.getKey(); 486 V value = e.getValue(); 487 sb.append(key == this ? "(this Map)" : key); 488 sb.append('='); 489 sb.append(value == this ? "(this Map)" : value); 490 if (! i.hasNext()) 491 return sb.append('}').toString(); 492 sb.append(',').append(' '); 493 } 494 } 495 496 /** 497 * Returns a shallow copy of this <tt>AbstractMap</tt> instance: the keys 498 * and values themselves are not cloned. 499 * 500 * @return a shallow copy of this map 501 */ 502 protected Object clone() throws CloneNotSupportedException { 503 AbstractMapPro<K,V> result = (AbstractMapPro<K,V>)super.clone(); 504 result.keySet = null; 505 result.values = null; 506 return result; 507 } 508 509 /** 510 * Utility method for SimpleEntry and SimpleImmutableEntry. 511 * Test for equality, checking for nulls. 512 */ 513 private static boolean eq(Object o1, Object o2) { 514 return o1 == null ? o2 == null : o1.equals(o2); 515 } 516 517 // Implementation Note: SimpleEntry and SimpleImmutableEntry 518 // are distinct unrelated classes, even though they share 519 // some code. Since you can't add or subtract final-ness 520 // of a field in a subclass, they can't share representations, 521 // and the amount of duplicated code is too small to warrant 522 // exposing a common abstract class. 523 524 525 /** 526 * An Entry maintaining a key and a value. The value may be 527 * changed using the <tt>setValue</tt> method. This class 528 * facilitates the process of building custom map 529 * implementations. For example, it may be convenient to return 530 * arrays of <tt>SimpleEntry</tt> instances in method 531 * <tt>Map.entrySet().toArray</tt>. 532 * 533 * @since 1.6 534 */ 535 public static class SimpleEntry<K,V> 536 implements Entry<K,V>, java.io.Serializable 537 { 538 private static final long serialVersionUID = -8499721149061103585L; 539 540 private final K key; 541 private V value; 542 543 /** 544 * Creates an entry representing a mapping from the specified 545 * key to the specified value. 546 * 547 * @param key the key represented by this entry 548 * @param value the value represented by this entry 549 */ 550 public SimpleEntry(K key, V value) { 551 this.key = key; 552 this.value = value; 553 } 554 555 /** 556 * Creates an entry representing the same mapping as the 557 * specified entry. 558 * 559 * @param entry the entry to copy 560 */ 561 public SimpleEntry(Entry<? extends K, ? extends V> entry) { 562 this.key = entry.getKey(); 563 this.value = entry.getValue(); 564 } 565 566 /** 567 * Returns the key corresponding to this entry. 568 * 569 * @return the key corresponding to this entry 570 */ 571 public K getKey() { 572 return key; 573 } 574 575 /** 576 * Returns the value corresponding to this entry. 577 * 578 * @return the value corresponding to this entry 579 */ 580 public V getValue() { 581 return value; 582 } 583 584 /** 585 * Replaces the value corresponding to this entry with the specified 586 * value. 587 * 588 * @param value new value to be stored in this entry 589 * @return the old value corresponding to the entry 590 */ 591 public V setValue(V value) { 592 V oldValue = this.value; 593 this.value = value; 594 return oldValue; 595 } 596 597 @Override 598 public boolean equals(Object o) { 599 if (!(o instanceof Map.Entry)) 600 return false; 601 Map.Entry e = (Map.Entry)o; 602 return eq(key, e.getKey()) && eq(value, e.getValue()); 603 } 604 605 @Override 606 public int hashCode() { 607 return (key == null ? 0 : key.hashCode()) ^ 608 (value == null ? 0 : value.hashCode()); 609 } 610 611 /** 612 * Returns a String representation of this map entry. This 613 * implementation returns the string representation of this 614 * entry's key followed by the equals character ("<tt>=</tt>") 615 * followed by the string representation of this entry's value. 616 * 617 * @return a String representation of this map entry 618 */ 619 public String toString() { 620 return key + "=" + value; 621 } 622 623 } 624 625 /** 626 * An Entry maintaining an immutable key and value. This class 627 * does not support method <tt>setValue</tt>. This class may be 628 * convenient in methods that return thread-safe snapshots of 629 * key-value mappings. 630 * 631 * @since 1.6 632 */ 633 public static class SimpleImmutableEntry<K,V> 634 implements Entry<K,V>, java.io.Serializable 635 { 636 private static final long serialVersionUID = 7138329143949025153L; 637 638 private final K key; 639 private final V value; 640 641 /** 642 * Creates an entry representing a mapping from the specified 643 * key to the specified value. 644 * 645 * @param key the key represented by this entry 646 * @param value the value represented by this entry 647 */ 648 public SimpleImmutableEntry(K key, V value) { 649 this.key = key; 650 this.value = value; 651 } 652 653 /** 654 * Creates an entry representing the same mapping as the 655 * specified entry. 656 * 657 * @param entry the entry to copy 658 */ 659 public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) { 660 this.key = entry.getKey(); 661 this.value = entry.getValue(); 662 } 663 664 /** 665 * Returns the key corresponding to this entry. 666 * 667 * @return the key corresponding to this entry 668 */ 669 public K getKey() { 670 return key; 671 } 672 673 /** 674 * Returns the value corresponding to this entry. 675 * 676 * @return the value corresponding to this entry 677 */ 678 public V getValue() { 679 return value; 680 } 681 682 /** 683 * Replaces the value corresponding to this entry with the specified 684 * value (optional operation). This implementation simply throws 685 * <tt>UnsupportedOperationException</tt>, as this class implements 686 * an <i>immutable</i> map entry. 687 * 688 * @param value new value to be stored in this entry 689 * @return (Does not return) 690 * @throws UnsupportedOperationException always 691 */ 692 public V setValue(V value) { 693 throw new UnsupportedOperationException(); 694 } 695 696 @Override 697 public boolean equals(Object o) { 698 if (!(o instanceof Map.Entry)) 699 return false; 700 Map.Entry e = (Map.Entry)o; 701 return eq(key, e.getKey()) && eq(value, e.getValue()); 702 } 703 704 @Override 705 public int hashCode() { 706 return (key == null ? 0 : key.hashCode()) ^ 707 (value == null ? 0 : value.hashCode()); 708 } 709 710 /** 711 * Returns a String representation of this map entry. This 712 * implementation returns the string representation of this 713 * entry's key followed by the equals character ("<tt>=</tt>") 714 * followed by the string representation of this entry's value. 715 * 716 * @return a String representation of this map entry 717 */ 718 public String toString() { 719 return key + "=" + value; 720 } 721 722 } 723 724 public static PageException invalidKey(Map map,Object key, boolean remove) { 725 726 StringBuilder sb=new StringBuilder(); 727 Iterator it = map.keySet().iterator(); 728 Object k; 729 while(it.hasNext()){ 730 k = it.next(); 731 if(sb.length()>0)sb.append(','); 732 sb.append(k.toString()); 733 } 734 return new ExpressionException( 735 (remove? 736 "cannot remove key ["+key+"] from struct, key doesn't exist": 737 "key [" + key + "] doesn't exist") + 738 " (existing keys:" + sb.toString() + ")" ); 739 } 740 741 }