001/* 002 * Copyright (c) 1995, 2021, 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.nio.charset.Charset; 028import java.util.function.BiConsumer; 029import java.util.function.BiFunction; 030 031import Torello.Java.Additional.Ret2; 032import Torello.Java.Additional.Tuple2; 033 034import java.io.*; 035import java.util.*; 036 037/** 038 * Immutable Wrapper for <CODE>java.util.Properties</CODE>, found in the "Java Collections 039 * Framework". 040 * 041 * <EMBED CLASS=globalDefs DATA-JDK=Properties> 042 * <EMBED CLASS='external-html' DATA-FILE-ID=DATA_CLASS> 043 */ 044@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="JDHBI_MAIN") 045public class ReadOnlyProperties extends ReadOnlyHashtable<Object, Object> 046{ 047 // ******************************************************************************************** 048 // ******************************************************************************************** 049 // Protected & Private Fields, Methods, Statics 050 // ******************************************************************************************** 051 // ******************************************************************************************** 052 053 054 /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */ 055 protected static final long serialVersionUID = 1; 056 057 // Minor Optimization where new Properties's that have no contents always re-use this static 058 // instance. 059 060 private static final Properties EMPTY_PROPERTIES = new Properties(0); 061 062 // Singleton & Empty ReadOnlyProperties, Uses the "Supplier Constructor" 063 private static final ReadOnlyProperties EMPTY_READONLY_PROPERTIES = 064 new ReadOnlyProperties(EMPTY_PROPERTIES); 065 066 // The actual Properties used by this instance. 067 private final Properties properties; 068 069 // TRUE => This was built using the class ROProperties 070 // FALSE => This was built using the clone() of a standard java.util.Properties constructor 071 072 private final boolean fromBuilderOrProperties; 073 074 // Mimics the C++ Keyword/Concept of "Friend Class". Is "Friends With" ROPropertiesBuilder 075 static class AccessBadge { private AccessBadge() { } } 076 private static final AccessBadge friendClassBadge = new AccessBadge(); 077 078 079 // ******************************************************************************************** 080 // ******************************************************************************************** 081 // Builder, Acess-Badge Constructor - and Static "Empty" getter (which is used by the builder) 082 // ******************************************************************************************** 083 // ******************************************************************************************** 084 085 086 public static ReadOnlyProperties emptyROP() 087 { return EMPTY_READONLY_PROPERTIES; } 088 089 // To all the readers out there following along: The "AccessBadge" thing is just a slightly 090 // wimpier substitute for the C++ keyword / concept 'friend' or "Friend Class". It means this 091 // constructor is (for all intents and purposes) a private-constructor, except for the class 092 // ROPropertiesBuilder 093 // 094 // This is the Constructor used by the Builder. It has a "Zero-Size" Optimization 095 096 ReadOnlyProperties(ROPropertiesBuilder ropb, ROPropertiesBuilder.AccessBadge badge) 097 { 098 // This is a little bizarre, but necessary. Properties extends Hashtable 099 super(friendClassBadge); 100 101 Objects.requireNonNull(badge, "Access Badge is null. Requires Friend-Class Badge"); 102 103 this.fromBuilderOrProperties = true; 104 this.properties = ropb; 105 } 106 107 108 // ******************************************************************************************** 109 // ******************************************************************************************** 110 // Constructors 111 // ******************************************************************************************** 112 // ******************************************************************************************** 113 114 115 /** 116 * Clones parameter {@code 'properties'} (and saves it) in order to guarantee that 117 * {@code 'this'} instance is Read-Only and furthermore shielded from outside modification. 118 * 119 * @param properties The {@code Properties} to be cloned and saved into this instance internal 120 * and private {@code 'properties'} field. 121 */ 122 @SuppressWarnings("unchecked") 123 public ReadOnlyProperties(Properties properties) 124 { 125 // This is a little bizarre, but necessary. Properties extends Hashtable 126 super(friendClassBadge); 127 this.fromBuilderOrProperties = false; 128 129 // TO-DO: FIX-ME, CLONE IS NOT ACCEPTABLE IN READ-ONLY !!! IT IS A BACK-DOOR !!! 130 this.properties = (Properties) properties.clone(); 131 } 132 133 /** 134 * If only a small amount of processing needs to be done on the contents of some Java 135 * Map, and using an entire Builder-Class seems disproportionately complex - <I>this 136 * constructor can convert any Java {@code Map} into a {@code ReadOnlyProperties}, using 137 * a simple {@code 'mapTranslator'}</I>. 138 * 139 * @param <A> The Key-Type of the User-Provided {@code Map}. 140 * @param <B> The Value-Type of the User-Provided {@code Map}. 141 * 142 * @param map Any Java {@code Map}. 143 * 144 * @param mapTranslator A function for mapping the iterated elements of Map-Types {@code 'A'} 145 * and {@code 'B'}, into the actual {@code Properties's} Key and Value Type {@code 'String'}. 146 * 147 * <BR /><BR />If this parameter is passed null, this method will throw a 148 * {@code NullPointerException}. 149 * 150 * @throws NullPointerException if either parameter {@code 'i'} or parameter 151 * {@code 'mapTranslator'} is passed null. 152 */ 153 public <A, B> ReadOnlyProperties 154 (Map<A, B> map, BiFunction<A, B, Ret2<String, String>> mapTranslator) 155 { 156 super(friendClassBadge); 157 158 Objects.requireNonNull(mapTranslator, "You have passed null to parameter 'mapTranslator'"); 159 160 fromBuilderOrProperties = false; 161 162 Properties properties = new Properties(map.size()); 163 164 for (Map.Entry<A, B> entry : map.entrySet()) 165 { 166 Ret2<String, String> ret2 = mapTranslator.apply(entry.getKey(), entry.getValue()); 167 properties.put(ret2.a, ret2.b); 168 } 169 170 // Empty Optimization (throw away, completely, the reference, use static-constant) 171 this.properties = (properties.size() == 0) ? EMPTY_PROPERTIES : properties; 172 } 173 174 175 // ******************************************************************************************** 176 // ******************************************************************************************** 177 // More Constructors, March 2024 178 // ******************************************************************************************** 179 // ******************************************************************************************** 180 181 182 /** 183 * Reads a property list (key and element pairs) from the input byte stream. The input stream 184 * is in a simple line-oriented format as specified in {@code load(Reader)} and is assumed to 185 * use the ISO 8859-1 character encoding; that is each byte is one Latin1 character. Characters 186 * not in Latin1, and certain special characters, are represented in keys and elements using 187 * Unicode escapes as defined in section {@code 3.3} of <cite>The Java Language 188 * Specification</cite>. 189 * 190 * <BR /><BR />The specified stream remains open after this method returns. 191 * 192 * <BR /><BR /><SPAN CLASS=CopiedJDK>This Detail-Comment was copied from class: 193 * {@code java.util.Properties}, <B>JDK 21</B> 194 * 195 * <BR /><BR /><B>Method:</B> Properties.load(InputStream.inStream) 196 * </SPAN> 197 * 198 * @param inStream the input stream. 199 * @param sizeIfKnown <EMBED CLASS='external-html' DATA-FILE-ID=PROP_ICAPACITY> 200 * @throws IOException if an error occurred when reading from the input stream. 201 * 202 * @throws IllegalArgumentException if the input stream contains a malformed Unicode escape 203 * sequence. 204 * 205 * @throws NullPointerException if {@code inStream} is null. 206 */ 207 public ReadOnlyProperties(InputStream inStream, Integer sizeIfKnown) throws IOException 208 { 209 super(friendClassBadge); 210 211 this.fromBuilderOrProperties = false; 212 213 this.properties = (sizeIfKnown != null) ? new Properties(sizeIfKnown) : new Properties(); 214 215 this.properties.load(inStream); 216 } 217 218 /** 219 * Reads a property list (key and element pairs) from the input character stream in a simple 220 * line-oriented format. 221 * 222 * <EMBED CLASS='external-html' DATA-FILE-ID=LOAD_DESC_READER> 223 * 224 * <BR /><BR /><SPAN CLASS=CopiedJDK>This Detail-Comment was copied from class: 225 * {@code java.util.Properties}, <B>JDK 21</B> 226 * 227 * <BR /><BR /><B>Method:</B> Properties.load(InputStream.inStream) 228 * </SPAN> 229 * 230 * @param reader the input character stream. 231 * @param sizeIfKnown <EMBED CLASS='external-html' DATA-FILE-ID=PROP_ICAPACITY> 232 * @throws IOException if an error occurred when reading from the input stream. 233 * 234 * @throws IllegalArgumentException if a malformed Unicode escape appears in the input. 235 * @throws NullPointerException if {@code reader} is null. 236 */ 237 public ReadOnlyProperties(Reader reader, Integer sizeIfKnown) throws IOException 238 { 239 super(friendClassBadge); 240 241 this.fromBuilderOrProperties = false; 242 243 this.properties = (sizeIfKnown != null) ? new Properties(sizeIfKnown) : new Properties(); 244 245 this.properties.load(reader); 246 } 247 248 /** 249 * Loads all of the properties represented by the XML document on the specified input stream 250 * into this properties table. 251 * 252 * <EMBED CLASS='external-html' DATA-FILE-ID=LOADXML_DESC_INPUT_STRM> 253 * 254 * <BR /><BR /><SPAN CLASS=CopiedJDK>This Detail-Comment was copied from class: 255 * {@code java.util.Properties}, <B>JDK 21</B> 256 * 257 * <BR /><BR /><B>Method:</B> Properties.load(InputStream.inStream) 258 * </SPAN> 259 * 260 * @param sizeIfKnown <EMBED CLASS='external-html' DATA-FILE-ID=PROP_ICAPACITY> 261 * @param in the input stream from which to read the XML document. 262 * 263 * @throws IOException if reading from the specified input stream results in an 264 * {@code IOException}. 265 * 266 * @throws java.io.UnsupportedEncodingException if the document's encoding declaration can be 267 * read and it specifies an encoding that is not supported 268 * 269 * @throws InvalidPropertiesFormatException Data on input stream does not constitute a valid 270 * XML document with the mandated document type. 271 * 272 * @throws NullPointerException if {@code in} is null. 273 * 274 * @spec https://www.w3.org/TR/xml Extensible Markup Language (XML) 1.0 (Fifth Edition) 275 * @see #storeToXML(OutputStream, String, String) 276 * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character Encoding in Entities</a> 277 */ 278 public ReadOnlyProperties(Integer sizeIfKnown, InputStream in) 279 throws IOException, UnsupportedEncodingException, InvalidPropertiesFormatException 280 { 281 super(friendClassBadge); 282 283 this.fromBuilderOrProperties = false; 284 285 this.properties = (sizeIfKnown != null) ? new Properties(sizeIfKnown) : new Properties(); 286 287 this.properties.loadFromXML(in); 288 } 289 290 291 // ******************************************************************************************** 292 // ******************************************************************************************** 293 // One More Constructor, for good luck - March 2024 294 // ******************************************************************************************** 295 // ******************************************************************************************** 296 297 298 /** 299 * Builds a {@code Properties} instance using the {@code 'refHolder'} & 300 * {@code 'computeNextEntry'} combination. 301 * 302 * @param refHolder <EMBED CLASS='external-html' DATA-FILE-ID=REF_HOLDER> 303 * @param computeNextEntry <EMBED CLASS='external-html' DATA-FILE-ID=COMPUTE_NEXT_RUN> 304 * @param sizeIfKnown <EMBED CLASS='external-html' DATA-FILE-ID=PROP_ICAPACITY> 305 */ 306 public ReadOnlyProperties( 307 Tuple2<Object, Object> refHolder, 308 Runnable computeNextEntry, 309 Integer sizeIfKnown 310 ) 311 { 312 super(friendClassBadge); 313 314 Objects.requireNonNull(refHolder, ROHelpers.NULL_MSG + "'refHolder'"); 315 Objects.requireNonNull(computeNextEntry, ROHelpers.NULL_MSG + "'computeNextEntry'"); 316 317 fromBuilderOrProperties = false; 318 319 Properties properties = new Properties(((sizeIfKnown == null) ? 16 : sizeIfKnown)); 320 321 do 322 { 323 computeNextEntry.run(); 324 if ((refHolder.a == null) && (refHolder.b == null)) break; 325 properties.put(refHolder.a, refHolder.b); 326 } 327 while (true); 328 329 // Empty Optimization (throw away, completely, the reference, use static-constant) 330 this.properties = (properties.size() == 0) ? EMPTY_PROPERTIES : properties; 331 } 332 333 334 // ******************************************************************************************** 335 // ******************************************************************************************** 336 // Convert to java.util Types 337 // ******************************************************************************************** 338 // ******************************************************************************************** 339 340 341 /** 342 * Clone's {@code 'this'} instance internal {@code Properties<E>} field, and returns it. 343 * <EMBED CLASS='external-html' DATA-TYPE=Properties DATA-FILE-ID=CLONE_TO> 344 * 345 * @return An independent, mutable copy of {@code 'this'} instance' internal 346 * {@code Properties<E>} data-structure. 347 */ 348 @SuppressWarnings("unchecked") // The clone() cast 349 public Properties cloneToProperties() 350 { 351 return fromBuilderOrProperties 352 ? new Properties(this.properties) 353 : (Properties) this.properties.clone(); 354 } 355 356 /** 357 * Invokes {@code java.util.Collections.unmodifiableMap} on the internal {@code Properties}. 358 * <EMBED CLASS='external-html' DATA-RET_TYPE=Map DATA-FILE-ID=WRAP_TO_IMMUTABLE> 359 * 360 * @return A {@code Map} which adheres to the JDK interface {@code java.util.Map}, but throws 361 * an {@code UnsupportedOperationException} if a user attempts to invoke a Mutator-Method on 362 * the returned instance. 363 */ 364 public Map<Object, Object> wrapToImmutableMap() 365 { return Collections.unmodifiableMap(this.properties); } 366 367 368 // ******************************************************************************************** 369 // ******************************************************************************************** 370 // Original JDK Methods, java.util.Properties 371 // ******************************************************************************************** 372 // ******************************************************************************************** 373 374 375 /** 376 * Writes this property list (key and element pairs) from this instance' internal 377 * {@code Properties} table to the output character stream (in a format suitable for using the 378 * {@code Properties.load(Reader)} method. 379 * 380 * <EMBED CLASS='external-html' DATA-FILE-ID=STORE_DESC_WRITER> 381 * 382 * @param writer an output character stream writer. 383 * @param comments a description of the property list. 384 * 385 * @throws IOException if writing this property list to the specified output stream throws an 386 * {@code IOException}. 387 * 388 * @throws ClassCastException if this {@code Properties} object contains any keys or values 389 * that are not {@code Strings}. 390 * 391 * @throws NullPointerException if {@code writer} is null. 392 */ 393 public void store(Writer writer, String comments) 394 throws IOException 395 { this.properties.store(writer, comments); } 396 397 /** 398 * Writes this property list (key and element pairs) from this instance' internal 399 * {@code Properties} table to the output character stream in a format suitable for using the 400 * {@code Properties.load(InputStream)} method. 401 * 402 * <EMBED CLASS='external-html' DATA-FILE-ID=STORE_DESC_OUTPUTS> 403 * 404 * @param out an output stream. 405 * @param comments a description of the property list. 406 * 407 * @throws IOException if writing this property list to the specified output stream throws an 408 * {@code IOException}. 409 * 410 * @throws ClassCastException if this {@code Properties} object contains any keys or values 411 * that are not {@code Strings}. 412 * 413 * @throws NullPointerException if {@code out} is null. 414 */ 415 public void store(OutputStream out, String comments) 416 throws IOException 417 { this.properties.store(out, comments); } 418 419 /** 420 * Emits an XML document representing all of the properties contained in this table. 421 * 422 * <BR /><BR /> An invocation of this method of the form {@code props.storeToXML(os, comment)} 423 * behaves in exactly the same way as the invocation 424 * {@code props.storeToXML(os, comment, "UTF-8");}. 425 * 426 * @param os the output stream on which to emit the XML document. 427 * @param comment a description of the property list, or {@code null} if no comment is desired. 428 * 429 * @throws IOException if writing to the specified output stream results in an 430 * {@code IOException}. 431 * 432 * @throws NullPointerException if {@code os} is null. 433 * 434 * @throws ClassCastException if this {@code Properties} object contains any keys or values 435 * that are not {@code Strings}. 436 */ 437 public void storeToXML(OutputStream os, String comment) 438 throws IOException 439 { this.properties.storeToXML(os, comment); } 440 441 /** 442 * Emits an XML document representing all of the properties contained in this table, using the 443 * specified encoding. 444 * 445 * <EMBED CLASS='external-html' DATA-FILE-ID=STOREXML_DESC_OS_S_S> 446 * 447 * @param os the output stream on which to emit the XML document. 448 * @param comment a description of the property list, or {@code null} if no comment is desired. 449 * 450 * @param encoding the name of a supported 451 * <a href="../lang/package-summary.html#charenc">character encoding</a> 452 * 453 * @throws IOException if writing to the specified output stream results in an 454 * {@code IOException}. 455 * 456 * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the 457 * implementation. 458 * 459 * @throws NullPointerException if {@code os} is {@code null}, or if {@code encoding} is 460 * {@code null}. 461 * 462 * @throws ClassCastException if this {@code Properties} object contains any keys or values 463 * that are not {@code Strings}. 464 * 465 * @spec https://www.w3.org/TR/xml Extensible Markup Language (XML) 1.0 (Fifth Edition) 466 * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character Encoding in Entities</a> 467 */ 468 public void storeToXML(OutputStream os, String comment, String encoding) throws IOException 469 { this.properties.storeToXML(os, comment, encoding); } 470 471 /** 472 * Emits an XML document representing all of the properties contained in this table, using 473 * the specified encoding. 474 * 475 * <EMBED CLASS='external-html' DATA-FILE-ID=STOREXML_DESC_OS_S_CHRS> 476 * 477 * @param os the output stream on which to emit the XML document. 478 * @param comment a description of the property list, or {@code null} if no comment is desired. 479 * @param charset the charset 480 * 481 * @throws IOException if writing to the specified output stream results in an 482 * {@code IOException}. 483 * 484 * @throws NullPointerException if {@code os} or {@code charset} is {@code null}. 485 * 486 * @throws ClassCastException if this {@code Properties} object contains any keys or values 487 * that are not {@code Strings}. 488 * 489 * @spec https://www.w3.org/TR/xml Extensible Markup Language (XML) 1.0 (Fifth Edition) 490 * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character Encoding in Entities</a> 491 */ 492 public void storeToXML(OutputStream os, String comment, Charset charset) throws IOException 493 { this.properties.storeToXML(os, comment, charset); } 494 495 /** 496 * Searches for the property with the specified key in this property list. If the key is not 497 * found in this property list, the default property list, and its defaults, recursively, are 498 * then checked. The method returns {@code null} if the property is not found. 499 * 500 * @param key the property key. 501 * @return the value in this property list with the specified key value. 502 */ 503 public String getProperty(String key) 504 { return this.properties.getProperty(key); } 505 506 /** 507 * Searches for the property with the specified key in this property list. If the key is not 508 * found in this property list, the default property list, and its defaults, recursively, are 509 * then checked. The method returns the default value argument if the property is not found. 510 * 511 * @param key the hashtable key. 512 * @param defaultValue a default value. 513 * 514 * @return the value in this property list with the specified key value. 515 */ 516 public String getProperty(String key, String defaultValue) 517 { return this.properties.getProperty(key, defaultValue); } 518 519 /** 520 * Returns an enumeration of all the keys in this property list, including distinct keys in 521 * the default property list if a key of the same name has not already been found from the main 522 * properties list. 523 * 524 * @return an enumeration of all the keys in this property list, including the keys in the 525 * default property list. 526 * 527 * @throws ClassCastException if any key in this property list is not a {@code String}. 528 * @see #stringPropertyNames 529 */ 530 public Enumeration<?> propertyNames() 531 { return this.properties.propertyNames(); } 532 533 /** 534 * Returns an unmodifiable set of keys from this property list where the key and its 535 * corresponding value are strings, including distinct keys in the default property list if a 536 * key of the same name has not already been found from the main properties list. Properties 537 * whose key or value is not of type {@code String} are omitted. 538 * 539 * <BR /><BR />The returned set is not backed by this {@code Properties} object. Changes to 540 * this {@code Properties} object are not reflected in the returned set. 541 * 542 * @return an unmodifiable set of keys in this property list where the key and its 543 * corresponding value are strings, including the keys in the default property list. 544 */ 545 public ReadOnlySet<String> stringPropertyNames() 546 { return InterfaceBuilder.toReadOnlySet(this.properties.stringPropertyNames()); } 547 548 /** 549 * Prints this property list out to the specified output stream. This method is useful for 550 * debugging. 551 * 552 * @param out an output stream. 553 * @throws ClassCastException if any key in this property list is not a {@code String}. 554 */ 555 public void list(PrintStream out) 556 { this.properties.list(out); } 557 558 /** 559 * Prints this property list out to the specified output stream. This method is useful for 560 * debugging. 561 * 562 * @param out an output stream. 563 * @throws ClassCastException if any key in this property list is not a {@code String}. 564 */ 565 public void list(PrintWriter out) 566 { this.properties.list(out); } 567 568 // 569 // Hashtable methods overridden and delegated to a ConcurrentHashMap instance 570 571 @Override 572 public int size() 573 { return this.properties.size(); } 574 575 @Override 576 public boolean isEmpty() 577 { return this.properties.isEmpty(); } 578 579 @Override 580 public Enumeration<Object> keys() 581 { return this.properties.keys(); } 582 583 @Override 584 public Enumeration<Object> elements() 585 { return this.properties.elements(); } 586 587 @Override 588 public boolean contains(Object value) 589 { return this.properties.contains(value); } 590 591 @Override 592 public boolean containsValue(Object value) 593 { return this.properties.containsValue(value); } 594 595 @Override 596 public boolean containsKey(Object key) 597 { return this.properties.containsKey(key); } 598 599 @Override 600 public Object get(Object key) 601 { return this.properties.get(key); } 602 603 @Override 604 public ReadOnlySet<Object> keySet() 605 { 606 return InterfaceBuilder.toReadOnlySet( 607 fromBuilderOrProperties 608 ? ((ROPropertiesBuilder) this.properties)._keySet(friendClassBadge) 609 : this.properties.keySet() 610 ); 611 } 612 613 @Override 614 public ReadOnlyCollection<Object> values() 615 { 616 return InterfaceBuilder.toReadOnlyCollection( 617 fromBuilderOrProperties 618 ? ((ROPropertiesBuilder) this.properties)._values(friendClassBadge) 619 : this.properties.values() 620 ); 621 } 622 623 @Override 624 public ReadOnlySet<ReadOnlyMap.Entry<Object, Object>> entrySet() 625 { 626 return ROHelpers.toReadOnlyEntrySet( 627 fromBuilderOrProperties 628 ? ((ROPropertiesBuilder) this.properties)._entrySet(friendClassBadge) 629 : this.properties.entrySet() 630 ); 631 } 632 633 @Override 634 public Object getOrDefault(Object key, Object defaultValue) 635 { return this.properties.getOrDefault(key, defaultValue); } 636 637 @Override 638 public synchronized void forEach(BiConsumer<? super Object, ? super Object> action) 639 { this.properties.forEach(action); } 640 641 642 // ******************************************************************************************** 643 // ******************************************************************************************** 644 // java.lang.Object 645 // ******************************************************************************************** 646 // ******************************************************************************************** 647 648 649 /** 650 * Returns a {@code String} representation of this {@code Properties}. The {@code String} 651 * representation consists of a list of the collection's elements in the order they are 652 * returned by its iterator, enclosed in square brackets ({@code "[]"}). Adjacent elements are 653 * separated by the characters {@code ", "} (comma and space). Elements are converted to 654 * {@code String's} as by {@code String.valueOf(Object)}. 655 * 656 * @return a {@code String} representation of this {@code Properties} 657 */ 658 @Override 659 public String toString() 660 { return this.properties.toString(); } 661 662 /** 663 * Compares the specified Object with this List for equality, as per the definition in the 664 * class {@code java.util.Properties}. 665 * 666 * @param o object to be compared for equality with this {@code Properties} 667 * @return {@code TRUE} if the specified Object is equal to this map 668 */ 669 @Override 670 public boolean equals(Object o) 671 { 672 if (this == o) return true; 673 if (! (o instanceof ReadOnlyProperties)) return false; 674 return this.properties.equals(((ReadOnlyProperties) o).properties); 675 } 676 677 @Override 678 public synchronized int hashCode() 679 { return this.properties.hashCode(); } 680}