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.*;
017import static org.apache.juneau.internal.ClassUtils.*;
018
019import java.lang.reflect.*;
020import java.util.*;
021
022import org.apache.juneau.PropertyStoreBuilder.*;
023import org.apache.juneau.internal.*;
024import org.apache.juneau.json.*;
025import org.apache.juneau.reflect.*;
026
027
028/**
029 * Represents an immutable collection of properties.
030 *
031 * <p>
032 * The general idea behind a property store is to serve as a reusable configuration of an artifact (e.g. a Serializer)
033 * such that the artifact can be cached and reused if the property stores are 'equal'.
034 *
035 * <h5 class='topic'>Concept</h5>
036 *
037 * <p>
038 * For example, two serializers of the same type created with the same configuration will always end up being
039 * the same serializer:
040 *
041 * <p class='bcode w800'>
042 *    WriterSerializer s1 = JsonSerializer.<jsm>create</jsm>().pojoSwaps(MySwap.<jk>class</jk>).simple().build();
043 *    WriterSerializer s2 = JsonSerializer.<jsm>create</jsm>().simple().pojoSwaps(MySwap.<jk>class</jk>).build();
044 *    <jk>assert</jk>(s1 == s2);
045 * </p>
046 *
047 * <p>
048 * This has the effect of significantly improving performance, especially if you're creating many Serializers and
049 * Parsers.
050 *
051 * <h5 class='topic'>PropertyStoreBuilder</h5>
052 *
053 * <p>
054 * The {@link PropertyStoreBuilder} class is used to build up and instantiate immutable <c>PropertyStore</c>
055 * objects.
056 *
057 * <p>
058 * In the example above, the property store being built looks like the following:
059 *
060 * <p class='bcode w800'>
061 *    PropertyStore ps = PropertyStore
062 *       .<jsm>create</jsm>()
063 *       .set(<js>"BeanContext.pojoSwaps.lc"</js>, MySwap.<jk>class</jk>)
064 *       .set(<js>"JsonSerializer.simpleMode.b"</js>, <jk>true</jk>)
065 *       .build();
066 * </p>
067 *
068 * <p>
069 * Property stores are immutable, comparable, and their hashcodes are calculated exactly one time.
070 * That makes them particularly suited for use as hashmap keys, and thus for caching reusable serializers and parsers.
071 *
072 * <h5 class='topic'>Property naming convention</h5>
073 *
074 * <p>
075 * Property names must have the following format...
076 * <p class='bcode w800'>
077 *    <js>"{class}.{name}.{type}"</js>
078 * </p>
079 * <p>
080 * ...where the parts consist of the following...
081 * <ul>
082 *    <li><js>"{class}"</js> - The group name of the property (e.g. <js>"JsonSerializer"</js>).
083 *       <br>It's always going to be the simple class name of the class it's associated with.
084 *    <li><js>"{name}"</js> - The property name (e.g. <js>"useWhitespace"</js>).
085 *    <li><js>"{type}"</js> - The property data type.
086 *       <br>A 1 or 2 character string that identifies the data type of the property.
087 *       <br>Valid values are:
088 *       <ul>
089 *          <li><js>"s"</js>:  <c>String</c>
090 *          <li><js>"b"</js>:  <c>Boolean</c>
091 *          <li><js>"i"</js>:  <c>Integer</c>
092 *          <li><js>"c"</js>:  <c>Class</c>
093 *          <li><js>"o"</js>:  <c>Object</c>
094 *          <li><js>"ss"</js>:  <c>TreeSet&lt;String&gt;</c>
095 *          <li><js>"si"</js>:  <c>TreeSet&lt;Integer&gt;</c>
096 *          <li><js>"sc"</js>:  <c>TreeSet&lt;Class&gt;</c>
097 *          <li><js>"ls"</js>:  <c>Linkedlist&lt;String&gt;</c>
098 *          <li><js>"li"</js>:  <c>Linkedlist&lt;Integer&gt;</c>
099 *          <li><js>"lc"</js>:  <c>Linkedlist&lt;Class&gt;</c>
100 *          <li><js>"lo"</js>:  <c>Linkedlist&lt;Object&gt;</c>
101 *          <li><js>"sms"</js>:  <c>TreeMap&lt;String,String&gt;</c>
102 *          <li><js>"smi"</js>:  <c>TreeMap&lt;String,Integer&gt;</c>
103 *          <li><js>"smc"</js>:  <c>TreeMap&lt;String,Class&gt;</c>
104 *          <li><js>"smo"</js>:  <c>TreeMap&lt;String,Object&gt;</c>
105 *          <li><js>"oms"</js>:  <c>LinkedHashMap&lt;String,String&gt;</c>
106 *          <li><js>"omi"</js>:  <c>LinkedHashMap&lt;String,Integer&gt;</c>
107 *          <li><js>"omc"</js>:  <c>LinkedHashMap&lt;String,Class&gt;</c>
108 *          <li><js>"omo"</js>:  <c>LinkedHashMap&lt;String,Object&gt;</c>
109 *       </ul>
110 * </ul>
111 *
112 * <p>
113 * For example, <js>"BeanContext.pojoSwaps.lc"</js> refers to a property on the <c>BeanContext</c> class
114 * called <c>pojoSwaps</c> that has a data type of <c>List&lt;Class&gt;</c>.
115 *
116 * <h5 class='topic'>Property value normalization</h5>
117 *
118 * <p>
119 * Property values get 'normalized' when they get set.
120 * For example, calling <code>propertyStore.set(<js>"BeanContext.debug.b"</js>, <js>"true"</js>)</code> will cause the property
121 * value to be converted to a boolean.
122 *
123 * <h5 class='topic'>Set types</h5>
124 *
125 * <p>
126 * The <js>"sX"</js> property types are sorted sets.
127 * <br>Use these for collections of objects where the order is not important.
128 * <br>Internally, a <c>TreeSet</c> is used so that the order in which you add elements does not affect the
129 * resulting order of the property.
130 *
131 * <h5 class='topic'>List types</h5>
132 *
133 * <p>
134 * The <js>"lX"</js> property types are ordered lists.
135 * <br>Use these in cases where the order in which entries are added is important.
136 *
137 * <p>
138 * Adding to a list property will cause the new entries to be added to the BEGINNING of the list.
139 * <br>This ensures that the resulting order of the list is in most-to-least importance.
140 *
141 * <p>
142 * For example, multiple calls to <c>pojoSwaps()</c> causes new entries to be added to the beginning of the list
143 * so that previous values can be 'overridden':
144 * <p class='bcode w800'>
145 *    <jc>// Swap order:  [MySwap2.class, MySwap1.class]</jc>
146 *    JsonSerializer.create().pojoSwaps(MySwap1.<jk>class</jk>).pojoSwaps(MySwap2.<jk>class</jk>).build();
147 * </p>
148 *
149 * <p>
150 * Note that the order is different when passing multiple values into the <c>pojoSwaps()</c> method, in which
151 * case the order should be first-match-wins:
152 * <p class='bcode w800'>
153 *    <jc>// Swap order:  [MySwap1.class, MySwap2.class]</jc>
154 *    JsonSerializer.create().pojoSwaps(MySwap1.<jk>class</jk>,MySwap2.<jk>class</jk>).build();
155 * </p>
156 *
157 * <p>
158 * Combined, the results look like this:
159 * <p class='bcode w800'>
160 *    <jc>// Swap order:  [MySwap4.class, MySwap3.class, MySwap1.class, MySwap2.class]</jc>
161 *    JsonSerializer
162 *       .create()
163 *       .pojoSwaps(MySwap1.<jk>class</jk>,MySwap2.<jk>class</jk>)
164 *       .pojoSwaps(MySwap3.<jk>class</jk>)
165 *       .pojoSwaps(MySwap4.<jk>class</jk>)
166 *       .build();
167 * </p>
168 *
169 * <h5 class='topic'>Map types</h5>
170 *
171 * <p>
172 * The <js>"smX"</js> and <js>"omX"</js> are sorted and order maps respectively.
173 *
174 * <h5 class='topic'>Command properties</h5>
175 *
176 * <p>
177 * Set and list properties have the additional convenience 'command' names for adding and removing entries:
178 * <p class='bcode w800'>
179 *    <js>"{class}.{name}.{type}/add"</js>  <jc>// Add a value to the set/list.</jc>
180 *    <js>"{class}.{name}.{type}/remove"</js>  <jc>// Remove a value from the set/list.</jc>
181 * </p>
182 *
183 * <p>
184 * Map properties have the additional convenience property name for adding and removing map entries:
185 * <p class='bcode w800'>
186 *    <js>"{class}.{name}.{type}/add.{key}"</js>  <jc>// Add a map entry (or delete if the value is null).</jc>
187 * </p>
188 */
189@SuppressWarnings("unchecked")
190public final class PropertyStore {
191
192   /**
193    * A default empty property store.
194    */
195   public static PropertyStore DEFAULT = PropertyStore.create().build();
196
197   final SortedMap<String,PropertyGroup> groups;
198   private final int hashCode;
199
200   // Created by PropertyStoreBuilder.build()
201   PropertyStore(Map<String,PropertyGroupBuilder> propertyMaps) {
202      TreeMap<String,PropertyGroup> m = new TreeMap<>();
203      for (Map.Entry<String,PropertyGroupBuilder> p : propertyMaps.entrySet())
204         m.put(p.getKey(), p.getValue().build());
205      this.groups = Collections.unmodifiableSortedMap(m);
206      this.hashCode = groups.hashCode();
207   }
208
209   /**
210    * Creates a new empty builder for a property store.
211    *
212    * @return A new empty builder for a property store.
213    */
214   public static PropertyStoreBuilder create() {
215      return new PropertyStoreBuilder();
216   }
217
218   /**
219    * Creates a new property store builder initialized with the values in this property store.
220    *
221    * @return A new property store builder.
222    */
223   public PropertyStoreBuilder builder() {
224      return new PropertyStoreBuilder(this);
225   }
226
227   private Property findProperty(String key) {
228      String g = group(key);
229      String k = key.substring(g.length()+1);
230      PropertyGroup pm = groups.get(g);
231
232      if (pm != null) {
233         Property p = pm.get(k);
234         if (p != null)
235            return p;
236      }
237
238      String s = System.getProperty(key);
239      if (s != null)
240         return PropertyStoreBuilder.MutableProperty.create(k, s).build();
241
242      return null;
243   }
244
245   /**
246    * Returns the raw property value with the specified name.
247    *
248    * @param key The property name.
249    * @return The property value, or <jk>null</jk> if it doesn't exist.
250    */
251   public Object getProperty(String key) {
252      Property p = findProperty(key);
253      return p == null ? null : p.value;
254   }
255
256   /**
257    * Returns the property value with the specified name.
258    *
259    * @param key The property name.
260    * @param c The class to cast or convert the value to.
261    * @param def The default value.
262    * @return The property value, or the default value if it doesn't exist.
263    */
264   public <T> T getProperty(String key, Class<T> c, T def) {
265      Property p = findProperty(key);
266      return p == null ? def : p.as(c);
267   }
268
269   /**
270    * Returns the class property with the specified name.
271    *
272    * @param key The property name.
273    * @param type The class type of the property.
274    * @param def The default value.
275    * @return The property value, or the default value if it doesn't exist.
276    */
277   public <T> Class<? extends T> getClassProperty(String key, Class<T> type, Class<? extends T> def) {
278      Property p = findProperty(key);
279      return p == null ? def : (Class<T>)p.as(Class.class);
280   }
281
282   /**
283    * Returns the array property value with the specified name.
284    *
285    * @param key The property name.
286    * @param eType The class type of the elements in the property.
287    * @return The property value, or an empty array if it doesn't exist.
288    */
289   public <T> T[] getArrayProperty(String key, Class<T> eType) {
290      Property p = findProperty(key);
291      return (T[]) (p == null ? Array.newInstance(eType, 0) : p.asArray(eType));
292   }
293
294   /**
295    * Returns the array property value with the specified name.
296    *
297    * @param key The property name.
298    * @param eType The class type of the elements in the property.
299    * @param def The default value.
300    * @return The property value, or an empty array if it doesn't exist.
301    */
302   public <T> T[] getArrayProperty(String key, Class<T> eType, T[] def) {
303      Property p = findProperty(key);
304      return p == null ? def : p.asArray(eType);
305   }
306
307   /**
308    * Returns the class array property with the specified name.
309    *
310    * @param key The property name.
311    * @return The property value, or an empty array if it doesn't exist.
312    */
313   public Class<?>[] getClassArrayProperty(String key) {
314      Property p = findProperty(key);
315      return p == null ? new Class[0] : p.as(Class[].class);
316   }
317
318   /**
319    * Returns the class array property with the specified name.
320    *
321    * @param key The property name.
322    * @param def The default value.
323    * @return The property value, or an empty array if it doesn't exist.
324    */
325   public Class<?>[] getClassArrayProperty(String key, Class<?>[] def) {
326      Property p = findProperty(key);
327      return p == null ? def : p.as(Class[].class);
328   }
329
330   /**
331    * Returns the class array property with the specified name.
332    *
333    * @param key The property name.
334    * @param eType The class type of the elements in the property.
335    * @return The property value, or an empty array if it doesn't exist.
336    */
337   public <T> Class<T>[] getClassArrayProperty(String key, Class<T> eType) {
338      Property p = findProperty(key);
339      return p == null ? new Class[0] : p.as(Class[].class);
340   }
341
342   /**
343    * Returns the set property with the specified name.
344    *
345    * @param key The property name.
346    * @param eType The class type of the elements in the property.
347    * @return The property value as an unmodifiable <c>LinkedHashSet</c>, or an empty set if it doesn't exist.
348    */
349   public <T> Set<T> getSetProperty(String key, Class<T> eType) {
350      Property p = findProperty(key);
351      return p == null ? Collections.EMPTY_SET : p.asSet(eType);
352   }
353
354   /**
355    * Returns the set property with the specified name.
356    *
357    * @param key The property name.
358    * @param eType The class type of the elements in the property.
359    * @param def The default value if the property doesn't exist or is empty.
360    * @return The property value as an unmodifiable <c>LinkedHashSet</c>, or the default value if it doesn't exist or is empty.
361    */
362   public <T> Set<T> getSetProperty(String key, Class<T> eType, Set<T> def) {
363      Set<T> l = getSetProperty(key, eType);
364      return (l.isEmpty() ? def : l);
365   }
366
367   /**
368    * Returns the class set property with the specified name.
369    *
370    * @param key The property name.
371    * @return The property value as an unmodifiable <c>LinkedHashSet</c>, or an empty set if it doesn't exist.
372    */
373   @SuppressWarnings("rawtypes")
374   public Set<Class<?>> getClassSetProperty(String key) {
375      Property p = findProperty(key);
376      return p == null ? Collections.EMPTY_SET : (Set)p.asSet(Class.class);
377   }
378
379   /**
380    * Returns the class set property with the specified name.
381    *
382    * @param key The property name.
383    * @param eType The class type of the elements in the property.
384    * @return The property value as an unmodifiable <c>LinkedHashSet</c>, or an empty set if it doesn't exist.
385    */
386   @SuppressWarnings("rawtypes")
387   public <T> Set<Class<T>> getClassSetProperty(String key, Class<T> eType) {
388      Property p = findProperty(key);
389      return p == null ? Collections.EMPTY_SET : (Set)p.asSet(Class.class);
390   }
391
392   /**
393    * Returns the list property with the specified name.
394    *
395    * @param key The property name.
396    * @param eType The class type of the elements in the property.
397    * @return The property value as an unmodifiable <c>ArrayList</c>, or an empty list if it doesn't exist.
398    */
399   public <T> List<T> getListProperty(String key, Class<T> eType) {
400      Property p = findProperty(key);
401      return p == null ? Collections.EMPTY_LIST : p.asList(eType);
402   }
403
404   /**
405    * Returns the list property with the specified name.
406    *
407    * @param key The property name.
408    * @param eType The class type of the elements in the property.
409    * @param def The default value if the property doesn't exist or is empty.
410    * @return The property value as an unmodifiable <c>ArrayList</c>, or the default value if it doesn't exist or is empty.
411    */
412   public <T> List<T> getListProperty(String key, Class<T> eType, List<T> def) {
413      List<T> l = getListProperty(key, eType);
414      return (l.isEmpty() ? def : l);
415   }
416
417   /**
418    * Returns the class list property with the specified name.
419    *
420    * @param key The property name.
421    * @return The property value as an unmodifiable <c>ArrayList</c>, or an empty list if it doesn't exist.
422    */
423   @SuppressWarnings("rawtypes")
424   public List<Class<?>> getClassListProperty(String key) {
425      Property p = findProperty(key);
426      return p == null ? Collections.EMPTY_LIST : (List)p.asList(Class.class);
427   }
428
429   /**
430    * Returns the class list property with the specified name.
431    *
432    * @param key The property name.
433    * @param eType The class type of the elements in the property.
434    * @return The property value as an unmodifiable <c>ArrayList</c>, or an empty list if it doesn't exist.
435    */
436   @SuppressWarnings("rawtypes")
437   public <T> List<Class<T>> getClassListProperty(String key, Class<T> eType) {
438      Property p = findProperty(key);
439      return p == null ? Collections.EMPTY_LIST : (List)p.asList(Class.class);
440   }
441
442   /**
443    * Returns the map property with the specified name.
444    *
445    * @param key The property name.
446    * @param eType The class type of the elements in the property.
447    * @return The property value as an unmodifiable <c>LinkedHashMap</c>, or an empty map if it doesn't exist.
448    */
449   public <T> Map<String,T> getMapProperty(String key, Class<T> eType) {
450      Property p = findProperty(key);
451      return p == null ? Collections.EMPTY_MAP : p.asMap(eType);
452   }
453
454   /**
455    * Returns the class map property with the specified name.
456    *
457    * @param key The property name.
458    * @return The property value as an unmodifiable <c>LinkedHashMap</c>, or an empty map if it doesn't exist.
459    */
460   @SuppressWarnings("rawtypes")
461   public Map<String,Class<?>> getClassMapProperty(String key) {
462      Property p = findProperty(key);
463      return p == null ? Collections.EMPTY_MAP : (Map)p.asMap(Class.class);
464   }
465
466   /**
467    * Returns the class map property with the specified name.
468    *
469    * @param key The property name.
470    * @param eType The class type of the elements in the property.
471    * @return The property value as an unmodifiable <c>LinkedHashMap</c>, or an empty map if it doesn't exist.
472    */
473   @SuppressWarnings("rawtypes")
474   public <T> Map<String,Class<T>> getClassMapProperty(String key, Class<T> eType) {
475      Property p = findProperty(key);
476      return p == null ? Collections.EMPTY_MAP : (Map)p.asMap(Class.class);
477   }
478
479   /**
480    * Returns an instance of the specified class, string, or object property.
481    *
482    * <p>
483    * If instantiating a class, assumes the class has a no-arg constructor.
484    * Otherwise, throws a runtime exception.
485    *
486    * @param key The property name.
487    * @param type The class type of the property.
488    * @param def
489    *    The default value if the property doesn't exist.
490    *    <br>Can either be an instance of <c>T</c>, or a <code>Class&lt;? <jk>extends</jk> T&gt;</code>, or <jk>null</jk>.
491    * @return A new property instance.
492    */
493   public <T> T getInstanceProperty(String key, Class<T> type, Object def) {
494      return getInstanceProperty(key, type, def, ResourceResolver.BASIC);
495   }
496
497   /**
498    * Returns an instance of the specified class, string, or object property.
499    *
500    * @param key The property name.
501    * @param type The class type of the property.
502    * @param def
503    *    The default value if the property doesn't exist.
504    *    <br>Can either be an instance of <c>T</c>, or a <code>Class&lt;? <jk>extends</jk> T&gt;</code>.
505    * @param resolver
506    *    The resolver to use for instantiating objects.
507    * @param args
508    *    Arguments to pass to the constructor.
509    *    Constructors matching the arguments are always used before no-arg constructors.
510    * @return A new property instance.
511    */
512   public <T> T getInstanceProperty(String key, Class<T> type, Object def, ResourceResolver resolver, Object...args) {
513      return getInstanceProperty(key, null, type, def, resolver, args);
514   }
515
516   /**
517    * Returns an instance of the specified class, string, or object property.
518    *
519    * @param key The property name.
520    * @param outer The outer object if the class we're instantiating is an inner class.
521    * @param type The class type of the property.
522    * @param def
523    *    The default value if the property doesn't exist.
524    *    <br>Can either be an instance of <c>T</c>, or a <code>Class&lt;? <jk>extends</jk> T&gt;</code>.
525    * @param resolver
526    *    The resolver to use for instantiating objects.
527    * @param args
528    *    Arguments to pass to the constructor.
529    *    Constructors matching the arguments are always used before no-arg constructors.
530    * @return A new property instance.
531    */
532   public <T> T getInstanceProperty(String key, Object outer, Class<T> type, Object def, ResourceResolver resolver, Object...args) {
533      Property p = findProperty(key);
534      if (p != null)
535         return p.asInstance(outer, type, resolver, args);
536      if (def == null)
537         return null;
538      if (def instanceof Class)
539         return resolver.resolve(outer, (Class<T>)def, args);
540      if (type.isInstance(def))
541         return (T)def;
542      throw new ConfigException("Could not instantiate property ''{0}'' as type ''{1}'' with default value ''{2}''", key, type, def);
543   }
544
545   /**
546    * Returns the specified property as an array of instantiated objects.
547    *
548    * @param key The property name.
549    * @param type The class type of the property.
550    * @param def The default object to return if the property doesn't exist.
551    * @return A new property instance.
552    */
553   public <T> T[] getInstanceArrayProperty(String key, Class<T> type, T[] def) {
554      return getInstanceArrayProperty(key, type, def, ResourceResolver.BASIC);
555   }
556
557   /**
558    * Returns the specified property as an array of instantiated objects.
559    *
560    * @param key The property name.
561    * @param type The class type of the property.
562    * @param def The default object to return if the property doesn't exist.
563    * @param resolver
564    *    The resolver to use for instantiating objects.
565    * @param args
566    *    Arguments to pass to the constructor.
567    *    Constructors matching the arguments are always used before no-arg constructors.
568    * @return A new property instance.
569    */
570   public <T> T[] getInstanceArrayProperty(String key, Class<T> type, T[] def, ResourceResolver resolver, Object...args) {
571      return getInstanceArrayProperty(key, null, type, def, resolver, args);
572   }
573
574   /**
575    * Returns the specified property as an array of instantiated objects.
576    *
577    * @param key The property name.
578    * @param outer The outer object if the class we're instantiating is an inner class.
579    * @param type The class type of the property.
580    * @param def The default object to return if the property doesn't exist.
581    * @param resolver
582    *    The resolver to use for instantiating objects.
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, ResourceResolver resolver, Object...args) {
589      Property p = findProperty(key);
590      return p == null ? def : p.asInstanceArray(outer, type, resolver, 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   /**
679    * A group of properties with the same prefixes.
680    */
681   public static class PropertyGroup {
682      final SortedMap<String,Property> properties;
683      private final int hashCode;
684
685      PropertyGroup(Map<String,MutableProperty> properties) {
686         TreeMap<String,Property> m = new TreeMap<>();
687         for (Map.Entry<String,MutableProperty> p : properties.entrySet())
688            m.put(p.getKey(), p.getValue().build());
689         this.properties = Collections.unmodifiableSortedMap(m);
690         this.hashCode = this.properties.hashCode();
691      }
692
693      PropertyGroupBuilder builder() {
694         return new PropertyGroupBuilder(properties);
695      }
696
697      Property get(String key) {
698         return properties.get(key);
699      }
700
701      @Override /* Object */
702      public int hashCode() {
703         return hashCode;
704      }
705
706      @Override /* Object */
707      public boolean equals(Object o) {
708         if (o instanceof PropertyGroup)
709            return properties.equals(((PropertyGroup)o).properties);
710         return false;
711      }
712
713      Set<String> keySet() {
714         return properties.keySet();
715      }
716
717      /**
718       * Converts this object to serializable form.
719       *
720       * @return The serializable form of this object.
721       */
722      public Map<String,Property> swap() {
723         return properties;
724      }
725   }
726
727   //-------------------------------------------------------------------------------------------------------------------
728   // Property
729   //-------------------------------------------------------------------------------------------------------------------
730
731   /**
732    * A property in a property store group.
733    */
734   public 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      /**
774       * Converts this property to the specified type.
775       *
776       * @param <T> The type to convert the property to.
777       * @param c The type to convert the property to.
778       * @return The converted type.
779       * @throws ConfigException If value could not be converted.
780       */
781      public <T> T as(Class<T> c) {
782         Class<?> c2 = ClassInfo.of(c).getPrimitiveWrapper();
783         if (c2 != null)
784            c = (Class<T>)c2;
785         if (c.isInstance(value))
786            return (T)value;
787         if (c.isArray() && value instanceof Collection)
788            return (T)asArray(c.getComponentType());
789         if (type == STRING) {
790            T t = fromString(c, value.toString());
791            if (t != null)
792               return t;
793         }
794         throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}'' on property ''{2}''", type, c, name);
795      }
796
797      /**
798       * Converts this property to the specified array type.
799       *
800       * @param <T> The element type to convert the property to.
801       * @param eType The element type to convert the property to.
802       * @return The converted type.
803       * @throws ConfigException If value could not be converted.
804       */
805      public <T> T[] asArray(Class<T> eType) {
806         if (value instanceof Collection) {
807            Collection<?> l = (Collection<?>)value;
808            Object t = Array.newInstance(eType, l.size());
809            int i = 0;
810            for (Object o : l) {
811               Object o2 = null;
812               if (eType.isInstance(o))
813                  o2 = o;
814               else if (type == SET_STRING || type == LIST_STRING) {
815                  o2 = fromString(eType, o.toString());
816                  if (o2 == null)
817                     throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
818               } else {
819                  throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
820               }
821               Array.set(t, i++, o2);
822            }
823            return (T[])t;
824         }
825         throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
826      }
827
828      /**
829       * Converts this property to the specified set type.
830       *
831       * @param <T> The element type to convert the property to.
832       * @param eType The element type to convert the property to.
833       * @return The converted type.
834       * @throws ConfigException If value could not be converted.
835       */
836      public <T> Set<T> asSet(Class<T> eType) {
837         if (type == SET_STRING && eType == String.class || type == SET_INTEGER && eType == Integer.class || type == SET_CLASS && eType == Class.class) {
838            return (Set<T>)value;
839         } else if (type == SET_STRING) {
840            Set<T> s = new LinkedHashSet<>();
841            for (Object o : (Set<?>)value) {
842               T t = fromString(eType, o.toString());
843               if (t == null)
844                  throw new ConfigException("Invalid property conversion ''{0}'' to ''Set<{1}>'' on property ''{2}''", type, eType, name);
845               s.add(t);
846            }
847            return Collections.unmodifiableSet(s);
848         } else {
849            throw new ConfigException("Invalid property conversion ''{0}'' to ''Set<{1}>'' on property ''{2}''", type, eType, name);
850         }
851      }
852
853      /**
854       * Converts this property to the specified list type.
855       *
856       * @param <T> The element type to convert the property to.
857       * @param eType The element type to convert the property to.
858       * @return The converted type.
859       * @throws ConfigException If value could not be converted.
860       */
861      public <T> List<T> asList(Class<T> eType) {
862         if (type == LIST_STRING && eType == String.class || type == LIST_INTEGER && eType == Integer.class || type == LIST_CLASS && eType == Class.class || type == LIST_OBJECT) {
863            return (List<T>)value;
864         } else if (type == PropertyType.LIST_STRING) {
865            List<T> l = new ArrayList<>();
866            for (Object o : (List<?>)value) {
867               T t = fromString(eType, o.toString());
868               if (t == null)
869                  throw new ConfigException("Invalid property conversion ''{0}'' to ''List<{1}>'' on property ''{2}''", type, eType, name);
870               l.add(t);
871            }
872            return unmodifiableList(l);
873         } else {
874            throw new ConfigException("Invalid property conversion ''{0}'' to ''List<{1}>'' on property ''{2}''", type, eType, name);
875         }
876      }
877
878      /**
879       * Converts this property to the specified map type.
880       *
881       * @param <T> The element type to convert the property to.
882       * @param eType The element type to convert the property to.
883       * @return The converted type.
884       * @throws ConfigException If value could not be converted.
885       */
886      public <T> Map<String,T> asMap(Class<T> eType) {
887         if (
888            eType == String.class && (type == SORTED_MAP_STRING || type == ORDERED_MAP_STRING)
889            || eType == Integer.class && (type == SORTED_MAP_INTEGER || type == ORDERED_MAP_INTEGER)
890            || eType == Class.class && (type == SORTED_MAP_CLASS || type == ORDERED_MAP_CLASS)
891            || (type == SORTED_MAP_OBJECT || type == ORDERED_MAP_OBJECT)) {
892            return (Map<String,T>)value;
893         } else if (type == SORTED_MAP_STRING || type == ORDERED_MAP_STRING) {
894            Map<String,T> m = new LinkedHashMap<>();
895            for (Map.Entry<String,String> e : ((Map<String,String>)value).entrySet()) {
896               T t = fromString(eType, e.getValue());
897               if (t == null)
898                  throw new ConfigException("Invalid property conversion ''{0}'' to ''Map<String,{1}>'' on property ''{2}''", type, eType, name);
899               m.put(e.getKey(), t);
900            }
901            return unmodifiableMap(m);
902         } else {
903            throw new ConfigException("Invalid property conversion ''{0}'' to ''Map<String,{1}>'' on property ''{2}''", type, eType, name);
904         }
905      }
906
907      /**
908       * Converts this property to the specified instance type.
909       *
910       * @param outer The outer class if this is a member type.
911       * @param iType The type to instantiate.
912       * @param resolver The resource resolver.
913       * @param args The arguments to pass to the constructor.
914       * @param <T> The type to instantiate.
915       * @return The instantiated object.
916       * @throws ConfigException If value could not be instantiated.
917       */
918      public <T> T asInstance(Object outer, Class<T> iType, ResourceResolver resolver, Object...args) {
919         if (value == null)
920            return null;
921         if (type == STRING)
922            return fromString(iType, value.toString());
923         else if (type == OBJECT || type == CLASS) {
924            T t = instantiate(resolver, outer, iType, value, args);
925            if (t != null)
926               return t;
927         }
928         throw new ConfigException("Invalid property instantiation ''{0}'' to ''{1}'' on property ''{2}''", type, iType, name);
929      }
930
931      /**
932       * Converts this property to an array of specified instance type.
933       *
934       * @param outer The outer class if this is a member type.
935       * @param eType The entry type to instantiate.
936       * @param resolver The resource resolver.
937       * @param args The arguments to pass to the constructor.
938       * @param <T> The type to instantiate.
939       * @return The instantiated object.
940       * @throws ConfigException If value could not be instantiated.
941       */
942      public <T> T[] asInstanceArray(Object outer, Class<T> eType, ResourceResolver resolver, Object...args) {
943         if (value instanceof Collection) {
944            Collection<?> l = (Collection<?>)value;
945            Object t = Array.newInstance(eType, l.size());
946            int i = 0;
947            for (Object o : l) {
948               Object o2 = null;
949               if (eType.isInstance(o))
950                  o2 = o;
951               else if (type == SET_STRING || type == LIST_STRING)
952                  o2 = fromString(eType, o.toString());
953               else if (type == SET_CLASS || type == LIST_CLASS || type == LIST_OBJECT)
954                  o2 = instantiate(resolver, outer, eType, o, args);
955               if (o2 == null)
956                  throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
957               Array.set(t, i++, o2);
958            }
959            return (T[])t;
960         }
961         throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
962      }
963
964      @Override /* Object */
965      public int hashCode() {
966         return hashCode;
967      }
968
969      @Override /* Object */
970      public boolean equals(Object o) {
971         if (o instanceof Property)
972            return ((Property)o).value.equals(value);
973         return false;
974      }
975
976      /**
977       * Converts this object to serializable form.
978       *
979       * @return The serializable form of this object.
980       */
981      public Object swap() {
982         return value;
983      }
984   }
985
986   //-------------------------------------------------------------------------------------------------------------------
987   // Utility methods
988   //-------------------------------------------------------------------------------------------------------------------
989
990   static <T> T instantiate(ResourceResolver resolver, Object outer, Class<T> c, Object value, Object...args) {
991      if (ClassInfo.of(c).isParentOf(value.getClass()))
992         return (T)value;
993      if (ClassInfo.of(value.getClass()).isChildOf(Class.class))
994         return resolver.resolve(outer, (Class<T>)value, args);
995      return null;
996   }
997
998   private static String group(String key) {
999      if (key == null || key.indexOf('.') == -1 || key.charAt(key.length()-1) == '.')
1000         throw new ConfigException("Invalid property name specified: ''{0}''", key);
1001      String g = key.substring(0, key.indexOf('.'));
1002      if (g.isEmpty())
1003         throw new ConfigException("Invalid property name specified: ''{0}''", key);
1004      return g;
1005   }
1006
1007   @Override /* Object */
1008   public String toString() {
1009      return SimpleJsonSerializer.DEFAULT.toString(this);
1010   }
1011}