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   @SuppressWarnings("rawtypes")
372   public Set<Class<?>> getClassSetProperty(String key) {
373      Property p = findProperty(key);
374      return p == null ? Collections.EMPTY_SET : (Set)p.asSet(Class.class);
375   }
376
377   /**
378    * Returns the class set property with the specified name.
379    *
380    * @param key The property name.
381    * @param eType The class type of the elements in the property.
382    * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or an empty set if it doesn't exist.
383    */
384   @SuppressWarnings("rawtypes")
385   public <T> Set<Class<T>> getClassSetProperty(String key, Class<T> eType) {
386      Property p = findProperty(key);
387      return p == null ? Collections.EMPTY_SET : (Set)p.asSet(Class.class);
388   }
389
390   /**
391    * Returns the list property with the specified name.
392    *
393    * @param key The property name.
394    * @param eType The class type of the elements in the property.
395    * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist.
396    */
397   public <T> List<T> getListProperty(String key, Class<T> eType) {
398      Property p = findProperty(key);
399      return p == null ? Collections.EMPTY_LIST : p.asList(eType);
400   }
401
402   /**
403    * Returns the list property with the specified name.
404    *
405    * @param key The property name.
406    * @param eType The class type of the elements in the property.
407    * @param def The default value if the property doesn't exist or is empty.
408    * @return The property value as an unmodifiable <code>ArrayList</code>, or the default value if it doesn't exist or is empty.
409    */
410   public <T> List<T> getListProperty(String key, Class<T> eType, List<T> def) {
411      List<T> l = getListProperty(key, eType);
412      return (l.isEmpty() ? def : l);
413   }
414
415   /**
416    * Returns the class list property with the specified name.
417    *
418    * @param key The property name.
419    * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist.
420    */
421   @SuppressWarnings("rawtypes")
422   public List<Class<?>> getClassListProperty(String key) {
423      Property p = findProperty(key);
424      return p == null ? Collections.EMPTY_LIST : (List)p.asList(Class.class);
425   }
426
427   /**
428    * Returns the class list property with the specified name.
429    *
430    * @param key The property name.
431    * @param eType The class type of the elements in the property.
432    * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist.
433    */
434   @SuppressWarnings("rawtypes")
435   public <T> List<Class<T>> getClassListProperty(String key, Class<T> eType) {
436      Property p = findProperty(key);
437      return p == null ? Collections.EMPTY_LIST : (List)p.asList(Class.class);
438   }
439
440   /**
441    * Returns the map property with the specified name.
442    *
443    * @param key The property name.
444    * @param eType The class type of the elements in the property.
445    * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist.
446    */
447   public <T> Map<String,T> getMapProperty(String key, Class<T> eType) {
448      Property p = findProperty(key);
449      return p == null ? Collections.EMPTY_MAP : p.asMap(eType);
450   }
451
452   /**
453    * Returns the class map property with the specified name.
454    *
455    * @param key The property name.
456    * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist.
457    */
458   @SuppressWarnings("rawtypes")
459   public Map<String,Class<?>> getClassMapProperty(String key) {
460      Property p = findProperty(key);
461      return p == null ? Collections.EMPTY_MAP : (Map)p.asMap(Class.class);
462   }
463
464   /**
465    * Returns the class map property with the specified name.
466    *
467    * @param key The property name.
468    * @param eType The class type of the elements in the property.
469    * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist.
470    */
471   @SuppressWarnings("rawtypes")
472   public <T> Map<String,Class<T>> getClassMapProperty(String key, Class<T> eType) {
473      Property p = findProperty(key);
474      return p == null ? Collections.EMPTY_MAP : (Map)p.asMap(Class.class);
475   }
476
477   /**
478    * Returns an instance of the specified class, string, or object property.
479    *
480    * <p>
481    * If instantiating a class, assumes the class has a no-arg constructor.
482    * Otherwise, throws a runtime exception.
483    *
484    * @param key The property name.
485    * @param type The class type of the property.
486    * @param def
487    *    The default value if the property doesn't exist.
488    *    <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>.
489    * @return A new property instance.
490    */
491   public <T> T getInstanceProperty(String key, Class<T> type, Object def) {
492      return getInstanceProperty(key, type, def, ResourceResolver.BASIC);
493   }
494
495   /**
496    * Returns an instance of the specified class, string, or object property.
497    *
498    * @param key The property name.
499    * @param type The class type of the property.
500    * @param def
501    *    The default value if the property doesn't exist.
502    *    <br>Can either be an instance of <code>T</code>, or a <code>Class&lt;? <jk>extends</jk> T&gt;</code>.
503    * @param resolver
504    *    The resolver to use for instantiating objects.
505    * @param args
506    *    Arguments to pass to the constructor.
507    *    Constructors matching the arguments are always used before no-arg constructors.
508    * @return A new property instance.
509    */
510   public <T> T getInstanceProperty(String key, Class<T> type, Object def, ResourceResolver resolver, Object...args) {
511      return getInstanceProperty(key, null, type, def, resolver, args);
512   }
513
514   /**
515    * Returns an instance of the specified class, string, or object property.
516    *
517    * @param key The property name.
518    * @param outer The outer object if the class we're instantiating is an inner class.
519    * @param type The class type of the property.
520    * @param def
521    *    The default value if the property doesn't exist.
522    *    <br>Can either be an instance of <code>T</code>, or a <code>Class&lt;? <jk>extends</jk> T&gt;</code>.
523    * @param resolver
524    *    The resolver to use for instantiating objects.
525    * @param args
526    *    Arguments to pass to the constructor.
527    *    Constructors matching the arguments are always used before no-arg constructors.
528    * @return A new property instance.
529    */
530   public <T> T getInstanceProperty(String key, Object outer, Class<T> type, Object def, ResourceResolver resolver, Object...args) {
531      Property p = findProperty(key);
532      if (p != null)
533         return p.asInstance(outer, type, resolver, args);
534      if (def == null)
535         return null;
536      if (def instanceof Class)
537         return resolver.resolve(outer, (Class<T>)def, args);
538      if (type.isInstance(def))
539         return (T)def;
540      throw new ConfigException("Could not instantiate property ''{0}'' as type ''{1}'' with default value ''{2}''", key, type, def);
541   }
542
543   /**
544    * Returns the specified property as an array of instantiated objects.
545    *
546    * @param key The property name.
547    * @param type The class type of the property.
548    * @param def The default object to return if the property doesn't exist.
549    * @return A new property instance.
550    */
551   public <T> T[] getInstanceArrayProperty(String key, Class<T> type, T[] def) {
552      return getInstanceArrayProperty(key, type, def, ResourceResolver.BASIC);
553   }
554
555   /**
556    * Returns the specified property as an array of instantiated objects.
557    *
558    * @param key The property name.
559    * @param type The class type of the property.
560    * @param def The default object to return if the property doesn't exist.
561    * @param resolver
562    *    The resolver to use for instantiating objects.
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, ResourceResolver resolver, Object...args) {
569      return getInstanceArrayProperty(key, null, type, def, resolver, 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 resolver
580    *    The resolver to use for instantiating objects.
581    * @param args
582    *    Arguments to pass to the constructor.
583    *    Constructors matching the arguments are always used before no-arg constructors.
584    * @return A new property instance.
585    */
586   public <T> T[] getInstanceArrayProperty(String key, Object outer, Class<T> type, T[] def, ResourceResolver resolver, Object...args) {
587      Property p = findProperty(key);
588      return p == null ? def : p.asInstanceArray(outer, type, resolver, args);
589   }
590
591   /**
592    * Returns the keys found in the specified property group.
593    *
594    * <p>
595    * The keys are NOT prefixed with group names.
596    *
597    * @param group The group name.
598    * @return The set of property keys, or an empty set if the group was not found.
599    */
600   public Set<String> getPropertyKeys(String group) {
601      if (group == null)
602         return Collections.EMPTY_SET;
603      PropertyGroup g = groups.get(group);
604      return g == null ? Collections.EMPTY_SET : g.keySet();
605   }
606
607   @Override /* Object */
608   public int hashCode() {
609      return hashCode;
610   }
611
612   /**
613    * Returns a hashcode of this property store using only the specified group names.
614    *
615    * @param groups The names of the property groups to use in the calculation.
616    * @return The hashcode.
617    */
618   public Integer hashCode(String...groups) {
619      HashCode c = new HashCode();
620      for (String p : groups)
621         if (p != null)
622            c.add(p).add(this.groups.get(p));
623      return c.get();
624   }
625
626   @Override /* Object */
627   public boolean equals(Object o) {
628      if (this == o)
629         return true;
630      if (o instanceof PropertyStore)
631         return (this.groups.equals(((PropertyStore)o).groups));
632      return false;
633   }
634
635   /**
636    * Compares two property stores, but only based on the specified group names.
637    *
638    * @param ps The property store to compare to.
639    * @param groups The groups to compare.
640    * @return <jk>true</jk> if the two property stores are equal in the specified groups.
641    */
642   public boolean equals(PropertyStore ps, String...groups) {
643      if (this == ps)
644         return true;
645      for (String p : groups) {
646         if (p != null) {
647            PropertyGroup pg1 = this.groups.get(p), pg2 = ps.groups.get(p);
648            if (pg1 == null && pg2 == null)
649               continue;
650            if (pg1 == null || pg2 == null)
651               return false;
652            if (! pg1.equals(pg2))
653               return false;
654         }
655      }
656      return true;
657   }
658
659   /**
660    * Used for debugging.
661    *
662    * <p>
663    * Allows property stores to be serialized to easy-to-read JSON objects.
664    *
665    * @param beanSession The bean session.
666    * @return The property groups.
667    */
668   public Map<String,PropertyGroup> swap(BeanSession beanSession) {
669      return groups;
670   }
671
672   //-------------------------------------------------------------------------------------------------------------------
673   // PropertyGroup
674   //-------------------------------------------------------------------------------------------------------------------
675
676   static class PropertyGroup {
677      final SortedMap<String,Property> properties;
678      private final int hashCode;
679
680      PropertyGroup(Map<String,MutableProperty> properties) {
681         TreeMap<String,Property> m = new TreeMap<>();
682         for (Map.Entry<String,MutableProperty> p : properties.entrySet())
683            m.put(p.getKey(), p.getValue().build());
684         this.properties = Collections.unmodifiableSortedMap(m);
685         this.hashCode = this.properties.hashCode();
686      }
687
688      PropertyGroupBuilder builder() {
689         return new PropertyGroupBuilder(properties);
690      }
691
692      Property get(String key) {
693         return properties.get(key);
694      }
695
696      @Override /* Object */
697      public int hashCode() {
698         return hashCode;
699      }
700
701      @Override /* Object */
702      public boolean equals(Object o) {
703         if (o instanceof PropertyGroup)
704            return properties.equals(((PropertyGroup)o).properties);
705         return false;
706      }
707
708      Set<String> keySet() {
709         return properties.keySet();
710      }
711
712      public Map<String,Property> swap(BeanSession beanSession) {
713         return properties;
714      }
715   }
716
717   //-------------------------------------------------------------------------------------------------------------------
718   // Property
719   //-------------------------------------------------------------------------------------------------------------------
720
721   static class Property {
722      private final String name;
723      final Object value;
724      private final int hashCode;
725      private final PropertyType type;
726
727      Property(String name, Object value, PropertyType type) {
728         this.name = name;
729         this.value = value;
730         this.type = type;
731         this.hashCode = value.hashCode();
732      }
733
734      MutableProperty mutable() {
735         switch(type) {
736            case STRING:
737            case BOOLEAN:
738            case INTEGER:
739            case CLASS:
740            case OBJECT: return new MutableSimpleProperty(name, type, value);
741            case SET_STRING:
742            case SET_INTEGER:
743            case SET_CLASS: return new MutableSetProperty(name, type, value);
744            case LIST_STRING:
745            case LIST_INTEGER:
746            case LIST_CLASS:
747            case LIST_OBJECT: return new MutableListProperty(name, type, value);
748            case SORTED_MAP_STRING:
749            case SORTED_MAP_INTEGER:
750            case SORTED_MAP_CLASS:
751            case SORTED_MAP_OBJECT: return new MutableMapProperty(name, type, value);
752            case ORDERED_MAP_STRING:
753            case ORDERED_MAP_INTEGER:
754            case ORDERED_MAP_CLASS:
755            case ORDERED_MAP_OBJECT: return new MutableLinkedMapProperty(name, type, value);
756         }
757         throw new ConfigException("Invalid type specified: ''{0}''", type);
758      }
759
760      public <T> T as(Class<T> c) {
761         Class<?> c2 = ClassUtils.getPrimitiveWrapper(c);
762         if (c2 != null)
763            c = (Class<T>)c2;
764         if (c.isInstance(value))
765            return (T)value;
766         if (c.isArray() && value instanceof Collection)
767            return (T)asArray(c.getComponentType());
768         if (type == STRING) {
769            T t = ClassUtils.fromString(c, value.toString());
770            if (t != null)
771               return t;
772         }
773         throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}'' on property ''{2}''", type, c, name);
774      }
775
776      public <T> T[] asArray(Class<T> eType) {
777         if (value instanceof Collection) {
778            Collection<?> l = (Collection<?>)value;
779            Object t = Array.newInstance(eType, l.size());
780            int i = 0;
781            for (Object o : l) {
782               Object o2 = null;
783               if (eType.isInstance(o))
784                  o2 = o;
785               else if (type == SET_STRING || type == LIST_STRING) {
786                  o2 = ClassUtils.fromString(eType, o.toString());
787                  if (o2 == null)
788                     throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
789               } else {
790                  throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
791               }
792               Array.set(t, i++, o2);
793            }
794            return (T[])t;
795         }
796         throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
797      }
798
799      public <T> Set<T> asSet(Class<T> eType) {
800         if (type == SET_STRING && eType == String.class || type == SET_INTEGER && eType == Integer.class || type == SET_CLASS && eType == Class.class) {
801            return (Set<T>)value;
802         } else if (type == SET_STRING) {
803            Set<T> s = new LinkedHashSet<>();
804            for (Object o : (Set<?>)value) {
805               T t = ClassUtils.fromString(eType, o.toString());
806               if (t == null)
807                  throw new ConfigException("Invalid property conversion ''{0}'' to ''Set<{1}>'' on property ''{2}''", type, eType, name);
808               s.add(t);
809            }
810            return Collections.unmodifiableSet(s);
811         } else {
812            throw new ConfigException("Invalid property conversion ''{0}'' to ''Set<{1}>'' on property ''{2}''", type, eType, name);
813         }
814      }
815
816      public <T> List<T> asList(Class<T> eType) {
817         if (type == LIST_STRING && eType == String.class || type == LIST_INTEGER && eType == Integer.class || type == LIST_CLASS && eType == Class.class || type == LIST_OBJECT) {
818            return (List<T>)value;
819         } else if (type == PropertyType.LIST_STRING) {
820            List<T> l = new ArrayList<>();
821            for (Object o : (List<?>)value) {
822               T t = ClassUtils.fromString(eType, o.toString());
823               if (t == null)
824                  throw new ConfigException("Invalid property conversion ''{0}'' to ''List<{1}>'' on property ''{2}''", type, eType, name);
825               l.add(t);
826            }
827            return unmodifiableList(l);
828         } else {
829            throw new ConfigException("Invalid property conversion ''{0}'' to ''List<{1}>'' on property ''{2}''", type, eType, name);
830         }
831      }
832
833      public <T> Map<String,T> asMap(Class<T> eType) {
834         if (
835            eType == String.class && (type == SORTED_MAP_STRING || type == ORDERED_MAP_STRING)
836            || eType == Integer.class && (type == SORTED_MAP_INTEGER || type == ORDERED_MAP_INTEGER)
837            || eType == Class.class && (type == SORTED_MAP_CLASS || type == ORDERED_MAP_CLASS)
838            || (type == SORTED_MAP_OBJECT || type == ORDERED_MAP_OBJECT)) {
839            return (Map<String,T>)value;
840         } else if (type == SORTED_MAP_STRING || type == ORDERED_MAP_STRING) {
841            Map<String,T> m = new LinkedHashMap<>();
842            for (Map.Entry<String,String> e : ((Map<String,String>)value).entrySet()) {
843               T t = ClassUtils.fromString(eType, e.getValue());
844               if (t == null)
845                  throw new ConfigException("Invalid property conversion ''{0}'' to ''Map<String,{1}>'' on property ''{2}''", type, eType, name);
846               m.put(e.getKey(), t);
847            }
848            return unmodifiableMap(m);
849         } else {
850            throw new ConfigException("Invalid property conversion ''{0}'' to ''Map<String,{1}>'' on property ''{2}''", type, eType, name);
851         }
852      }
853
854      public <T> T asInstance(Object outer, Class<T> iType, ResourceResolver resolver, Object...args) {
855         if (value == null)
856            return null;
857         if (type == STRING)
858            return ClassUtils.fromString(iType, value.toString());
859         else if (type == OBJECT || type == CLASS) {
860            T t = instantiate(resolver, outer, iType, value, args);
861            if (t != null)
862               return t;
863         }
864         throw new ConfigException("Invalid property instantiation ''{0}'' to ''{1}'' on property ''{2}''", type, iType, name);
865      }
866
867      public <T> T[] asInstanceArray(Object outer, Class<T> eType, ResourceResolver resolver, Object...args) {
868         if (value instanceof Collection) {
869            Collection<?> l = (Collection<?>)value;
870            Object t = Array.newInstance(eType, l.size());
871            int i = 0;
872            for (Object o : l) {
873               Object o2 = null;
874               if (eType.isInstance(o))
875                  o2 = o;
876               else if (type == SET_STRING || type == LIST_STRING)
877                  o2 = ClassUtils.fromString(eType, o.toString());
878               else if (type == SET_CLASS || type == LIST_CLASS || type == LIST_OBJECT)
879                  o2 = instantiate(resolver, outer, eType, o, args);
880               if (o2 == null)
881                  throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
882               Array.set(t, i++, o2);
883            }
884            return (T[])t;
885         }
886         throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
887      }
888
889      @Override /* Object */
890      public int hashCode() {
891         return hashCode;
892      }
893
894      @Override /* Object */
895      public boolean equals(Object o) {
896         if (o instanceof Property)
897            return ((Property)o).value.equals(value);
898         return false;
899      }
900
901      public Object swap(BeanSession beanSession) {
902         return value;
903      }
904   }
905
906   //-------------------------------------------------------------------------------------------------------------------
907   // Utility methods
908   //-------------------------------------------------------------------------------------------------------------------
909
910   static <T> T instantiate(ResourceResolver resolver, Object outer, Class<T> c, Object value, Object...args) {
911      if (ClassUtils.isParentClass(c, value.getClass()))
912         return (T)value;
913      if (ClassUtils.isParentClass(Class.class, value.getClass()))
914         return resolver.resolve(outer, (Class<T>)value, args);
915      return null;
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 SimpleJsonSerializer.DEFAULT.toString(this);
930   }
931}