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