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'>
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'>
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'>
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'>
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'>
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'>
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'>
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'>
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 * <h5 class='topic'>Setting properties</h5>
188 * 
189 * <p>
190 * TODO
191 * 
192 * <h5 class='topic'>Retrieving properties</h5>
193 * 
194 * <p>
195 * TODO
196 * 
197 */
198@SuppressWarnings("unchecked")
199public final class PropertyStore {
200   
201   /**
202    * A default empty property store.
203    */
204   public static PropertyStore DEFAULT = PropertyStore.create().build();
205
206   final SortedMap<String,PropertyGroup> groups;
207   private final int hashCode;
208
209   // Created by PropertyStoreBuilder.build()
210   PropertyStore(Map<String,PropertyGroupBuilder> propertyMaps) {
211      TreeMap<String,PropertyGroup> m = new TreeMap<>();
212      for (Map.Entry<String,PropertyGroupBuilder> p : propertyMaps.entrySet()) 
213         m.put(p.getKey(), p.getValue().build());
214      this.groups = Collections.unmodifiableSortedMap(m);
215      this.hashCode = groups.hashCode();
216   }
217   
218   /**
219    * Creates a new empty builder for a property store.
220    * 
221    * @return A new empty builder for a property store.
222    */
223   public static PropertyStoreBuilder create() {
224      return new PropertyStoreBuilder();
225   }
226   
227   /**
228    * Creates a new property store builder initialized with the values in this property store.
229    * 
230    * @return A new property store builder.
231    */
232   public PropertyStoreBuilder builder() {
233      return new PropertyStoreBuilder(this);
234   }
235   
236   private Property findProperty(String key) {
237      String g = group(key);
238      String k = key.substring(g.length()+1);
239      PropertyGroup pm = groups.get(g);
240      
241      if (pm != null) {
242         Property p = pm.get(k);
243         if (p != null)
244            return p;
245      }
246
247      String s = System.getProperty(key);
248      if (s != null)
249         return PropertyStoreBuilder.MutableProperty.create(k, s).build();
250      
251      return null;
252   }
253
254   /**
255    * Returns the raw property value with the specified name.
256    * 
257    * @param key The property name.
258    * @return The property value, or <jk>null</jk> if it doesn't exist.
259    */
260   public Object getProperty(String key) {
261      Property p = findProperty(key);
262      return p == null ? null : p.value;
263   }
264
265   /**
266    * Returns the property value with the specified name.
267    * 
268    * @param key The property name.
269    * @param c The class to cast or convert the value to.
270    * @param def The default value.
271    * @return The property value, or the default value if it doesn't exist.
272    */
273   public <T> T getProperty(String key, Class<T> c, T def) {
274      Property p = findProperty(key);
275      return p == null ? def : p.as(c);
276   }
277
278   /**
279    * Returns the class property with the specified name.
280    * 
281    * @param key The property name.
282    * @param type The class type of the property.
283    * @param def The default value.
284    * @return The property value, or the default value if it doesn't exist.
285    */
286   public <T> Class<? extends T> getClassProperty(String key, Class<T> type, Class<? extends T> def) {
287      Property p = findProperty(key);
288      return p == null ? def : (Class<T>)p.as(Class.class);
289   }
290   
291   /**
292    * Returns the array property value with the specified name.
293    * 
294    * @param key The property name.
295    * @param eType The class type of the elements in the property.
296    * @return The property value, or an empty array if it doesn't exist.
297    */
298   public <T> T[] getArrayProperty(String key, Class<T> eType) {
299      Property p = findProperty(key);
300      return (T[]) (p == null ? Array.newInstance(eType, 0) : p.asArray(eType));
301   }
302   
303   /**
304    * Returns the array property value with the specified name.
305    * 
306    * @param key The property name.
307    * @param eType The class type of the elements in the property.
308    * @param def The default value.
309    * @return The property value, or an empty array if it doesn't exist.
310    */
311   public <T> T[] getArrayProperty(String key, Class<T> eType, T[] def) {
312      Property p = findProperty(key);
313      return p == null ? def : p.asArray(eType);
314   }
315
316   /**
317    * Returns the class array property with the specified name.
318    * 
319    * @param key The property name.
320    * @return The property value, or an empty array if it doesn't exist.
321    */
322   public Class<?>[] getClassArrayProperty(String key) {
323      Property p = findProperty(key);
324      return p == null ? new Class[0] : p.as(Class[].class);
325   }
326
327   /**
328    * Returns the class array property with the specified name.
329    * 
330    * @param key The property name.
331    * @param def The default value.
332    * @return The property value, or an empty array if it doesn't exist.
333    */
334   public Class<?>[] getClassArrayProperty(String key, Class<?>[] def) {
335      Property p = findProperty(key);
336      return p == null ? def : p.as(Class[].class);
337   }
338
339   /**
340    * Returns the class array property with the specified name.
341    * 
342    * @param key The property name.
343    * @param eType The class type of the elements in the property.
344    * @return The property value, or an empty array if it doesn't exist.
345    */
346   public <T> Class<T>[] getClassArrayProperty(String key, Class<T> eType) {
347      Property p = findProperty(key);
348      return p == null ? new Class[0] : p.as(Class[].class);
349   }
350
351   /**
352    * Returns the set property with the specified name.
353    * 
354    * @param key The property name.
355    * @param eType The class type of the elements in the property.
356    * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or an empty set if it doesn't exist.
357    */
358   public <T> Set<T> getSetProperty(String key, Class<T> eType) {
359      Property p = findProperty(key);
360      return p == null ? Collections.EMPTY_SET : p.asSet(eType);
361   }
362   
363   /**
364    * Returns the set property with the specified name.
365    * 
366    * @param key The property name.
367    * @param eType The class type of the elements in the property.
368    * @param def The default value if the property doesn't exist or is empty.
369    * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or the default value if it doesn't exist or is empty.
370    */
371   public <T> Set<T> getSetProperty(String key, Class<T> eType, Set<T> def) {
372      Set<T> l = getSetProperty(key, eType);
373      return (l.isEmpty() ? def : l);
374   }
375
376   /**
377    * Returns the class set property with the specified name.
378    * 
379    * @param key The property name.
380    * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or an empty set if it doesn't exist.
381    */
382   public Set<Class<?>> getClassSetProperty(String key) {
383      Property p = findProperty(key);
384      return p == null ? Collections.EMPTY_SET : p.asSet(Class.class);
385   }
386
387   /**
388    * Returns the class set property with the specified name.
389    * 
390    * @param key The property name.
391    * @param eType The class type of the elements in the property.
392    * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or an empty set if it doesn't exist.
393    */
394   public <T> Set<Class<T>> getClassSetProperty(String key, Class<T> eType) {
395      Property p = findProperty(key);
396      return p == null ? Collections.EMPTY_SET : p.asSet(Class.class);
397   }
398
399   /**
400    * Returns the list property with the specified name.
401    * 
402    * @param key The property name.
403    * @param eType The class type of the elements in the property.
404    * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist.
405    */
406   public <T> List<T> getListProperty(String key, Class<T> eType) {
407      Property p = findProperty(key);
408      return p == null ? Collections.EMPTY_LIST : p.asList(eType);
409   }
410   
411   /**
412    * Returns the list property with the specified name.
413    * 
414    * @param key The property name.
415    * @param eType The class type of the elements in the property.
416    * @param def The default value if the property doesn't exist or is empty.
417    * @return The property value as an unmodifiable <code>ArrayList</code>, or the default value if it doesn't exist or is empty.
418    */
419   public <T> List<T> getListProperty(String key, Class<T> eType, List<T> def) {
420      List<T> l = getListProperty(key, eType);
421      return (l.isEmpty() ? def : l);
422   }
423
424   /**
425    * Returns the class list property with the specified name.
426    * 
427    * @param key The property name.
428    * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist.
429    */
430   public List<Class<?>> getClassListProperty(String key) {
431      Property p = findProperty(key);
432      return p == null ? Collections.EMPTY_LIST : p.asList(Class.class);
433   }
434
435   /**
436    * Returns the class list property with the specified name.
437    * 
438    * @param key The property name.
439    * @param eType The class type of the elements in the property.
440    * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist.
441    */
442   public <T> List<Class<T>> getClassListProperty(String key, Class<T> eType) {
443      Property p = findProperty(key);
444      return p == null ? Collections.EMPTY_LIST : p.asList(Class.class);
445   }
446
447   /**
448    * Returns the map property with the specified name.
449    * 
450    * @param key The property name.
451    * @param eType The class type of the elements in the property.
452    * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist.
453    */
454   public <T> Map<String,T> getMapProperty(String key, Class<T> eType) {
455      Property p = findProperty(key);
456      return p == null ? Collections.EMPTY_MAP : p.asMap(eType);
457   }
458   
459   /**
460    * Returns the class map property with the specified name.
461    * 
462    * @param key The property name.
463    * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist.
464    */
465   public Map<String,Class<?>> getClassMapProperty(String key) {
466      Property p = findProperty(key);
467      return p == null ? Collections.EMPTY_MAP : p.asMap(Class.class);
468   }
469
470   /**
471    * Returns the class map property with the specified name.
472    * 
473    * @param key The property name.
474    * @param eType The class type of the elements in the property.
475    * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist.
476    */
477   public <T> Map<String,Class<T>> getClassMapProperty(String key, Class<T> eType) {
478      Property p = findProperty(key);
479      return p == null ? Collections.EMPTY_MAP : p.asMap(Class.class);
480   }
481
482   /**
483    * Returns an instance of the specified class, string, or object property.
484    * 
485    * <p>
486    * If instantiating a class, assumes the class has a no-arg constructor.
487    * Otherwise, throws a runtime exception.
488    * 
489    * @param key The property name.
490    * @param type The class type of the property.
491    * @param def 
492    *    The default value if the property doesn't exist.
493    *    <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>.
494    * @return A new property instance.
495    */
496   public <T> T getInstanceProperty(String key, Class<T> type, Object def) {
497      return getInstanceProperty(key, type, def, false);
498   }
499   
500   /**
501    * Returns an instance of the specified class, string, or object property.
502    * 
503    * @param key The property name.
504    * @param type The class type of the property.
505    * @param def 
506    *    The default value if the property doesn't exist.
507    *    <br>Can either be an instance of <code>T</code>, or a <code>Class&lt;? <jk>extends</jk> T&gt;</code>.
508    * @param fuzzyArgs 
509    *    Use fuzzy constructor arg matching.  
510    *    <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored.
511    *    <br>No-arg constructors are also used if no other constructors are found.
512    * @param args 
513    *    Arguments to pass to the constructor.
514    *    Constructors matching the arguments are always used before no-arg constructors.
515    * @return A new property instance.
516    */
517   public <T> T getInstanceProperty(String key, Class<T> type, Object def, boolean fuzzyArgs, Object...args) {
518      return getInstanceProperty(key, null, type, def, fuzzyArgs, args);
519   }
520
521   /**
522    * Returns an instance of the specified class, string, or object property.
523    * 
524    * @param key The property name.
525    * @param outer The outer object if the class we're instantiating is an inner class.
526    * @param type The class type of the property.
527    * @param def 
528    *    The default value if the property doesn't exist.
529    *    <br>Can either be an instance of <code>T</code>, or a <code>Class&lt;? <jk>extends</jk> T&gt;</code>.
530    * @param fuzzyArgs 
531    *    Use fuzzy constructor arg matching.  
532    *    <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored.
533    *    <br>No-arg constructors are also used if no other constructors are found.
534    * @param args 
535    *    Arguments to pass to the constructor.
536    *    Constructors matching the arguments are always used before no-arg constructors.
537    * @return A new property instance.
538    */
539   public <T> T getInstanceProperty(String key, Object outer, Class<T> type, Object def, boolean fuzzyArgs, Object...args) {
540      Property p = findProperty(key);
541      if (p != null)
542         return p.asInstance(outer, type, fuzzyArgs, args);
543      if (def == null)
544         return null;
545      if (def instanceof Class) 
546         return ClassUtils.newInstance(type, def, fuzzyArgs, args);
547      if (type.isInstance(def))
548         return (T)def;
549      throw new ConfigException("Could not instantiate property ''{0}'' as type ''{1}'' with default value ''{2}''", key, type, def);
550   }
551
552   /**
553    * Returns the specified property as an array of instantiated objects.
554    * 
555    * @param key The property name.
556    * @param type The class type of the property.
557    * @param def The default object to return if the property doesn't exist.
558    * @return A new property instance.
559    */
560   public <T> T[] getInstanceArrayProperty(String key, Class<T> type, T[] def) {
561      return getInstanceArrayProperty(key, type, def, false);
562   }
563
564   /**
565    * Returns the specified property as an array of instantiated objects.
566    * 
567    * @param key The property name.
568    * @param type The class type of the property.
569    * @param def The default object to return if the property doesn't exist.
570    * @param fuzzyArgs 
571    *    Use fuzzy constructor arg matching.  
572    *    <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored.
573    *    <br>No-arg constructors are also used if no other constructors are found.
574    * @param args 
575    *    Arguments to pass to the constructor.
576    *    Constructors matching the arguments are always used before no-arg constructors.
577    * @return A new property instance.
578    */
579   public <T> T[] getInstanceArrayProperty(String key, Class<T> type, T[] def, boolean fuzzyArgs, Object...args) {
580      return getInstanceArrayProperty(key, null, type, def, fuzzyArgs, args);
581   }
582
583   /**
584    * Returns the specified property as an array of instantiated objects.
585    * 
586    * @param key The property name.
587    * @param outer The outer object if the class we're instantiating is an inner class.
588    * @param type The class type of the property.
589    * @param def The default object to return if the property doesn't exist.
590    * @param fuzzyArgs 
591    *    Use fuzzy constructor arg matching.  
592    *    <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored.
593    *    <br>No-arg constructors are also used if no other constructors are found.
594    * @param args 
595    *    Arguments to pass to the constructor.
596    *    Constructors matching the arguments are always used before no-arg constructors.
597    * @return A new property instance.
598    */
599   public <T> T[] getInstanceArrayProperty(String key, Object outer, Class<T> type, T[] def, boolean fuzzyArgs, Object...args) {
600      Property p = findProperty(key);
601      return p == null ? def : p.asInstanceArray(outer, type, fuzzyArgs, args);
602   }
603
604   /**
605    * Returns the keys found in the specified property group.
606    * 
607    * <p>
608    * The keys are NOT prefixed with group names.
609    * 
610    * @param group The group name.
611    * @return The set of property keys, or an empty set if the group was not found. 
612    */
613   public Set<String> getPropertyKeys(String group) {
614      if (group == null)
615         return Collections.EMPTY_SET;
616      PropertyGroup g = groups.get(group);
617      return g == null ? Collections.EMPTY_SET : g.keySet();
618   }
619
620   @Override /* Object */
621   public int hashCode() {
622      return hashCode;
623   }
624   
625   /**
626    * Returns a hashcode of this property store using only the specified group names.
627    * 
628    * @param groups The names of the property groups to use in the calculation.
629    * @return The hashcode.
630    */
631   public Integer hashCode(String...groups) {
632      HashCode c = new HashCode();
633      for (String p : groups)
634         if (p != null)
635            c.add(p).add(this.groups.get(p));
636      return c.get();
637   }
638
639   @Override /* Object */
640   public boolean equals(Object o) {
641      if (this == o)
642         return true;
643      if (o instanceof PropertyStore) 
644         return (this.groups.equals(((PropertyStore)o).groups));
645      return false;
646   }
647
648   /**
649    * Compares two property stores, but only based on the specified group names.
650    * 
651    * @param ps The property store to compare to.
652    * @param groups The groups to compare.
653    * @return <jk>true</jk> if the two property stores are equal in the specified groups.
654    */
655   public boolean equals(PropertyStore ps, String...groups) {
656      if (this == ps)
657         return true;
658      for (String p : groups) {
659         if (p != null) {
660            PropertyGroup pg1 = this.groups.get(p), pg2 = ps.groups.get(p);
661            if (pg1 == null && pg2 == null)
662               continue;
663            if (pg1 == null || pg2 == null)
664               return false;
665            if (! pg1.equals(pg2))
666               return false;
667         }
668      }
669      return true;
670   }
671   
672   /**
673    * Used for debugging.
674    * 
675    * <p>
676    * Allows property stores to be serialized to easy-to-read JSON objects.
677    * 
678    * @param beanSession The bean session.
679    * @return The property groups.
680    */
681   public Map<String,PropertyGroup> swap(BeanSession beanSession) {
682      return groups;
683   }
684   
685   //-------------------------------------------------------------------------------------------------------------------
686   // PropertyGroup
687   //-------------------------------------------------------------------------------------------------------------------
688   
689   static class PropertyGroup {
690      final SortedMap<String,Property> properties;
691      private final int hashCode;
692      
693      PropertyGroup(Map<String,MutableProperty> properties) {
694         TreeMap<String,Property> m = new TreeMap<>();
695         for (Map.Entry<String,MutableProperty> p : properties.entrySet()) 
696            m.put(p.getKey(), p.getValue().build());
697         this.properties = Collections.unmodifiableSortedMap(m);
698         this.hashCode = this.properties.hashCode();
699      }
700      
701      PropertyGroupBuilder builder() {
702         return new PropertyGroupBuilder(properties);
703      }
704      
705      Property get(String key) {
706         return properties.get(key);
707      }
708
709      @Override /* Object */
710      public int hashCode() {
711         return hashCode;
712      }
713
714      @Override /* Object */
715      public boolean equals(Object o) {
716         if (o instanceof PropertyGroup)
717            return properties.equals(((PropertyGroup)o).properties);
718         return false;
719      }
720
721      Set<String> keySet() {
722         return properties.keySet();
723      }
724
725      public Map<String,Property> swap(BeanSession beanSession) {
726         return properties;
727      }
728   }
729
730   //-------------------------------------------------------------------------------------------------------------------
731   // Property
732   //-------------------------------------------------------------------------------------------------------------------
733
734   static class Property {
735      private final String name;
736      final Object value;
737      private final int hashCode; 
738      private final PropertyType type;
739
740      Property(String name, Object value, PropertyType type) {
741         this.name = name;
742         this.value = value;
743         this.type = type;
744         this.hashCode = value.hashCode();
745      }
746      
747      MutableProperty mutable() {
748         switch(type) {
749            case STRING:  
750            case BOOLEAN:
751            case INTEGER:
752            case CLASS:  
753            case OBJECT: return new MutableSimpleProperty(name, type, value);
754            case SET_STRING:
755            case SET_INTEGER:
756            case SET_CLASS: return new MutableSetProperty(name, type, value);
757            case LIST_STRING:
758            case LIST_INTEGER:
759            case LIST_CLASS: 
760            case LIST_OBJECT: return new MutableListProperty(name, type, value);
761            case SORTED_MAP_STRING: 
762            case SORTED_MAP_INTEGER: 
763            case SORTED_MAP_CLASS: 
764            case SORTED_MAP_OBJECT: return new MutableMapProperty(name, type, value);
765            case ORDERED_MAP_STRING: 
766            case ORDERED_MAP_INTEGER: 
767            case ORDERED_MAP_CLASS: 
768            case ORDERED_MAP_OBJECT: return new MutableLinkedMapProperty(name, type, value);
769         }
770         throw new ConfigException("Invalid type specified: ''{0}''", type);
771      }
772      
773      public <T> T as(Class<T> c) {
774         Class<?> c2 = ClassUtils.getPrimitiveWrapper(c);
775         if (c2 != null)
776            c = (Class<T>)c2;
777         if (c.isInstance(value))
778            return (T)value;
779         if (c.isArray() && value instanceof Collection) 
780            return (T)asArray(c.getComponentType());
781         if (type == STRING) {
782            T t = ClassUtils.fromString(c, value.toString());
783            if (t != null)
784               return t;
785         }
786         throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}'' on property ''{2}''", type, c, name);
787      }
788      
789      public <T> T[] asArray(Class<T> eType) {
790         if (value instanceof Collection) {
791            Collection<?> l = (Collection<?>)value;
792            Object t = Array.newInstance(eType, l.size());
793            int i = 0;
794            for (Object o : l) {
795               Object o2 = null;
796               if (eType.isInstance(o))
797                  o2 = o;
798               else if (type == SET_STRING || type == LIST_STRING) {
799                  o2 = ClassUtils.fromString(eType, o.toString());
800                  if (o2 == null)
801                     throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
802               } else {
803                  throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
804               }
805               Array.set(t, i++, o2);
806            }
807            return (T[])t;
808         }
809         throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
810      }
811      
812      public <T> Set<T> asSet(Class<T> eType) {
813         if (type == SET_STRING && eType == String.class || type == SET_INTEGER && eType == Integer.class || type == SET_CLASS && eType == Class.class) {
814            return (Set<T>)value;
815         } else if (type == SET_STRING) {
816            Set<T> s = new LinkedHashSet<>();
817            for (Object o : (Set<?>)value) {
818               T t = ClassUtils.fromString(eType, o.toString());
819               if (t == null)
820                  throw new ConfigException("Invalid property conversion ''{0}'' to ''Set<{1}>'' on property ''{2}''", type, eType, name);
821               s.add(t);
822            }
823            return Collections.unmodifiableSet(s);
824         } else {
825            throw new ConfigException("Invalid property conversion ''{0}'' to ''Set<{1}>'' on property ''{2}''", type, eType, name);
826         }
827      }
828
829      public <T> List<T> asList(Class<T> eType) {
830         if (type == LIST_STRING && eType == String.class || type == LIST_INTEGER && eType == Integer.class || type == LIST_CLASS && eType == Class.class || type == LIST_OBJECT) {
831            return (List<T>)value;
832         } else if (type == PropertyType.LIST_STRING) {
833            List<T> l = new ArrayList<>();
834            for (Object o : (List<?>)value) {
835               T t = ClassUtils.fromString(eType, o.toString());
836               if (t == null)
837                  throw new ConfigException("Invalid property conversion ''{0}'' to ''List<{1}>'' on property ''{2}''", type, eType, name);
838               l.add(t);
839            }
840            return unmodifiableList(l); 
841         } else {
842            throw new ConfigException("Invalid property conversion ''{0}'' to ''List<{1}>'' on property ''{2}''", type, eType, name);
843         }
844      }
845
846      public <T> Map<String,T> asMap(Class<T> eType) {
847         if (
848            eType == String.class && (type == SORTED_MAP_STRING || type == ORDERED_MAP_STRING) 
849            || eType == Integer.class && (type == SORTED_MAP_INTEGER || type == ORDERED_MAP_INTEGER) 
850            || eType == Class.class && (type == SORTED_MAP_CLASS || type == ORDERED_MAP_CLASS) 
851            || (type == SORTED_MAP_OBJECT || type == ORDERED_MAP_OBJECT)) {
852            return (Map<String,T>)value;
853         } else if (type == SORTED_MAP_STRING || type == ORDERED_MAP_STRING) {
854            Map<String,T> m = new LinkedHashMap<>();
855            for (Map.Entry<String,String> e : ((Map<String,String>)value).entrySet()) {
856               T t = ClassUtils.fromString(eType, e.getValue());
857               if (t == null)
858                  throw new ConfigException("Invalid property conversion ''{0}'' to ''Map<String,{1}>'' on property ''{2}''", type, eType, name);
859               m.put(e.getKey(), t);
860            }
861            return unmodifiableMap(m); 
862         } else {
863            throw new ConfigException("Invalid property conversion ''{0}'' to ''Map<String,{1}>'' on property ''{2}''", type, eType, name);
864         }
865      }
866
867      public <T> T asInstance(Object outer, Class<T> iType, boolean fuzzyArgs, Object...args) {
868         if (type == STRING) 
869            return ClassUtils.fromString(iType, value.toString());
870         else if (type == OBJECT || type == CLASS) 
871            return ClassUtils.newInstanceFromOuter(outer, iType, value, fuzzyArgs, args);
872         throw new ConfigException("Invalid property instantiation ''{0}'' to ''{1}'' on property ''{2}''", type, iType, name);
873      }
874
875      public <T> T[] asInstanceArray(Object outer, Class<T> eType, boolean fuzzyArgs, Object...args) {
876         if (value instanceof Collection) {
877            Collection<?> l = (Collection<?>)value;
878            Object t = Array.newInstance(eType, l.size());
879            int i = 0;
880            for (Object o : l) {
881               Object o2 = null;
882               if (eType.isInstance(o))
883                  o2 = o;
884               else if (type == SET_STRING || type == LIST_STRING) 
885                  o2 = ClassUtils.fromString(eType, o.toString());
886               else if (type == SET_CLASS || type == LIST_CLASS || type == LIST_OBJECT)
887                  o2 = ClassUtils.newInstanceFromOuter(outer, eType, o, fuzzyArgs, args);
888               if (o2 == null)
889                  throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
890               Array.set(t, i++, o2);
891            }
892            return (T[])t;
893         }
894         throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
895      }
896
897      @Override /* Object */
898      public int hashCode() {
899         return hashCode;
900      }
901      
902      @Override /* Object */
903      public boolean equals(Object o) {
904         if (o instanceof Property)
905            return ((Property)o).value.equals(value);
906         return false;
907      }
908      
909      public Object swap(BeanSession beanSession) {
910         return value;
911      }
912   }
913   
914   //-------------------------------------------------------------------------------------------------------------------
915   // Utility methods
916   //-------------------------------------------------------------------------------------------------------------------
917   
918   private static String group(String key) {
919      if (key == null || key.indexOf('.') == -1 || key.charAt(key.length()-1) == '.')
920         throw new ConfigException("Invalid property name specified: ''{0}''", key);
921      String g = key.substring(0, key.indexOf('.'));
922      if (g.isEmpty())
923         throw new ConfigException("Invalid property name specified: ''{0}''", key);
924      return g;
925   }
926
927   @Override /* Object */
928   public String toString() {
929      return JsonSerializer.DEFAULT_LAX.toString(this);
930   }
931}