001/* 002 * Copyright (c) 1994, 2023, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025package Torello.Java.ReadOnly; 026 027import java.util.*; 028 029import java.util.function.BiConsumer; 030import java.util.function.Function; 031import java.util.function.BiFunction; 032 033/** 034 * A Copy of Java's {@code Hashtable} class; used for building a {@link ReadOnlyHashtable}. 035 * Maintains <I>an internal and inaccessible {@code Hashtable<K, V>} instance</I>. 036 * 037 * <EMBED CLASS=globalDefs DATA-JDK=Hashtable> 038 * <EMBED CLASS='external-html' DATA-A_AN=a DATA-FILE-ID=BUILDERS> 039 * <EMBED CLASS='external-html' DATA-FILE-ID=SYNCHRONIZED> 040 * 041 * @param <K> the type of keys maintained by this map 042 * @param <V> the type of mapped values 043 * @see ReadOnlyHashtable 044 */ 045@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="JDHBI_BUILDER") 046public final class ROHashtableBuilder<K, V> 047 extends Hashtable<K, V> 048 implements Cloneable, java.io.Serializable 049{ 050 // ******************************************************************************************** 051 // ******************************************************************************************** 052 // Fields & Builder Stuff 053 // ******************************************************************************************** 054 // ******************************************************************************************** 055 056 057 /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */ 058 protected static final long serialVersionUID = 1; 059 060 // Exception messages need this 061 private static final String ROHT = "Hashtable"; 062 063 private boolean built = false; 064 065 // Mimics the C++ Concept of "Friend Class". This badge is utilized by the "build()" method 066 // directly below this inner-class declaration. It is the only place where this declaration is 067 // actually used anywhere. This prohibits access to the constructor that is used to anybody 068 // else 069 070 static class AccessBadge { private AccessBadge() { } } 071 private static final AccessBadge friendClassBadge = new AccessBadge(); 072 073 /** 074 * Simply transfers {@code 'this'} instance' internal {@code Hashtable} to the 075 * {@code ReadOnlyHashtable} Wrapper-Class. 076 * 077 * @return a newly constructed {@code ReadOnlyHashtable} "Wrapper-Class", shielding the 078 * internal {@code 'hashTable'} private-field from any modification. 079 */ 080 public synchronized ReadOnlyHashtable<K, V> build() 081 { 082 this.built = true; 083 084 return (size() == 0) 085 ? ReadOnlyHashtable.emptyROHT() 086 : new ReadOnlyHashtable<>(this, friendClassBadge); 087 } 088 089 090 // ******************************************************************************************** 091 // ******************************************************************************************** 092 // Modified Constructors 093 // ******************************************************************************************** 094 // ******************************************************************************************** 095 096 097 /** 098 * Constructs a new, empty {@code ROHashtableBuilder} with the specified initial capacity and 099 * the specified load factor. 100 * 101 * @param initialCapacity the initial capacity of the hashtable. 102 * @param loadFactor the load factor of the hashtable. 103 * 104 * @throws IllegalArgumentException if the initial capacity is les than zero, or if the load 105 * factor is nonpositive. 106 */ 107 public ROHashtableBuilder(int initialCapacity, float loadFactor) 108 { super(initialCapacity, loadFactor); } 109 110 /** 111 * Constructs a new, empty {@code ROHashtableBuilder} with the specified initial capacity and 112 * default load factor (0.75). 113 * 114 * @param initialCapacity the initial capacity of the hashtable. 115 * @throws IllegalArgumentException if the initial capacity is less than zero. 116 */ 117 public ROHashtableBuilder(int initialCapacity) 118 { super(initialCapacity); } 119 120 /** 121 * Constructs a new, empty {@code ROHashtableBuilder} with a default initial capacity (11) and 122 * load factor (0.75). 123 */ 124 public ROHashtableBuilder() 125 { this(11, 0.75f); } 126 127 /** 128 * Constructs a new {@code ROHashtableBuilder} with the same mappings as the given Map. The 129 * builder is created with an initial capacity sufficient to hold the mappings in the given Map 130 * and a default load factor (0.75). 131 * 132 * @param t the map whose mappings are to be placed in this map. 133 * @throws NullPointerException if the specified map is null. 134 */ 135 public ROHashtableBuilder(Map<? extends K, ? extends V> t) 136 { 137 // Note that below is the actual Hashtable Constructor Code. It **DOES NOT** break the 138 // "Read Only Contract" - Specifically, subsequent changes to 't' **WILL NOT** write 139 // through into this internal hashTable instance. They are copied / cloned "into" this map 140 141 super(t); 142 } 143 144 145 // ******************************************************************************************** 146 // ******************************************************************************************** 147 // Original JDK Methods 148 // ******************************************************************************************** 149 // ******************************************************************************************** 150 151 152 153 /** 154 * Maps the specified {@code key} to the specified {@code value} in this hashtable. Neither the 155 * key nor the value can be {@code null}. 156 * 157 * <BR /><BR />The value can be retrieved by calling the {@code get} method with a key that is 158 * equal to the original key. 159 * 160 * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR> 161 * 162 * @param key the hashtable key 163 * @param value the value 164 * 165 * @return the previous value of the specified key in this hashtable, or {@code null} if it did 166 * not have one 167 * 168 * @throws NullPointerException if the key or value is {@code null} 169 * @see #get(Object) 170 */ 171 public synchronized V put(K key, V value) 172 { 173 if (this.built) throw new AttemptedModificationException(ROHT); 174 return super.put(key, value); 175 } 176 177 /** 178 * Removes the key (and its corresponding value) from this hashtable. This method does nothing 179 * if the key is not in the hashtable. 180 * 181 * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR> 182 * 183 * @param key the key that needs to be removed 184 * 185 * @return the value to which the key had been mapped in this hashtable, or {@code null} if the 186 * key did not have a mapping 187 * 188 * @throws NullPointerException if the key is {@code null} 189 */ 190 public synchronized V remove(Object key) 191 { 192 if (this.built) throw new AttemptedModificationException(ROHT); 193 return super.remove(key); 194 } 195 196 /** 197 * Copies all of the mappings from the specified map to this hashtable. These mappings will 198 * replace any mappings that this hashtable had for any of the keys currently in the specified 199 * map. 200 * 201 * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR> 202 * 203 * @param t mappings to be stored in this map 204 * @throws NullPointerException if the specified map is null 205 */ 206 public synchronized void putAll(Map<? extends K, ? extends V> t) 207 { 208 if (this.built) throw new AttemptedModificationException(ROHT); 209 super.putAll(t); 210 } 211 212 /** 213 * Clears this hashtable so that it contains no keys. 214 * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR> 215 */ 216 public synchronized void clear() 217 { 218 if (this.built) throw new AttemptedModificationException(ROHT); 219 super.clear(); 220 } 221 222 223 // ******************************************************************************************** 224 // ******************************************************************************************** 225 // Comparison and hashing 226 // ******************************************************************************************** 227 // ******************************************************************************************** 228 229 230 231 /** 232 * Replaces each entry's value with the result of invoking the given function on that entry 233 * until all entries have been processed or the function throws an exception. Exceptions thrown 234 * by the function are relayed to the caller. 235 * 236 * The default implementation is equivalent to, for this map: 237 * 238 * <BR /><DIV CLASS=SNIP>{@code 239 * for (Map.Entry<K, V> entry : map.entrySet()) 240 * entry.setValue(function.apply(entry.getKey(), entry.getValue())); 241 * }</DIV> 242 * 243 * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR> 244 * 245 * @param function the function to apply to each entry 246 * 247 * @throws ClassCastException if the class of a replacement value prevents it from being stored 248 * in this map 249 * 250 * @throws NullPointerException if the specified function or the replacement value is null 251 * @throws ConcurrentModificationException - if an entry is found to be removed during iteration 252 */ 253 public synchronized void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) 254 { 255 if (this.built) throw new AttemptedModificationException(ROHT); 256 super.replaceAll(function); 257 } 258 259 /** 260 * If the specified key is not already associated with a value (or is mapped to null) 261 * associates it with the given value and returns null, else returns the current value. 262 * 263 * <BR /><BR />The default implementation is equivalent to, for this map: 264 * 265 * <BR /><DIV CLASS=SNIP>{@code 266 * V v = map.get(key); 267 * if (v == null) v = map.put(key, value); 268 * return v; 269 * }</DIV> 270 * 271 * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR> 272 * 273 * @param key key with which the specified value is to be associated 274 * @param value value to be associated with the specified key 275 * 276 * @return the previous value associated with the specified key, or null if there was no 277 * mapping for the key. (A null return can also indicate that the map previously associated 278 * null with the key, if the implementation supports null values.) 279 * 280 * @throws ClassCastException if the key or value is of an inappropriate type for this map 281 * (optional) 282 * 283 * @throws NullPointerException if the specified key or value is null 284 */ 285 public synchronized V putIfAbsent(K key, V value) 286 { 287 if (this.built) throw new AttemptedModificationException(ROHT); 288 return super.putIfAbsent(key, value); 289 } 290 291 /** 292 * Replaces the entry for the specified key only if currently mapped to the specified value. 293 * 294 * <BR /><BR />The default implementation is equivalent to, for this map: 295 * 296 * <BR /><DIV CLASS=SNIP>{@code 297 * if (map.containsKey(key) && Objects.equals(map.get(key), oldValue)) 298 * { 299 * map.put(key, newValue); 300 * return true; 301 * } 302 * 303 * else return false; 304 * }</DIV> 305 * 306 * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR> 307 * 308 * @param key key with which the specified value is associated 309 * @param value value expected to be associated with the specified key 310 * @return {@code TRUE} if the value was replaced 311 * 312 * @throws ClassCastException if the class of a specified key or value prevents it from being 313 * stored in this map 314 * 315 * @throws NullPointerException if a specified key or newValue is null, and this map does not 316 * permit null keys or values 317 */ 318 public synchronized boolean remove(Object key, Object value) 319 { 320 if (this.built) throw new AttemptedModificationException(ROHT); 321 return super.remove(key, value); 322 } 323 324 /** 325 * Replaces the entry for the specified key only if currently mapped to the specified value. 326 * 327 * <BR /><BR />The default implementation is equivalent to, for this map: 328 * 329 * <BR /><DIV CLASS=SNIP>{@code 330 * if (map.containsKey(key) && Objects.equals(map.get(key), oldValue)) 331 * { 332 * map.put(key, newValue); 333 * return true; 334 * } 335 * 336 * else return false; 337 * }</DIV> 338 * 339 * <BR /><BR />The default implementation does not throw NullPointerException for maps that do 340 * not support null values if oldValue is null unless newValue is also null. 341 * 342 * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR> 343 * 344 * @param key key with which the specified value is associated 345 * @param oldValue value expected to be associated with the specified key 346 * @param newValue value to be associated with the specified key 347 * @return {@code TRUE} if the value was replaced 348 * 349 * @throws ClassCastException if the class of a specified key or value prevents it from being 350 * stored in this map 351 * 352 * @throws NullPointerException if a specified key or oldValue / newValue is null 353 */ 354 public synchronized boolean replace(K key, V oldValue, V newValue) 355 { 356 if (this.built) throw new AttemptedModificationException(ROHT); 357 return super.replace(key, oldValue, newValue); 358 } 359 360 /** 361 * Replaces the entry for the specified key only if it is currently mapped to some value. 362 * 363 * <BR /><BR />The default implementation is equivalent to, for this map: 364 * 365 * <BR /><DIV CLASS=SNIP>{@code 366 * if (map.containsKey(key)) return map.put(key, value); 367 * else return null; 368 * }</DIV> 369 * 370 * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR> 371 * 372 * @param key key with which the specified value is associated 373 * @param value value to be associated with the specified key 374 * 375 * @return the previous value associated with the specified key, or null if there was no 376 * mapping for the key. (A null return can also indicate that the map previously associated 377 * null with the key, if the implementation supports null values.) 378 * 379 * @throws ClassCastException if the class of the specified key or value prevents it from being 380 * stored in this map 381 * 382 * @throws NullPointerException if the specified key or value is null 383 */ 384 public synchronized V replace(K key, V value) 385 { 386 if (this.built) throw new AttemptedModificationException(ROHT); 387 return super.replace(key, value); 388 } 389 390 /** 391 * If the specified key is not already associated with a value (or is mapped to null), attempts 392 * to compute its value using the given mapping function and enters it into this map unless 393 * null. 394 * 395 * <BR /><BR />If the mapping function returns null, no mapping is recorded. If the mapping 396 * function itself throws an (unchecked) exception, the exception is rethrown, and no mapping 397 * is recorded. The most common usage is to construct a new object serving as an initial mapped 398 * value or memoized result, as in: 399 * 400 * <BR /><DIV CLASS=SNIP>{@code 401 * map.computeIfAbsent(key, k -> new Value(f(k))); 402 * }</DIV> 403 * 404 * <BR />Or to implement a multi-value map, {@code Map<K,Collection<V>>}, supporting multiple 405 * values per key: 406 * 407 * <BR /><DIV CLASS=SNIP>{@code 408 * map.computeIfAbsent(key, k -> new HashSet<V>()).add(v); 409 * }</DIV> 410 * 411 * <BR /><BR />The mapping function should not modify this map during computation. 412 * 413 * <BR /><BR />This method will, on a best-effort basis, throw a 414 * {@link java.util.ConcurrentModificationException} if the mapping function modified this map 415 * during computation. 416 * 417 * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR> 418 * 419 * @param key key with which the specified value is to be associated 420 * @param mappingFunction the mapping function to compute a value 421 * 422 * @return the current (existing or computed) value associated with the specified key, 423 * or null if the computed value is null 424 * 425 * @throws ConcurrentModificationException if it is detected that the 426 * mapping function modified this map 427 */ 428 public synchronized V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) 429 { 430 if (this.built) throw new AttemptedModificationException(ROHT); 431 return computeIfAbsent(key, mappingFunction); 432 } 433 434 /** 435 * If the value for the specified key is present and non-null, attempts to compute a new 436 * mapping given the key and its current mapped value. If the remapping function returns null, 437 * the mapping is removed. 438 * 439 * <BR /><BR />If the remapping function itself throws an (unchecked) exception, the exception 440 * is rethrown, and the current mapping is left unchanged. 441 * 442 * <BR /><BR />The remapping function should not modify this map during computation. 443 * 444 * <BR /><BR />This method will, on a best-effort basis, throw a 445 * {@link java.util.ConcurrentModificationException} if the remapping function modified this 446 * map during computation. 447 * 448 * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR> 449 * 450 * @param key key with which the specified value is to be associated 451 * @param remappingFunction the remapping function to compute a value 452 * @return the new value associated with the specified key, or null if none 453 * 454 * @throws ConcurrentModificationException if it is detected that the remapping function 455 * modified this map 456 */ 457 public synchronized V computeIfPresent 458 (K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) 459 { 460 if (this.built) throw new AttemptedModificationException(ROHT); 461 return super.computeIfPresent(key, remappingFunction); 462 } 463 464 /** 465 * Attempts to compute a mapping for the specified key and its current mapped value (or null if 466 * there is no current mapping). For example, to either create or append a String msg to a 467 * value mapping: 468 * 469 * <BR /><DIV CLASS=SNIP>{@code 470 * map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg)) 471 * }</DIV> 472 * 473 * <BR /><BR />(Method merge() is often simpler to use for such purposes.) 474 * 475 * <BR /><BR />If the remapping function returns null, the mapping is removed (or remains 476 * absent if initially absent). If the remapping function itself throws an (unchecked) 477 * exception, the exception is rethrown, and the current mapping is left unchanged. 478 * 479 * <BR /><BR />The remapping function should not modify this map during computation. 480 * 481 * <BR /><BR />This method will, on a best-effort basis, throw a 482 * {@code ConcurrentModificationException} if the remapping function modified this map during 483 * computation. 484 * 485 * <BR /><BR />This method will, on a best-effort basis, throw a 486 * {@link java.util.ConcurrentModificationException} if the remapping function modified this 487 * map during computation. 488 * 489 * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR> 490 * 491 * @param key key with which the specified value is to be associated 492 * @param remappingFunction the remapping function to compute a value 493 * @return the new value associated with the specified key, or null if none 494 * 495 * @throws ConcurrentModificationException if it is detected that the remapping function 496 * modified this map 497 */ 498 public synchronized V compute 499 (K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) 500 { 501 if (this.built) throw new AttemptedModificationException(ROHT); 502 return super.compute(key, remappingFunction); 503 } 504 505 /** 506 * If the specified key is not already associated with a value or is associated with null, 507 * associates it with the given non-null value. Otherwise, replaces the associated value with 508 * the results of the given remapping function, or removes if the result is null. This method 509 * may be of use when combining multiple mapped values for a key. For example, to either create 510 * or append a String msg to a value mapping: 511 * 512 * <BR /><DIV CLASS=SNIP>{@code 513 * map.merge(key, msg, String::concat) 514 * }</DIV> 515 * 516 * <BR /><BR />This method will, on a best-effort basis, throw a 517 * {@code java.util.ConcurrentModificationException} if the remapping function modified this 518 * map during computation. 519 * 520 * <EMBED DATA-TYPE=Hashtable CLASS='external-html' DATA-FILE-ID=MUTATOR> 521 * 522 * @param key key with which the resulting value is to be associated 523 * 524 * @param value the non-null value to be merged with the existing value associated with the key 525 * or, if no existing value or a null value is associated with the key, to be associated with 526 * the key 527 * 528 * @param remappingFunction the remapping function to recompute a value if present 529 * 530 * @return the new value associated with the specified key, or null if no value is associated 531 * with the key 532 * 533 * @throws ConcurrentModificationException if it is detected that the remapping function 534 * modified this map 535 */ 536 public synchronized V merge 537 (K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) 538 { 539 if (this.built) throw new AttemptedModificationException(ROHT); 540 return super.merge(key, value, remappingFunction); 541 } 542 543 544 // ******************************************************************************************** 545 // ******************************************************************************************** 546 // Methods inherited from class java.util.Hashtable 547 // ******************************************************************************************** 548 // ******************************************************************************************** 549 550 551 // contains, containsKey, containsValue, elements, entrySet, forEach, get, getOrDefault, 552 // hashCode, isEmpty, keys, keySet, rehash, size, toString, values 553 // 554 // Introspection Only: contains, containsKey, containsValue, elements, forEach, get, 555 // getOrDefault, hashCode, isEmpty, keys, size, toString 556 // 557 // Potential Mutator: entrySet, keySet, values 558 559 /** 560 * Restricts a back-door into the underlying data-structure. 561 * <EMBED CLASS='external-html'DATA-RET_TYPE=Set DATA-FILE-ID=UNMODIFIABLE> 562 * @return a {@code java.util.Set} that cannot modify this {@code Hashtable-Builder} 563 */ 564 public Set<Map.Entry<K, V>> entrySet() 565 { return Collections.unmodifiableSet(super.entrySet()); } 566 567 Set<Map.Entry<K, V>> _entrySet(ReadOnlyHashtable.AccessBadge friendClassBadge) 568 { return super.entrySet(); } 569 570 /** 571 * Restricts a back-door into the underlying data-structure. 572 * <EMBED CLASS='external-html'DATA-RET_TYPE=Set DATA-FILE-ID=UNMODIFIABLE> 573 * @return a {@code java.util.Set} that cannot modify this {@code Hashtable-Builder} 574 */ 575 public Set<K> keySet() 576 { return Collections.unmodifiableSet(super.keySet()); } 577 578 Set<K> _keySet(ReadOnlyHashtable.AccessBadge friendClassBadge) 579 { return super.keySet(); } 580 581 /** 582 * Restricts a back-door into the underlying data-structure. 583 * <EMBED CLASS='external-html'DATA-RET_TYPE=Collection DATA-FILE-ID=UNMODIFIABLE> 584 * @return a {@code java.util.Collection} that cannot modify this {@code Hashtable-Builder} 585 */ 586 public Collection<V> values() 587 { return Collections.unmodifiableCollection(super.values()); } 588 589 Collection<V> _values(ReadOnlyHashtable.AccessBadge friendClassBadge) 590 { return super.values(); } 591 592 593 // ******************************************************************************************** 594 // ******************************************************************************************** 595 // java.lang.Object & Cloneable 596 // ******************************************************************************************** 597 // ******************************************************************************************** 598 599 600 /** 601 * Compares the specified Object with this Builder for equality, as per the definition in the 602 * private and internal field {@code 'hashTable'}. 603 * 604 * @param o object to be compared for equality with this {@code ROHashtableBuilder} instance 605 * @return true if the specified Object is equal to this Builder 606 */ 607 public synchronized boolean equals(Object o) 608 { 609 if (this == o) return true; 610 if (! (o instanceof ROHashtableBuilder)) return false; 611 return super.equals((Hashtable) o); 612 } 613 614 /** 615 * Clones this instance' of {@code ROHashtableBuilder}. 616 * 617 * <BR /><BR />The clone that's returned has had it's internal {@code 'built'} boolean-flag set 618 * {@code FALSE} 619 * 620 * @return a clone of this builder 621 */ 622 public synchronized ROHashtableBuilder<K, V> clone() 623 { return new ROHashtableBuilder<>(this); } 624 625 private ROHashtableBuilder(ROHashtableBuilder<K, V> rohtb) 626 { super(rohtb); this.built=false; } 627}