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.util.*;
028import java.io.*;
029
030import java.nio.charset.Charset;
031import java.util.function.BiConsumer;
032import java.util.function.BiFunction;
033import java.util.function.Function;
034
035/**
036 * A Copy of Java's {@code Properties} class; used for building a {@link ReadOnlyProperties}.
037 * Maintains <I>an internal and inaccessible {@code Properties} instance</I>.  
038 * 
039 * <EMBED CLASS=globalDefs DATA-JDK=Properties>
040 * <EMBED CLASS='external-html' DATA-A_AN=a DATA-FILE-ID=BUILDERS>
041 * @see ReadOnlyProperties
042 */
043@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="JDHBI_BUILDER")
044public final class ROPropertiesBuilder
045    extends Properties
046    implements Cloneable, java.io.Serializable
047{
048    // ********************************************************************************************
049    // ********************************************************************************************
050    // Fields & Builder
051    // ********************************************************************************************
052    // ********************************************************************************************
053
054
055    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */
056    protected static final long serialVersionUID = 1;
057
058    // Exception messages need this
059    private static final String ROP = "Properties";
060
061    private boolean built = false;
062
063    // Mimics the C++ Concept of "Friend Class".  This badge is utilized by the "build()" method
064    // directly below this inner-class declaration.  It is the only place where this declaration is
065    // actually used anywhere.  This prohibits access to the constructor that is used to anybody
066    // else
067
068    static class AccessBadge { private AccessBadge() { } }
069    private static final AccessBadge friendClassBadge = new AccessBadge();
070
071    /**
072     * Simply transfers {@code 'this'} instance' internal {@code Properties} to the 
073     * {@code ReadOnlyProperties} Wrapper-Class.
074     * 
075     * @return a newly constructed {@code ReadOnlyProperties} "Wrapper-Class", shielding the
076     * internal {@code 'properties'} private-field from any modification.
077     */
078    public ReadOnlyProperties build()
079    {
080        this.built = true;
081
082        return (size() == 0)
083            ? ReadOnlyProperties.emptyROP()
084            : new ReadOnlyProperties(this, friendClassBadge);
085    }
086
087
088    // ********************************************************************************************
089    // ********************************************************************************************
090    // Modified Constructors
091    // ********************************************************************************************
092    // ********************************************************************************************
093
094
095    /**
096     * Creates an empty {@code ROPropertiesBuilder} (property list) with no default values.
097     *
098     * <BR /><BR />The initial capacity of a {@code Properties} object created
099     * with this constructor is unspecified.
100     */
101    public ROPropertiesBuilder()
102    { super(); }
103
104    /**
105     * Creates an empty {@code ROPropertiesBuilder} (property list) with no default values, and
106     * with an initial size accommodating the specified number of elements without the need to
107     * dynamically resize.
108     *
109     * @param initialCapacity the {@code Properties} will be sized to accommodate this many
110     * elements
111     * 
112     * @throws IllegalArgumentException if the initial capacity is less than zero.
113     */
114    public ROPropertiesBuilder(int initialCapacity)
115    { super(initialCapacity); }
116
117    /**
118     * Creates an empty {@code ROPropertiesBuilder} (property list) with the specified defaults.
119     *
120     * <BR /><BR />The initial capacity of a {@code Properties} object created with this
121     * constructor is unspecified.
122     *
123     * @param defaults the defaults.
124     */
125    public ROPropertiesBuilder(Properties defaults)
126    { super(defaults); }
127
128
129    // ********************************************************************************************
130    // ********************************************************************************************
131    // Original JDK Methods
132    // ********************************************************************************************
133    // ********************************************************************************************
134
135
136    /**
137     * Calls the {@code Hashtable} method {@code put}. Provided for parallelism with the
138     * {@code getProperty} method.  Enforces use of {@code String's} for property keys and values.
139     * The value returned is the result of the {@code Hashtable} call to {@code put}.
140     *
141     * @param key the key to be placed into this property list.
142     * @param value the value corresponding to {@code key}.
143     * 
144     * @return the previous value of the specified key in this property list, or {@code null} if it
145     * did not have one.
146     * 
147     * @see #getProperty
148     */
149    public synchronized Object setProperty(String key, String value)
150    {
151        if (this.built) throw new AttemptedModificationException(ROP);
152        return super.setProperty(key, value);
153    }
154
155    /**
156     * Reads a property list (key and element pairs) from the input character stream in a simple
157     * line-oriented format.
158     * 
159     * <EMBED CLASS='external-html' DATA-FILE-ID=LOAD_DESC_READER>
160     * 
161     * @param reader the input character stream.
162     * @throws IOException if an error occurred when reading from the input stream.
163     * @throws IllegalArgumentException if a malformed Unicode escape appears in the input.
164     * @throws NullPointerException if {@code reader} is null.
165     */
166    public synchronized void load(Reader reader) throws IOException
167    {
168        if (this.built) throw new AttemptedModificationException(ROP);
169        super.load(reader);
170    }
171
172    /**
173     * Reads a property list (key and element pairs) from the input byte stream. The input stream
174     * is in a simple line-oriented format as specified in
175     * {@link #load(java.io.Reader) load(Reader)} and is assumed to use the ISO 8859-1 character
176     * encoding; that is each byte is one Latin1 character. Characters not in Latin1, and certain
177     * special characters, are represented in keys and elements using Unicode escapes as defined in
178     * section {@code 3.3} of <cite>The Java Language Specification</cite>.
179     * 
180     * <BR /><BR />The specified stream remains open after this method returns.
181     *
182     * @param inStream the input stream.
183     * @throws IOException if an error occurred when reading from the input stream.
184     * 
185     * @throws IllegalArgumentException if the input stream contains a malformed Unicode escape
186     * sequence.
187     * 
188     * @throws NullPointerException if {@code inStream} is null.
189     */
190    public synchronized void load(InputStream inStream) throws IOException
191    {
192        if (this.built) throw new AttemptedModificationException(ROP);
193        super.load(inStream);
194    }
195
196    /**
197     * Loads all of the properties represented by the XML document on the specified input stream
198     * into this properties table.
199     * 
200     * <EMBED CLASS='external-html' DATA-FILE-ID=LOADXML_DESC_INPUT_STRM>
201     * 
202     * @param in the input stream from which to read the XML document.
203     * 
204     * @throws IOException if reading from the specified input stream results in an
205     * {@code IOException}.
206     * 
207     * @throws java.io.UnsupportedEncodingException if the document's encoding declaration can be
208     * read and it specifies an encoding that is not supported
209     * 
210     * @throws InvalidPropertiesFormatException Data on input stream does not constitute a valid
211     * XML document with the mandated document type.
212     * 
213     * @throws NullPointerException if {@code in} is null.
214     *
215     * @spec https://www.w3.org/TR/xml Extensible Markup Language (XML) 1.0 (Fifth Edition)
216     * @see #storeToXML(OutputStream, String, String)
217     * @see <a href="http://www.w3.org/TR/REC-xml/#charencoding">Character Encoding in Entities</a>
218     */
219    public synchronized void loadFromXML(InputStream in)
220        throws IOException, InvalidPropertiesFormatException
221    {
222        if (this.built) throw new AttemptedModificationException(ROP);
223        super.loadFromXML(in);
224    }
225
226
227    // ********************************************************************************************
228    // ********************************************************************************************
229    // Hashtable methods overridden and delegated to internal 'properties' instance
230    // ********************************************************************************************
231    // ********************************************************************************************
232
233
234    @Override
235    public synchronized Object put(Object key, Object value)
236    {
237        if (this.built) throw new AttemptedModificationException(ROP);
238        return super.put(key, value);
239    }
240
241    @Override
242    public synchronized Object remove(Object key)
243    {
244        if (this.built) throw new AttemptedModificationException(ROP);
245        return super.remove(key);
246    }
247
248    @Override
249    public synchronized void putAll(Map<?, ?> t)
250    {
251        if (this.built) throw new AttemptedModificationException(ROP);
252        super.putAll(t);
253    }
254
255    @Override
256    public synchronized void clear()
257    {
258        if (this.built) throw new AttemptedModificationException(ROP);
259        super.clear();
260    }
261
262    @Override
263    public synchronized void replaceAll(BiFunction<Object, Object, ?> function)
264    {
265        if (this.built) throw new AttemptedModificationException(ROP);
266        super.replaceAll(function);
267    }
268
269    @Override
270    public synchronized Object putIfAbsent(Object key, Object value)
271    {
272        if (this.built) throw new AttemptedModificationException(ROP);
273        return super.putIfAbsent(key, value);
274    }
275
276    @Override
277    public synchronized boolean remove(Object key, Object value)
278    {
279        if (this.built) throw new AttemptedModificationException(ROP);
280        return super.remove(key, value);
281    }
282
283    @Override
284    public synchronized boolean replace(Object key, Object oldValue, Object newValue)
285    {
286        if (this.built) throw new AttemptedModificationException(ROP);
287        return super.replace(key, oldValue, newValue);
288    }
289
290    @Override
291    public synchronized Object replace(Object key, Object value)
292    {
293        if (this.built) throw new AttemptedModificationException(ROP);
294        return super.replace(key, value);
295    }
296
297    @Override
298    public synchronized Object computeIfAbsent
299        (Object key, Function<Object, ?> mappingFunction)
300    {
301        if (this.built) throw new AttemptedModificationException(ROP);
302        return super.computeIfAbsent(key, mappingFunction);
303    }
304
305    @Override
306    public synchronized Object computeIfPresent
307        (Object key, BiFunction<Object, Object, ?> remappingFunction)
308    {
309        if (this.built) throw new AttemptedModificationException(ROP);
310        return super.computeIfPresent(key, remappingFunction);
311    }
312
313    @Override
314    public synchronized Object compute
315        (Object key, BiFunction<Object, Object, ?> remappingFunction)
316    {
317        if (this.built) throw new AttemptedModificationException(ROP);
318        return super.compute(key, remappingFunction);
319    }
320
321    @Override
322    public synchronized Object merge
323        (Object key, Object value, BiFunction<Object, Object, ?> remappingFunction)
324    {
325        if (this.built) throw new AttemptedModificationException(ROP);
326        return super.merge(key, value, remappingFunction);
327    }
328
329
330    // ********************************************************************************************
331    // ********************************************************************************************
332    // Methods inherited from class java.util.Properties
333    // ********************************************************************************************
334    // ********************************************************************************************
335
336
337    // contains, containsKey, containsValue, elements, entrySet, forEach, get, getOrDefault,
338    // getProperty, getProperty, hashCode, isEmpty, keys, keySet, list, list, propertyNames,
339    // rehash, save, size, store, store, storeToXML, storeToXML, storeToXML, stringPropertyNames,
340    // toString, values
341    // 
342    // Introspection Only:  contains, containsKey, containsValue, elements, forEach, get,
343    //                      getOrDefault, getProperty, getProperty, hashCode, isEmpty, keys, list,
344    //                      list, propertyNames, rehash, save, size, store, store, storeToXML,
345    //                      storeToXML, storeToXML, stringPropertyNames, toString
346    // 
347    // Potential Mutator: entrySet, keySet, values
348
349    /**
350     * Restricts a back-door into the underlying data-structure.
351     * <EMBED CLASS='external-html'DATA-RET_TYPE=Set DATA-FILE-ID=UNMODIFIABLE>
352     * @return a {@code java.util.Set} that cannot modify this {@code Properties-Builder}
353     */
354    public Set<Map.Entry<Object, Object>> entrySet()
355    { return Collections.unmodifiableSet(super.entrySet()); }
356
357    Set<Map.Entry<Object, Object>> _entrySet(ReadOnlyProperties.AccessBadge friendClassBadge)
358    { return super.entrySet(); }
359
360    /**
361     * Restricts a back-door into the underlying data-structure.
362     * <EMBED CLASS='external-html'DATA-RET_TYPE=Set DATA-FILE-ID=UNMODIFIABLE>
363     * @return a {@code java.util.Set} that cannot modify this {@code Properties-Builder}
364     */
365    public Set<Object> keySet()
366    { return Collections.unmodifiableSet(super.keySet()); }
367
368    Set<Object> _keySet(ReadOnlyProperties.AccessBadge friendClassBadge)
369    { return super.keySet(); }
370
371    /**
372     * Restricts a back-door into the underlying data-structure.
373     * <EMBED CLASS='external-html'DATA-RET_TYPE=Collection DATA-FILE-ID=UNMODIFIABLE>
374     * @return a {@code java.util.Collection} that cannot modify this {@code Properties-Builder}
375     */
376    public Collection<Object> values()
377    { return Collections.unmodifiableCollection(super.values()); }
378
379    Collection<Object> _values(ReadOnlyProperties.AccessBadge friendClassBadge)
380    { return super.values(); }
381
382
383    // ********************************************************************************************
384    // ********************************************************************************************
385    // java.lang.Object
386    // ********************************************************************************************
387    // ********************************************************************************************
388
389
390    /**
391     * Compares the specified Object with this Builder for equality, as per the definition in the
392     * private and internal field {@code 'properties'}.
393     *
394     * @param  o object to be compared for equality with this {@code ROPropertiesBuilder} instance
395     * @return true if the specified Object is equal to this Builder
396     */
397    public boolean equals(Object o)
398    {
399        if (this == o) return true;
400        if (! (o instanceof ROPropertiesBuilder)) return false;
401        return super.equals((Properties) o);
402    }
403
404    /**
405     * Clones this instance' of {@code ROPropertiesBuilder}.
406     * 
407     * <BR /><BR />The clone that's returned has had it's internal {@code 'built'} boolean-flag set
408     * {@code FALSE}
409     * 
410     * @return a clone of this builder
411     */
412    public synchronized ROPropertiesBuilder clone()
413    { return new ROPropertiesBuilder(this); }
414
415    private ROPropertiesBuilder(ROPropertiesBuilder rohtb)
416    { super(rohtb); this.built=false; }
417}