001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau;
014
015import static org.apache.juneau.PropertyType.*;
016import static org.apache.juneau.internal.CollectionUtils.*;
017
018import java.lang.reflect.*;
019import java.util.*;
020
021import org.apache.juneau.PropertyStoreBuilder.*;
022import org.apache.juneau.internal.*;
023import org.apache.juneau.json.*;
024
025
026/**
027 * Represents an immutable collection of properties.
028 *
029 * <p>
030 * The general idea behind a property store is to serve as a reusable configuration of an artifact (e.g. a Serializer)
031 * such that the artifact can be cached and reused if the property stores are 'equal'.
032 *
033 * <h5 class='topic'>Concept</h5>
034 *
035 * <p>
036 * For example, two serializers of the same type created with the same configuration will always end up being
037 * the same serializer:
038 *
039 * <p class='bcode w800'>
040 *    WriterSerializer s1 = JsonSerializer.<jsm>create</jsm>().pojoSwaps(MySwap.<jk>class</jk>).simple().build();
041 *    WriterSerializer s2 = JsonSerializer.<jsm>create</jsm>().simple().pojoSwaps(MySwap.<jk>class</jk>).build();
042 *    <jk>assert</jk>(s1 == s2);
043 * </p>
044 *
045 * <p>
046 * This has the effect of significantly improving performance, especially if you're creating many Serializers and
047 * Parsers.
048 *
049 * <h5 class='topic'>PropertyStoreBuilder</h5>
050 *
051 * <p>
052 * The {@link PropertyStoreBuilder} class is used to build up and instantiate immutable <code>PropertyStore</code>
053 * objects.
054 *
055 * <p>
056 * In the example above, the property store being built looks like the following:
057 *
058 * <p class='bcode w800'>
059 *    PropertyStore ps = PropertyStore
060 *       .<jsm>create</jsm>()
061 *       .set(<js>"BeanContext.pojoSwaps.lc"</js>, MySwap.<jk>class</jk>)
062 *       .set(<js>"JsonSerializer.simpleMode.b"</js>, <jk>true</jk>)
063 *       .build();
064 * </p>
065 *
066 * <p>
067 * Property stores are immutable, comparable, and their hashcodes are calculated exactly one time.
068 * That makes them particularly suited for use as hashmap keys, and thus for caching reusable serializers and parsers.
069 *
070 * <h5 class='topic'>Property naming convention</h5>
071 *
072 * <p>
073 * Property names must have the following format...
074 * <p class='bcode w800'>
075 *    <js>"{class}.{name}.{type}"</js>
076 * </p>
077 * <p>
078 * ...where the parts consist of the following...
079 * <ul>
080 *    <li><js>"{class}"</js> - The group name of the property (e.g. <js>"JsonSerializer"</js>).
081 *       <br>It's always going to be the simple class name of the class it's associated with.
082 *    <li><js>"{name}"</js> - The property name (e.g. <js>"useWhitespace"</js>).
083 *    <li><js>"{type}"</js> - The property data type.
084 *       <br>A 1 or 2 character string that identifies the data type of the property.
085 *       <br>Valid values are:
086 *       <ul>
087 *          <li><js>"s"</js>:  <code>String</code>
088 *          <li><js>"b"</js>:  <code>Boolean</code>
089 *          <li><js>"i"</js>:  <code>Integer</code>
090 *          <li><js>"c"</js>:  <code>Class</code>
091 *          <li><js>"o"</js>:  <code>Object</code>
092 *          <li><js>"ss"</js>:  <code>TreeSet&lt;String&gt;</code>
093 *          <li><js>"si"</js>:  <code>TreeSet&lt;Integer&gt;</code>
094 *          <li><js>"sc"</js>:  <code>TreeSet&lt;Class&gt;</code>
095 *          <li><js>"ls"</js>:  <code>Linkedlist&lt;String&gt;</code>
096 *          <li><js>"li"</js>:  <code>Linkedlist&lt;Integer&gt;</code>
097 *          <li><js>"lc"</js>:  <code>Linkedlist&lt;Class&gt;</code>
098 *          <li><js>"lo"</js>:  <code>Linkedlist&lt;Object&gt;</code>
099 *          <li><js>"sms"</js>:  <code>TreeMap&lt;String,String&gt;</code>
100 *          <li><js>"smi"</js>:  <code>TreeMap&lt;String,Integer&gt;</code>
101 *          <li><js>"smc"</js>:  <code>TreeMap&lt;String,Class&gt;</code>
102 *          <li><js>"smo"</js>:  <code>TreeMap&lt;String,Object&gt;</code>
103 *          <li><js>"oms"</js>:  <code>LinkedHashMap&lt;String,String&gt;</code>
104 *          <li><js>"omi"</js>:  <code>LinkedHashMap&lt;String,Integer&gt;</code>
105 *          <li><js>"omc"</js>:  <code>LinkedHashMap&lt;String,Class&gt;</code>
106 *          <li><js>"omo"</js>:  <code>LinkedHashMap&lt;String,Object&gt;</code>
107 *       </ul>
108 * </ul>
109 *
110 * <p>
111 * For example, <js>"BeanContext.pojoSwaps.lc"</js> refers to a property on the <code>BeanContext</code> class
112 * called <code>pojoSwaps</code> that has a data type of <code>List&lt;Class&gt;</code>.
113 *
114 * <h5 class='topic'>Property value normalization</h5>
115 *
116 * <p>
117 * Property values get 'normalized' when they get set.
118 * For example, calling <code>propertyStore.set(<js>"BeanContext.debug.b"</js>, <js>"true"</js>)</code> will cause the property
119 * value to be converted to a boolean.
120 *
121 * <h5 class='topic'>Set types</h5>
122 *
123 * <p>
124 * The <js>"sX"</js> property types are sorted sets.
125 * <br>Use these for collections of objects where the order is not important.
126 * <br>Internally, a <code>TreeSet</code> is used so that the order in which you add elements does not affect the
127 * resulting order of the property.
128 *
129 * <h5 class='topic'>List types</h5>
130 *
131 * <p>
132 * The <js>"lX"</js> property types are ordered lists.
133 * <br>Use these in cases where the order in which entries are added is important.
134 *
135 * <p>
136 * Adding to a list property will cause the new entries to be added to the BEGINNING of the list.
137 * <br>This ensures that the resulting order of the list is in most-to-least importance.
138 *
139 * <p>
140 * For example, multiple calls to <code>pojoSwaps()</code> causes new entries to be added to the beginning of the list
141 * so that previous values can be 'overridden':
142 * <p class='bcode w800'>
143 *    <jc>// Swap order:  [MySwap2.class, MySwap1.class]</jc>
144 *    JsonSerializer.create().pojoSwaps(MySwap1.<jk>class</jk>).pojoSwaps(MySwap2.<jk>class</jk>).build();
145 * </p>
146 *
147 * <p>
148 * Note that the order is different when passing multiple values into the <code>pojoSwaps()</code> method, in which
149 * case the order should be first-match-wins:
150 * <p class='bcode w800'>
151 *    <jc>// Swap order:  [MySwap1.class, MySwap2.class]</jc>
152 *    JsonSerializer.create().pojoSwaps(MySwap1.<jk>class</jk>,MySwap2.<jk>class</jk>).build();
153 * </p>
154 *
155 * <p>
156 * Combined, the results look like this:
157 * <p class='bcode w800'>
158 *    <jc>// Swap order:  [MySwap4.class, MySwap3.class, MySwap1.class, MySwap2.class]</jc>
159 *    JsonSerializer
160 *       .create()
161 *       .pojoSwaps(MySwap1.<jk>class</jk>,MySwap2.<jk>class</jk>)
162 *       .pojoSwaps(MySwap3.<jk>class</jk>)
163 *       .pojoSwaps(MySwap4.<jk>class</jk>)
164 *       .build();
165 * </p>
166 *
167 * <h5 class='topic'>Map types</h5>
168 *
169 * <p>
170 * The <js>"smX"</js> and <js>"omX"</js> are sorted and order maps respectively.
171 *
172 * <h5 class='topic'>Command properties</h5>
173 *
174 * <p>
175 * Set and list properties have the additional convenience 'command' names for adding and removing entries:
176 * <p class='bcode w800'>
177 *    <js>"{class}.{name}.{type}/add"</js>  <jc>// Add a value to the set/list.</jc>
178 *    <js>"{class}.{name}.{type}/remove"</js>  <jc>// Remove a value from the set/list.</jc>
179 * </p>
180 *
181 * <p>
182 * Map properties have the additional convenience property name for adding and removing map entries:
183 * <p class='bcode w800'>
184 *    <js>"{class}.{name}.{type}/add.{key}"</js>  <jc>// Add a map entry (or delete if the value is null).</jc>
185 * </p>
186 */
187@SuppressWarnings("unchecked")
188public final class PropertyStore {
189
190   /**
191    * A default empty property store.
192    */
193   public static PropertyStore DEFAULT = PropertyStore.create().build();
194
195   final SortedMap<String,PropertyGroup> groups;
196   private final int hashCode;
197
198   // Created by PropertyStoreBuilder.build()
199   PropertyStore(Map<String,PropertyGroupBuilder> propertyMaps) {
200      TreeMap<String,PropertyGroup> m = new TreeMap<>();
201      for (Map.Entry<String,PropertyGroupBuilder> p : propertyMaps.entrySet())
202         m.put(p.getKey(), p.getValue().build());
203      this.groups = Collections.unmodifiableSortedMap(m);
204      this.hashCode = groups.hashCode();
205   }
206
207   /**
208    * Creates a new empty builder for a property store.
209    *
210    * @return A new empty builder for a property store.
211    */
212   public static PropertyStoreBuilder create() {
213      return new PropertyStoreBuilder();
214   }
215
216   /**
217    * Creates a new property store builder initialized with the values in this property store.
218    *
219    * @return A new property store builder.
220    */
221   public PropertyStoreBuilder builder() {
222      return new PropertyStoreBuilder(this);
223   }
224
225   private Property findProperty(String key) {
226      String g = group(key);
227      String k = key.substring(g.length()+1);
228      PropertyGroup pm = groups.get(g);
229
230      if (pm != null) {
231         Property p = pm.get(k);
232         if (p != null)
233            return p;
234      }
235
236      String s = System.getProperty(key);
237      if (s != null)
238         return PropertyStoreBuilder.MutableProperty.create(k, s).build();
239
240      return null;
241   }
242
243   /**
244    * Returns the raw property value with the specified name.
245    *
246    * @param key The property name.
247    * @return The property value, or <jk>null</jk> if it doesn't exist.
248    */
249   public Object getProperty(String key) {
250      Property p = findProperty(key);
251      return p == null ? null : p.value;
252   }
253
254   /**
255    * Returns the property value with the specified name.
256    *
257    * @param key The property name.
258    * @param c The class to cast or convert the value to.
259    * @param def The default value.
260    * @return The property value, or the default value if it doesn't exist.
261    */
262   public <T> T getProperty(String key, Class<T> c, T def) {
263      Property p = findProperty(key);
264      return p == null ? def : p.as(c);
265   }
266
267   /**
268    * Returns the class property with the specified name.
269    *
270    * @param key The property name.
271    * @param type The class type of the property.
272    * @param def The default value.
273    * @return The property value, or the default value if it doesn't exist.
274    */
275   public <T> Class<? extends T> getClassProperty(String key, Class<T> type, Class<? extends T> def) {
276      Property p = findProperty(key);
277      return p == null ? def : (Class<T>)p.as(Class.class);
278   }
279
280   /**
281    * Returns the array property value with the specified name.
282    *
283    * @param key The property name.
284    * @param eType The class type of the elements in the property.
285    * @return The property value, or an empty array if it doesn't exist.
286    */
287   public <T> T[] getArrayProperty(String key, Class<T> eType) {
288      Property p = findProperty(key);
289      return (T[]) (p == null ? Array.newInstance(eType, 0) : p.asArray(eType));
290   }
291
292   /**
293    * Returns the array property value with the specified name.
294    *
295    * @param key The property name.
296    * @param eType The class type of the elements in the property.
297    * @param def The default value.
298    * @return The property value, or an empty array if it doesn't exist.
299    */
300   public <T> T[] getArrayProperty(String key, Class<T> eType, T[] def) {
301      Property p = findProperty(key);
302      return p == null ? def : p.asArray(eType);
303   }
304
305   /**
306    * Returns the class array property with the specified name.
307    *
308    * @param key The property name.
309    * @return The property value, or an empty array if it doesn't exist.
310    */
311   public Class<?>[] getClassArrayProperty(String key) {
312      Property p = findProperty(key);
313      return p == null ? new Class[0] : p.as(Class[].class);
314   }
315
316   /**
317    * Returns the class array property with the specified name.
318    *
319    * @param key The property name.
320    * @param def The default value.
321    * @return The property value, or an empty array if it doesn't exist.
322    */
323   public Class<?>[] getClassArrayProperty(String key, Class<?>[] def) {
324      Property p = findProperty(key);
325      return p == null ? def : p.as(Class[].class);
326   }
327
328   /**
329    * Returns the class array property with the specified name.
330    *
331    * @param key The property name.
332    * @param eType The class type of the elements in the property.
333    * @return The property value, or an empty array if it doesn't exist.
334    */
335   public <T> Class<T>[] getClassArrayProperty(String key, Class<T> eType) {
336      Property p = findProperty(key);
337      return p == null ? new Class[0] : p.as(Class[].class);
338   }
339
340   /**
341    * Returns the set property with the specified name.
342    *
343    * @param key The property name.
344    * @param eType The class type of the elements in the property.
345    * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or an empty set if it doesn't exist.
346    */
347   public <T> Set<T> getSetProperty(String key, Class<T> eType) {
348      Property p = findProperty(key);
349      return p == null ? Collections.EMPTY_SET : p.asSet(eType);
350   }
351
352   /**
353    * Returns the set property with the specified name.
354    *
355    * @param key The property name.
356    * @param eType The class type of the elements in the property.
357    * @param def The default value if the property doesn't exist or is empty.
358    * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or the default value if it doesn't exist or is empty.
359    */
360   public <T> Set<T> getSetProperty(String key, Class<T> eType, Set<T> def) {
361      Set<T> l = getSetProperty(key, eType);
362      return (l.isEmpty() ? def : l);
363   }
364
365   /**
366    * Returns the class set property with the specified name.
367    *
368    * @param key The property name.
369    * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or an empty set if it doesn't exist.
370    */
371   public Set<Class<?>> getClassSetProperty(String key) {
372      Property p = findProperty(key);
373      return p == null ? Collections.EMPTY_SET : p.asSet(Class.class);
374   }
375
376   /**
377    * Returns the class set property with the specified name.
378    *
379    * @param key The property name.
380    * @param eType The class type of the elements in the property.
381    * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or an empty set if it doesn't exist.
382    */
383   public <T> Set<Class<T>> getClassSetProperty(String key, Class<T> eType) {
384      Property p = findProperty(key);
385      return p == null ? Collections.EMPTY_SET : p.asSet(Class.class);
386   }
387
388   /**
389    * Returns the list property with the specified name.
390    *
391    * @param key The property name.
392    * @param eType The class type of the elements in the property.
393    * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist.
394    */
395   public <T> List<T> getListProperty(String key, Class<T> eType) {
396      Property p = findProperty(key);
397      return p == null ? Collections.EMPTY_LIST : p.asList(eType);
398   }
399
400   /**
401    * Returns the list property with the specified name.
402    *
403    * @param key The property name.
404    * @param eType The class type of the elements in the property.
405    * @param def The default value if the property doesn't exist or is empty.
406    * @return The property value as an unmodifiable <code>ArrayList</code>, or the default value if it doesn't exist or is empty.
407    */
408   public <T> List<T> getListProperty(String key, Class<T> eType, List<T> def) {
409      List<T> l = getListProperty(key, eType);
410      return (l.isEmpty() ? def : l);
411   }
412
413   /**
414    * Returns the class list property with the specified name.
415    *
416    * @param key The property name.
417    * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist.
418    */
419   public List<Class<?>> getClassListProperty(String key) {
420      Property p = findProperty(key);
421      return p == null ? Collections.EMPTY_LIST : p.asList(Class.class);
422   }
423
424   /**
425    * Returns the class list property with the specified name.
426    *
427    * @param key The property name.
428    * @param eType The class type of the elements in the property.
429    * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist.
430    */
431   public <T> List<Class<T>> getClassListProperty(String key, Class<T> eType) {
432      Property p = findProperty(key);
433      return p == null ? Collections.EMPTY_LIST : p.asList(Class.class);
434   }
435
436   /**
437    * Returns the map property with the specified name.
438    *
439    * @param key The property name.
440    * @param eType The class type of the elements in the property.
441    * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist.
442    */
443   public <T> Map<String,T> getMapProperty(String key, Class<T> eType) {
444      Property p = findProperty(key);
445      return p == null ? Collections.EMPTY_MAP : p.asMap(eType);
446   }
447
448   /**
449    * Returns the class map property with the specified name.
450    *
451    * @param key The property name.
452    * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist.
453    */
454   public Map<String,Class<?>> getClassMapProperty(String key) {
455      Property p = findProperty(key);
456      return p == null ? Collections.EMPTY_MAP : p.asMap(Class.class);
457   }
458
459   /**
460    * Returns the class map property with the specified name.
461    *
462    * @param key The property name.
463    * @param eType The class type of the elements in the property.
464    * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist.
465    */
466   public <T> Map<String,Class<T>> getClassMapProperty(String key, Class<T> eType) {
467      Property p = findProperty(key);
468      return p == null ? Collections.EMPTY_MAP : p.asMap(Class.class);
469   }
470
471   /**
472    * Returns an instance of the specified class, string, or object property.
473    *
474    * <p>
475    * If instantiating a class, assumes the class has a no-arg constructor.
476    * Otherwise, throws a runtime exception.
477    *
478    * @param key The property name.
479    * @param type The class type of the property.
480    * @param def
481    *    The default value if the property doesn't exist.
482    *    <br>Can either be an instance of <code>T</code>, or a <code>Class&lt;? <jk>extends</jk> T&gt;</code>, or <jk>null</jk>.
483    * @return A new property instance.
484    */
485   public <T> T getInstanceProperty(String key, Class<T> type, Object def) {
486      return getInstanceProperty(key, type, def, false);
487   }
488
489   /**
490    * Returns an instance of the specified class, string, or object property.
491    *
492    * @param key The property name.
493    * @param type The class type of the property.
494    * @param def
495    *    The default value if the property doesn't exist.
496    *    <br>Can either be an instance of <code>T</code>, or a <code>Class&lt;? <jk>extends</jk> T&gt;</code>.
497    * @param fuzzyArgs
498    *    Use fuzzy constructor arg matching.
499    *    <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored.
500    *    <br>No-arg constructors are also used if no other constructors are found.
501    * @param args
502    *    Arguments to pass to the constructor.
503    *    Constructors matching the arguments are always used before no-arg constructors.
504    * @return A new property instance.
505    */
506   public <T> T getInstanceProperty(String key, Class<T> type, Object def, boolean fuzzyArgs, Object...args) {
507      return getInstanceProperty(key, null, type, def, fuzzyArgs, args);
508   }
509
510   /**
511    * Returns an instance of the specified class, string, or object property.
512    *
513    * @param key The property name.
514    * @param outer The outer object if the class we're instantiating is an inner class.
515    * @param type The class type of the property.
516    * @param def
517    *    The default value if the property doesn't exist.
518    *    <br>Can either be an instance of <code>T</code>, or a <code>Class&lt;? <jk>extends</jk> T&gt;</code>.
519    * @param fuzzyArgs
520    *    Use fuzzy constructor arg matching.
521    *    <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored.
522    *    <br>No-arg constructors are also used if no other constructors are found.
523    * @param args
524    *    Arguments to pass to the constructor.
525    *    Constructors matching the arguments are always used before no-arg constructors.
526    * @return A new property instance.
527    */
528   public <T> T getInstanceProperty(String key, Object outer, Class<T> type, Object def, boolean fuzzyArgs, Object...args) {
529      Property p = findProperty(key);
530      if (p != null)
531         return p.asInstance(outer, type, fuzzyArgs, args);
532      if (def == null)
533         return null;
534      if (def instanceof Class)
535         return ClassUtils.newInstance(type, def, fuzzyArgs, args);
536      if (type.isInstance(def))
537         return (T)def;
538      throw new ConfigException("Could not instantiate property ''{0}'' as type ''{1}'' with default value ''{2}''", key, type, def);
539   }
540
541   /**
542    * Returns the specified property as an array of instantiated objects.
543    *
544    * @param key The property name.
545    * @param type The class type of the property.
546    * @param def The default object to return if the property doesn't exist.
547    * @return A new property instance.
548    */
549   public <T> T[] getInstanceArrayProperty(String key, Class<T> type, T[] def) {
550      return getInstanceArrayProperty(key, type, def, false);
551   }
552
553   /**
554    * Returns the specified property as an array of instantiated objects.
555    *
556    * @param key The property name.
557    * @param type The class type of the property.
558    * @param def The default object to return if the property doesn't exist.
559    * @param fuzzyArgs
560    *    Use fuzzy constructor arg matching.
561    *    <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored.
562    *    <br>No-arg constructors are also used if no other constructors are found.
563    * @param args
564    *    Arguments to pass to the constructor.
565    *    Constructors matching the arguments are always used before no-arg constructors.
566    * @return A new property instance.
567    */
568   public <T> T[] getInstanceArrayProperty(String key, Class<T> type, T[] def, boolean fuzzyArgs, Object...args) {
569      return getInstanceArrayProperty(key, null, type, def, fuzzyArgs, args);
570   }
571
572   /**
573    * Returns the specified property as an array of instantiated objects.
574    *
575    * @param key The property name.
576    * @param outer The outer object if the class we're instantiating is an inner class.
577    * @param type The class type of the property.
578    * @param def The default object to return if the property doesn't exist.
579    * @param fuzzyArgs
580    *    Use fuzzy constructor arg matching.
581    *    <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored.
582    *    <br>No-arg constructors are also used if no other constructors are found.
583    * @param args
584    *    Arguments to pass to the constructor.
585    *    Constructors matching the arguments are always used before no-arg constructors.
586    * @return A new property instance.
587    */
588   public <T> T[] getInstanceArrayProperty(String key, Object outer, Class<T> type, T[] def, boolean fuzzyArgs, Object...args) {
589      Property p = findProperty(key);
590      return p == null ? def : p.asInstanceArray(outer, type, fuzzyArgs, args);
591   }
592
593   /**
594    * Returns the keys found in the specified property group.
595    *
596    * <p>
597    * The keys are NOT prefixed with group names.
598    *
599    * @param group The group name.
600    * @return The set of property keys, or an empty set if the group was not found.
601    */
602   public Set<String> getPropertyKeys(String group) {
603      if (group == null)
604         return Collections.EMPTY_SET;
605      PropertyGroup g = groups.get(group);
606      return g == null ? Collections.EMPTY_SET : g.keySet();
607   }
608
609   @Override /* Object */
610   public int hashCode() {
611      return hashCode;
612   }
613
614   /**
615    * Returns a hashcode of this property store using only the specified group names.
616    *
617    * @param groups The names of the property groups to use in the calculation.
618    * @return The hashcode.
619    */
620   public Integer hashCode(String...groups) {
621      HashCode c = new HashCode();
622      for (String p : groups)
623         if (p != null)
624            c.add(p).add(this.groups.get(p));
625      return c.get();
626   }
627
628   @Override /* Object */
629   public boolean equals(Object o) {
630      if (this == o)
631         return true;
632      if (o instanceof PropertyStore)
633         return (this.groups.equals(((PropertyStore)o).groups));
634      return false;
635   }
636
637   /**
638    * Compares two property stores, but only based on the specified group names.
639    *
640    * @param ps The property store to compare to.
641    * @param groups The groups to compare.
642    * @return <jk>true</jk> if the two property stores are equal in the specified groups.
643    */
644   public boolean equals(PropertyStore ps, String...groups) {
645      if (this == ps)
646         return true;
647      for (String p : groups) {
648         if (p != null) {
649            PropertyGroup pg1 = this.groups.get(p), pg2 = ps.groups.get(p);
650            if (pg1 == null && pg2 == null)
651               continue;
652            if (pg1 == null || pg2 == null)
653               return false;
654            if (! pg1.equals(pg2))
655               return false;
656         }
657      }
658      return true;
659   }
660
661   /**
662    * Used for debugging.
663    *
664    * <p>
665    * Allows property stores to be serialized to easy-to-read JSON objects.
666    *
667    * @param beanSession The bean session.
668    * @return The property groups.
669    */
670   public Map<String,PropertyGroup> swap(BeanSession beanSession) {
671      return groups;
672   }
673
674   //-------------------------------------------------------------------------------------------------------------------
675   // PropertyGroup
676   //-------------------------------------------------------------------------------------------------------------------
677
678   static class PropertyGroup {
679      final SortedMap<String,Property> properties;
680      private final int hashCode;
681
682      PropertyGroup(Map<String,MutableProperty> properties) {
683         TreeMap<String,Property> m = new TreeMap<>();
684         for (Map.Entry<String,MutableProperty> p : properties.entrySet())
685            m.put(p.getKey(), p.getValue().build());
686         this.properties = Collections.unmodifiableSortedMap(m);
687         this.hashCode = this.properties.hashCode();
688      }
689
690      PropertyGroupBuilder builder() {
691         return new PropertyGroupBuilder(properties);
692      }
693
694      Property get(String key) {
695         return properties.get(key);
696      }
697
698      @Override /* Object */
699      public int hashCode() {
700         return hashCode;
701      }
702
703      @Override /* Object */
704      public boolean equals(Object o) {
705         if (o instanceof PropertyGroup)
706            return properties.equals(((PropertyGroup)o).properties);
707         return false;
708      }
709
710      Set<String> keySet() {
711         return properties.keySet();
712      }
713
714      public Map<String,Property> swap(BeanSession beanSession) {
715         return properties;
716      }
717   }
718
719   //-------------------------------------------------------------------------------------------------------------------
720   // Property
721   //-------------------------------------------------------------------------------------------------------------------
722
723   static class Property {
724      private final String name;
725      final Object value;
726      private final int hashCode;
727      private final PropertyType type;
728
729      Property(String name, Object value, PropertyType type) {
730         this.name = name;
731         this.value = value;
732         this.type = type;
733         this.hashCode = value.hashCode();
734      }
735
736      MutableProperty mutable() {
737         switch(type) {
738            case STRING:
739            case BOOLEAN:
740            case INTEGER:
741            case CLASS:
742            case OBJECT: return new MutableSimpleProperty(name, type, value);
743            case SET_STRING:
744            case SET_INTEGER:
745            case SET_CLASS: return new MutableSetProperty(name, type, value);
746            case LIST_STRING:
747            case LIST_INTEGER:
748            case LIST_CLASS:
749            case LIST_OBJECT: return new MutableListProperty(name, type, value);
750            case SORTED_MAP_STRING:
751            case SORTED_MAP_INTEGER:
752            case SORTED_MAP_CLASS:
753            case SORTED_MAP_OBJECT: return new MutableMapProperty(name, type, value);
754            case ORDERED_MAP_STRING:
755            case ORDERED_MAP_INTEGER:
756            case ORDERED_MAP_CLASS:
757            case ORDERED_MAP_OBJECT: return new MutableLinkedMapProperty(name, type, value);
758         }
759         throw new ConfigException("Invalid type specified: ''{0}''", type);
760      }
761
762      public <T> T as(Class<T> c) {
763         Class<?> c2 = ClassUtils.getPrimitiveWrapper(c);
764         if (c2 != null)
765            c = (Class<T>)c2;
766         if (c.isInstance(value))
767            return (T)value;
768         if (c.isArray() && value instanceof Collection)
769            return (T)asArray(c.getComponentType());
770         if (type == STRING) {
771            T t = ClassUtils.fromString(c, value.toString());
772            if (t != null)
773               return t;
774         }
775         throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}'' on property ''{2}''", type, c, name);
776      }
777
778      public <T> T[] asArray(Class<T> eType) {
779         if (value instanceof Collection) {
780            Collection<?> l = (Collection<?>)value;
781            Object t = Array.newInstance(eType, l.size());
782            int i = 0;
783            for (Object o : l) {
784               Object o2 = null;
785               if (eType.isInstance(o))
786                  o2 = o;
787               else if (type == SET_STRING || type == LIST_STRING) {
788                  o2 = ClassUtils.fromString(eType, o.toString());
789                  if (o2 == null)
790                     throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
791               } else {
792                  throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
793               }
794               Array.set(t, i++, o2);
795            }
796            return (T[])t;
797         }
798         throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
799      }
800
801      public <T> Set<T> asSet(Class<T> eType) {
802         if (type == SET_STRING && eType == String.class || type == SET_INTEGER && eType == Integer.class || type == SET_CLASS && eType == Class.class) {
803            return (Set<T>)value;
804         } else if (type == SET_STRING) {
805            Set<T> s = new LinkedHashSet<>();
806            for (Object o : (Set<?>)value) {
807               T t = ClassUtils.fromString(eType, o.toString());
808               if (t == null)
809                  throw new ConfigException("Invalid property conversion ''{0}'' to ''Set<{1}>'' on property ''{2}''", type, eType, name);
810               s.add(t);
811            }
812            return Collections.unmodifiableSet(s);
813         } else {
814            throw new ConfigException("Invalid property conversion ''{0}'' to ''Set<{1}>'' on property ''{2}''", type, eType, name);
815         }
816      }
817
818      public <T> List<T> asList(Class<T> eType) {
819         if (type == LIST_STRING && eType == String.class || type == LIST_INTEGER && eType == Integer.class || type == LIST_CLASS && eType == Class.class || type == LIST_OBJECT) {
820            return (List<T>)value;
821         } else if (type == PropertyType.LIST_STRING) {
822            List<T> l = new ArrayList<>();
823            for (Object o : (List<?>)value) {
824               T t = ClassUtils.fromString(eType, o.toString());
825               if (t == null)
826                  throw new ConfigException("Invalid property conversion ''{0}'' to ''List<{1}>'' on property ''{2}''", type, eType, name);
827               l.add(t);
828            }
829            return unmodifiableList(l);
830         } else {
831            throw new ConfigException("Invalid property conversion ''{0}'' to ''List<{1}>'' on property ''{2}''", type, eType, name);
832         }
833      }
834
835      public <T> Map<String,T> asMap(Class<T> eType) {
836         if (
837            eType == String.class && (type == SORTED_MAP_STRING || type == ORDERED_MAP_STRING)
838            || eType == Integer.class && (type == SORTED_MAP_INTEGER || type == ORDERED_MAP_INTEGER)
839            || eType == Class.class && (type == SORTED_MAP_CLASS || type == ORDERED_MAP_CLASS)
840            || (type == SORTED_MAP_OBJECT || type == ORDERED_MAP_OBJECT)) {
841            return (Map<String,T>)value;
842         } else if (type == SORTED_MAP_STRING || type == ORDERED_MAP_STRING) {
843            Map<String,T> m = new LinkedHashMap<>();
844            for (Map.Entry<String,String> e : ((Map<String,String>)value).entrySet()) {
845               T t = ClassUtils.fromString(eType, e.getValue());
846               if (t == null)
847                  throw new ConfigException("Invalid property conversion ''{0}'' to ''Map<String,{1}>'' on property ''{2}''", type, eType, name);
848               m.put(e.getKey(), t);
849            }
850            return unmodifiableMap(m);
851         } else {
852            throw new ConfigException("Invalid property conversion ''{0}'' to ''Map<String,{1}>'' on property ''{2}''", type, eType, name);
853         }
854      }
855
856      public <T> T asInstance(Object outer, Class<T> iType, boolean fuzzyArgs, Object...args) {
857         if (type == STRING)
858            return ClassUtils.fromString(iType, value.toString());
859         else if (type == OBJECT || type == CLASS)
860            return ClassUtils.newInstanceFromOuter(outer, iType, value, fuzzyArgs, args);
861         throw new ConfigException("Invalid property instantiation ''{0}'' to ''{1}'' on property ''{2}''", type, iType, name);
862      }
863
864      public <T> T[] asInstanceArray(Object outer, Class<T> eType, boolean fuzzyArgs, Object...args) {
865         if (value instanceof Collection) {
866            Collection<?> l = (Collection<?>)value;
867            Object t = Array.newInstance(eType, l.size());
868            int i = 0;
869            for (Object o : l) {
870               Object o2 = null;
871               if (eType.isInstance(o))
872                  o2 = o;
873               else if (type == SET_STRING || type == LIST_STRING)
874                  o2 = ClassUtils.fromString(eType, o.toString());
875               else if (type == SET_CLASS || type == LIST_CLASS || type == LIST_OBJECT)
876                  o2 = ClassUtils.newInstanceFromOuter(outer, eType, o, fuzzyArgs, args);
877               if (o2 == null)
878                  throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
879               Array.set(t, i++, o2);
880            }
881            return (T[])t;
882         }
883         throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
884      }
885
886      @Override /* Object */
887      public int hashCode() {
888         return hashCode;
889      }
890
891      @Override /* Object */
892      public boolean equals(Object o) {
893         if (o instanceof Property)
894            return ((Property)o).value.equals(value);
895         return false;
896      }
897
898      public Object swap(BeanSession beanSession) {
899         return value;
900      }
901   }
902
903   //-------------------------------------------------------------------------------------------------------------------
904   // Utility methods
905   //-------------------------------------------------------------------------------------------------------------------
906
907   private static String group(String key) {
908      if (key == null || key.indexOf('.') == -1 || key.charAt(key.length()-1) == '.')
909         throw new ConfigException("Invalid property name specified: ''{0}''", key);
910      String g = key.substring(0, key.indexOf('.'));
911      if (g.isEmpty())
912         throw new ConfigException("Invalid property name specified: ''{0}''", key);
913      return g;
914   }
915
916   @Override /* Object */
917   public String toString() {
918      return SimpleJsonSerializer.DEFAULT.toString(this);
919   }
920}