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