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