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 java.util.Collections.*;
016
017import java.lang.annotation.*;
018import java.lang.reflect.*;
019import java.util.*;
020import java.util.concurrent.*;
021import java.util.regex.*;
022
023import org.apache.juneau.PropertyStore.*;
024import org.apache.juneau.collections.*;
025import org.apache.juneau.internal.*;
026import org.apache.juneau.json.*;
027import org.apache.juneau.marshall.*;
028import org.apache.juneau.reflect.*;
029import org.apache.juneau.svl.*;
030
031/**
032 * A builder for {@link PropertyStore} objects.
033 */
034public class PropertyStoreBuilder {
035
036   // Contains a cache of all created PropertyStore objects keyed by hashcode.
037   // Used to minimize memory consumption by reusing identical PropertyStores.
038   private static final Map<Integer,PropertyStore> CACHE = new ConcurrentHashMap<>();
039
040   // Maps property suffixes (e.g. "lc") to PropertyType (e.g. LIST_CLASS)
041   static final Map<String,PropertyType> SUFFIX_MAP = new ConcurrentHashMap<>();
042   static {
043      for (PropertyType pt : PropertyType.values())
044         SUFFIX_MAP.put(pt.getSuffix(), pt);
045   }
046
047   private final Map<String,PropertyGroupBuilder> groups = new ConcurrentSkipListMap<>();
048
049   // Previously-created property store.
050   private volatile PropertyStore propertyStore;
051
052   // Called by PropertyStore.builder()
053   PropertyStoreBuilder(PropertyStore ps) {
054      apply(ps);
055   }
056
057   // Called by PropertyStore.create()
058   PropertyStoreBuilder() {}
059
060   /**
061    * Creates a new {@link PropertyStore} based on the values in this builder.
062    *
063    * @return A new {@link PropertyStore} based on the values in this builder.
064    */
065   public synchronized PropertyStore build() {
066
067      // Reused the last one if we haven't change this builder.
068      if (propertyStore == null)
069         propertyStore = new PropertyStore(groups);
070
071      PropertyStore ps = CACHE.get(propertyStore.hashCode());
072      if (ps == null)
073         CACHE.put(propertyStore.hashCode(), propertyStore);
074      else if (! ps.equals(propertyStore))
075         throw new RuntimeException("Property store mismatch!  This shouldn't happen.  hashCode=["+propertyStore.hashCode()+"]\n---PS#1---\n" + ps.hashCodes() + "\n---PS#2---\n" + propertyStore.hashCodes());
076      else
077         propertyStore = ps;
078
079      return propertyStore;
080   }
081
082   /**
083    * Copies all the values in the specified property store into this builder.
084    *
085    * @param copyFrom The property store to copy the values from.
086    * @return This object (for method chaining).
087    */
088   public synchronized PropertyStoreBuilder apply(PropertyStore copyFrom) {
089      propertyStore = null;
090
091      if (copyFrom != null)
092         for (Map.Entry<String,PropertyGroup> e : copyFrom.groups.entrySet()) {
093            String gName = e.getKey();
094            PropertyGroupBuilder g1 = this.groups.get(gName);
095            PropertyGroup g2 = e.getValue();
096            if (g1 == null)
097               this.groups.put(gName, g2.builder());
098            else
099               g1.apply(g2);
100         }
101      return this;
102   }
103
104   /**
105    * Applies the settings in the specified annotations to this property store.
106    *
107    * @param al The list of annotations to apply.
108    * @param r The string resolver used to resolve any variables in the annotations.
109    * @return This object (for method chaining).
110    */
111   @SuppressWarnings("unchecked")
112   public PropertyStoreBuilder applyAnnotations(AnnotationList al, VarResolverSession r) {
113      for (AnnotationInfo<?> ai : al.sort()) {
114         try {
115            ai.getConfigApply(r).apply((AnnotationInfo<Annotation>)ai, this);
116         } catch (ConfigException ex) {
117            throw ex;
118         } catch (Exception ex) {
119            throw new ConfigException(ex, "Could not instantiate ConfigApply class {0}", ai);
120         }
121      }
122      return this;
123   }
124
125   /**
126    * Sets a configuration property value on this object.
127    *
128    * @param key The configuration property key (e.g <js>"BeanContext.foo.ss/add.1"</js>).
129    *    <ul>
130    *       <li>If name ends with <l>/append</l>, then the specified value is appended to the existing property value as an entry in a LIST property.
131    *       <li>If name ends with <l>/prepend</l>, then the specified value is prepended to the existing property value as an entry in a LIST property.
132    *       <li>If name ends with <l>/add</l>, then the specified value is added to the existing property value as an entry in a SET property.
133    *       <li>If name ends with <l>/put.{key}</l>, then the specified value is added to the existing property value as a key/value pair in a MAP property.
134    *       <li>If name ends with <l>/remove</l>, then the specified value is removed from the existing property property value in a SET or LIST property.
135    *    </ul>
136    * @param value The new value.
137    *    <ul>
138    *       <li>If <jk>null</jk>, the property value is deleted.
139    *       <li>In general, the value type can be anything.
140    *    </ul>
141    * @return This object (for method chaining).
142    */
143   public synchronized PropertyStoreBuilder set(String key, Object value) {
144      propertyStore = null;
145
146      String g = group(key);
147
148      int i = key.indexOf('/');
149      if (i != -1) {
150         String command = key.substring(i+1), arg = null;
151         String key2 = key.substring(0, i);
152         int j = command.indexOf('.');
153         if (j != -1) {
154            arg = command.substring(j+1);
155            command = command.substring(0, j);
156         }
157
158         if ("add".equals(command))
159            return addTo(key2, value);
160         if ("put".equals(command)) {
161            if (arg == null)
162               return putAllTo(key2, value);
163            return putTo(key2, arg, value);
164         }
165         if ("append".equals(command))
166            return appendTo(key2, value);
167         if ("prepend".equals(command))
168            return prependTo(key2, value);
169         if ("remove".equals(command)) {
170            if (arg != null)
171               throw new ConfigException("Invalid key specified: ''{0}''", key);
172            return removeFrom(key2, value);
173         }
174         throw new ConfigException("Invalid key specified: ''{0}''", key);
175      }
176
177      String n = g.isEmpty() ? key : key.substring(g.length()+1);
178
179      PropertyGroupBuilder gb = groups.get(g);
180      if (gb == null) {
181         gb = new PropertyGroupBuilder();
182         groups.put(g, gb);
183      }
184
185      gb.set(n, value);
186
187      if (gb.isEmpty())
188         groups.remove(g);
189
190      return this;
191   }
192
193   /**
194    * Sets a configuration property value on this object but only if it's not already set.
195    *
196    * @param key The configuration property key (e.g <js>"BeanContext.foo.ss"</js>).
197    *    <ul>
198    *       <li>Unlike {@link #set(String, Object)}, this method does not support appending or removing.
199    *    </ul>
200    * @param value The new value.
201    *    <ul>
202    *       <li>If <jk>null</jk>, the property value is deleted.
203    *       <li>In general, the value type can be anything.
204    *    </ul>
205    * @return This object (for method chaining).
206    */
207   public synchronized PropertyStoreBuilder setDefault(String key, Object value) {
208      propertyStore = null;
209
210      String g = group(key);
211
212      int i = key.indexOf('/');
213      if (i != -1) {
214         throw new ConfigException("Invalid key specified: ''{0}''", key);
215      }
216
217      String n = g.isEmpty() ? key : key.substring(g.length()+1);
218
219      PropertyGroupBuilder gb = groups.get(g);
220      if (gb == null) {
221         gb = new PropertyGroupBuilder();
222         groups.put(g, gb);
223      }
224
225      gb.setDefault(n, value);
226
227      return this;
228   }
229
230   /**
231    * Removes the property with the specified key.
232    *
233    * <p>
234    * This is equivalent to calling <code>set(key, <jk>null</jk>);</code>
235    *
236    * @param key The property key.
237    * @return This object (for method chaining).
238    */
239   public synchronized PropertyStoreBuilder remove(String key) {
240      propertyStore = null;
241      return set(key, null);
242   }
243
244   /**
245    * Convenience method for setting multiple properties in one call.
246    *
247    * <p>
248    * This replaces any previous configuration properties set on this store.
249    *
250    * @param newProperties The new properties to set.
251    * @return This object (for method chaining).
252    */
253   public synchronized PropertyStoreBuilder set(Map<String,Object> newProperties) {
254      propertyStore = null;
255      clear();
256      add(newProperties);
257      return this;
258   }
259
260   /**
261    * Convenience method for setting multiple properties in one call.
262    *
263    * <p>
264    * This appends to any previous configuration properties set on this store.
265    *
266    * @param newProperties The new properties to set.
267    * @return This object (for method chaining).
268    */
269   public synchronized PropertyStoreBuilder add(Map<String,Object> newProperties) {
270      propertyStore = null;
271
272      if (newProperties != null)
273         for (Map.Entry<String,Object> e : newProperties.entrySet())
274            set(e.getKey(), e.getValue());
275
276      return this;
277   }
278
279   /**
280    * Adds one or more values to a SET property.
281    *
282    * @param key The property key.
283    * @param value The new value to add to the property.
284    *    <ul>
285    *       <li>This can be a single value, Collection, array, or JSON array string.
286    *    </ul>
287    * @return This object (for method chaining).
288    * @throws ConfigException If property is not a SET property, or the argument is invalid.
289    */
290   public synchronized PropertyStoreBuilder addTo(String key, Object value) {
291      propertyStore = null;
292      String g = group(key);
293      String n = g.isEmpty() ? key : key.substring(g.length()+1);
294
295      // <TEMPORARY - Remove in 9.0>
296      if (isList(key))
297         return prependTo(key, value);
298      // </TEMPORARY>
299
300      if (! isSet(key))
301         throw new ConfigException("addTo() can only be used on properties of type Set on property ''{0}''.", key);
302
303      PropertyGroupBuilder gb = groups.get(g);
304      if (gb == null) {
305         gb = new PropertyGroupBuilder();
306         groups.put(g, gb);
307      }
308
309      gb.addToSet(n, value);
310
311      if (gb.isEmpty())
312         groups.remove(g);
313
314      return this;
315   }
316
317   /**
318    * Adds an entry to a MAP property.
319    *
320    * @param key The property key.
321    * @param mapKey The map key.
322    * @param value The new value to add to the property.
323    * @return This object (for method chaining).
324    * @throws ConfigException If property is not a SET property, or the argument is invalid.
325    */
326   public synchronized PropertyStoreBuilder putTo(String key, String mapKey, Object value) {
327      propertyStore = null;
328      String g = group(key);
329      String n = g.isEmpty() ? key : key.substring(g.length()+1);
330
331      if (! isMap(key))
332         throw new ConfigException("putTo() can only be used on properties of type Map on property ''{0}''.", key);
333
334      PropertyGroupBuilder gb = groups.get(g);
335      if (gb == null) {
336         gb = new PropertyGroupBuilder();
337         groups.put(g, gb);
338      }
339
340      gb.putToMap(n, mapKey, value);
341
342      if (gb.isEmpty())
343         groups.remove(g);
344
345      return this;
346   }
347
348   /**
349    * Adds multiple entries to a MAP property.
350    *
351    * @param key The property key.
352    * @param value The new values to add to the property.  Can be a {@link Map} or JSON string.
353    * @return This object (for method chaining).
354    * @throws ConfigException If property is not a SET property, or the argument is invalid.
355    */
356   public synchronized PropertyStoreBuilder putAllTo(String key, Object value) {
357      propertyStore = null;
358      String g = group(key);
359      String n = g.isEmpty() ? key : key.substring(g.length()+1);
360
361      if (! isMap(key))
362         throw new ConfigException("putTo() can only be used on properties of type Map on property ''{0}''.", key);
363
364      PropertyGroupBuilder gb = groups.get(g);
365      if (gb == null) {
366         gb = new PropertyGroupBuilder();
367         groups.put(g, gb);
368      }
369
370      gb.putAllToMap(n, value);
371
372      if (gb.isEmpty())
373         groups.remove(g);
374
375      return this;
376   }
377
378   /**
379    * Appends a value to the end of a LIST property.
380    *
381    * @param key The property key.
382    * @param value The new value to add to the property.
383    *    <ul>
384    *       <li>This can be a single value, Collection, array, or JSON array string.
385    *    </ul>
386    * @return This object (for method chaining).
387    * @throws ConfigException If property is not a SET/LIST property, or the argument is invalid.
388    */
389   public synchronized PropertyStoreBuilder appendTo(String key, Object value) {
390      propertyStore = null;
391      String g = group(key);
392      String n = g.isEmpty() ? key : key.substring(g.length()+1);
393
394      if (! isList(key))
395         throw new ConfigException("appendTo() can only be used on properties of type List on property ''{0}''.", key);
396
397      PropertyGroupBuilder gb = groups.get(g);
398      if (gb == null) {
399         gb = new PropertyGroupBuilder();
400         groups.put(g, gb);
401      }
402
403      gb.appendToList(n, value);
404
405      if (gb.isEmpty())
406         groups.remove(g);
407
408      return this;
409   }
410
411   /**
412    * Prepends a value to the beginning of a LIST property.
413    *
414    * @param key The property key.
415    * @param value The new value to add to the property.
416    *    <ul>
417    *       <li>This can be a single value, Collection, array, or JSON array string.
418    *    </ul>
419    * @return This object (for method chaining).
420    * @throws ConfigException If property is not a SET/LIST property, or the argument is invalid.
421    */
422   public synchronized PropertyStoreBuilder prependTo(String key, Object value) {
423      propertyStore = null;
424      String g = group(key);
425      String n = g.isEmpty() ? key : key.substring(g.length()+1);
426
427      if (! isList(key))
428         throw new ConfigException("prependTo() can only be used on properties of type List on property ''{0}''.", key);
429
430      PropertyGroupBuilder gb = groups.get(g);
431      if (gb == null) {
432         gb = new PropertyGroupBuilder();
433         groups.put(g, gb);
434      }
435
436      gb.prependTo(n, value);
437
438      if (gb.isEmpty())
439         groups.remove(g);
440
441      return this;
442   }
443
444   /**
445    * Removes a value from a SET or LIST property.
446    *
447    * @param key The property key.
448    * @param value The property value in the property.
449    * @return This object (for method chaining).
450    * @throws ConfigException If property is not a SET or LIST property.
451    */
452   public synchronized PropertyStoreBuilder removeFrom(String key, Object value) {
453      propertyStore = null;
454      String g = group(key);
455      String n = g.isEmpty() ? key : key.substring(g.length()+1);
456
457      PropertyGroupBuilder gb = groups.get(g);
458
459      // Create property group anyway to generate a good error message.
460      if (gb == null)
461         gb = new PropertyGroupBuilder();
462
463      gb.removeFrom(n, value);
464
465      if (gb.isEmpty())
466         groups.remove(g);
467
468      return this;
469   }
470
471   /**
472    * Peeks at a property value.
473    *
474    * <p>
475    * Used for debugging purposes.
476    *
477    * @param key The property key.
478    * @return The property value, or <jk>null</jk> if it doesn't exist.
479    */
480   public Object peek(String key) {
481      String g = group(key);
482      String n = g.isEmpty() ? key : key.substring(g.length()+1);
483
484      PropertyGroupBuilder gb = groups.get(g);
485
486      // Create property group anyway to generate a good error message.
487      if (gb == null)
488         return null;
489
490      MutableProperty bp = gb.properties.get(n);
491      if (bp == null)
492         return null;
493
494      return bp.peek();
495   }
496
497   /**
498    * Same as {@link #peek(String)} but converts the value to the specified type.
499    *
500    * @param <T> The type to convert to.
501    * @param c The type to convert to.
502    * @param key The property key.
503    * @return The property value, or <jk>null</jk> if it doesn't exist.
504    */
505   public <T> T peek(Class<T> c, String key)  {
506      Object o = peek(key);
507      if (o == null)
508         return null;
509      return BeanContext.DEFAULT.createBeanSession().convertToType(o, c);
510   }
511
512   /**
513    * Clears all entries in this property store.
514    */
515   public synchronized void clear() {
516      propertyStore = null;
517      this.groups.clear();
518   }
519
520   /**
521    * Clears the PropertyStore cache.
522    */
523   public static void clearCache() {
524      CACHE.clear();
525   }
526
527   private static boolean isSet(String key) {
528      String s = key.substring(key.lastIndexOf('.')+1);
529      return s.length() == 2 && s.charAt(0) == 's';
530   }
531
532   private static boolean isMap(String key) {
533      String s = key.substring(key.lastIndexOf('.')+1);
534      return s.length() == 3 && s.charAt(1) == 'm';
535   }
536
537   private static boolean isList(String key) {
538      String s = key.substring(key.lastIndexOf('.')+1);
539      return s.length() == 2 && s.charAt(0) == 'l';
540   }
541
542   //-------------------------------------------------------------------------------------------------------------------
543   // PropertyGroupBuilder
544   //-------------------------------------------------------------------------------------------------------------------
545
546   static class PropertyGroupBuilder {
547      final Map<String,MutableProperty> properties = new ConcurrentSkipListMap<>();
548
549      PropertyGroupBuilder() {
550         this(emptyMap());
551      }
552
553      synchronized void apply(PropertyGroup copyFrom) {
554         for (Map.Entry<String,Property> e : copyFrom.properties.entrySet()) {
555            String pName = e.getKey();
556            MutableProperty p1 = properties.get(pName);
557            Property p2 = e.getValue();
558            if (p1 == null)
559               properties.put(pName, p2.mutable());
560            else
561               p1.apply(p2.value);
562         }
563      }
564
565      PropertyGroupBuilder(Map<String,Property> properties) {
566         for (Map.Entry<String,Property> p : properties.entrySet())
567            this.properties.put(p.getKey(), p.getValue().mutable());
568      }
569
570      synchronized void set(String key, Object value) {
571         MutableProperty p = properties.get(key);
572         if (p == null) {
573            p = MutableProperty.create(key, value);
574            if (! p.isEmpty())
575               properties.put(key, p);
576         } else {
577            p.set(value);
578            if (p.isEmpty())
579               properties.remove(key);
580            else
581               properties.put(key, p);
582         }
583      }
584
585      synchronized void setDefault(String key, Object value) {
586         MutableProperty p = properties.get(key);
587         if (p == null) {
588            p = MutableProperty.create(key, value);
589            if (! p.isEmpty())
590               properties.put(key, p);
591         }
592      }
593
594      synchronized void addToSet(String key, Object value) {
595         MutableProperty p = properties.get(key);
596         if (p == null) {
597            p = MutableProperty.create(key, null);
598            p.add(value);
599            if (! p.isEmpty())
600               properties.put(key, p);
601         } else {
602            p.add(value);
603            if (p.isEmpty())
604               properties.remove(key);
605            else
606               properties.put(key, p);
607         }
608      }
609
610      synchronized void putToMap(String key, String arg, Object value) {
611         MutableProperty p = properties.get(key);
612         if (p == null) {
613            p = MutableProperty.create(key, null);
614            p.put(arg, value);
615            if (! p.isEmpty())
616               properties.put(key, p);
617         } else {
618            p.put(arg, value);
619            if (p.isEmpty())
620               properties.remove(key);
621            else
622               properties.put(key, p);
623         }
624      }
625
626      synchronized void putAllToMap(String key, Object value) {
627         MutableProperty p = properties.get(key);
628         if (p == null) {
629            p = MutableProperty.create(key, null);
630            p.set(value);
631            if (! p.isEmpty())
632               properties.put(key, p);
633         } else {
634            p.putAll(value);
635            if (p.isEmpty())
636               properties.remove(key);
637            else
638               properties.put(key, p);
639         }
640      }
641
642      synchronized void appendToList(String key, Object value) {
643         MutableProperty p = properties.get(key);
644         if (p == null) {
645            p = MutableProperty.create(key, null);
646            p.append(value);
647            if (! p.isEmpty())
648               properties.put(key, p);
649         } else {
650            p.append(value);
651            if (p.isEmpty())
652               properties.remove(key);
653            else
654               properties.put(key, p);
655         }
656      }
657
658      synchronized void prependTo(String key, Object value) {
659         MutableProperty p = properties.get(key);
660         if (p == null) {
661            p = MutableProperty.create(key, null);
662            p.prepend(value);
663            if (! p.isEmpty())
664               properties.put(key, p);
665         } else {
666            p.prepend(value);
667            if (p.isEmpty())
668               properties.remove(key);
669            else
670               properties.put(key, p);
671         }
672      }
673
674      synchronized void removeFrom(String key, Object value) {
675         MutableProperty p = properties.get(key);
676         if (p == null) {
677            // Create property anyway to generate a good error message.
678            p = MutableProperty.create(key, null);
679            p.remove(value);
680         } else {
681            p.remove(value);
682            if (p.isEmpty())
683               properties.remove(key);
684            else
685               properties.put(key, p);
686         }
687      }
688
689      synchronized boolean isEmpty() {
690         return properties.isEmpty();
691      }
692
693      synchronized PropertyGroup build() {
694         return new PropertyGroup(properties);
695      }
696
697      /**
698       * Used by the <c>toString()</c> method for debugging.
699       *
700       * @param bs The bean session.
701       * @return This object converted to a map.
702       */
703      public Map<String,MutableProperty> swap(BeanSession bs) {
704         return properties;
705      }
706
707      @Override
708      public String toString() {
709         return (SimpleJson.DEFAULT == null ? "" : SimpleJson.DEFAULT.toString(properties));
710      }
711   }
712
713   //-------------------------------------------------------------------------------------------------------------------
714   // MutableProperty
715   //-------------------------------------------------------------------------------------------------------------------
716
717   static abstract class MutableProperty {
718      final String name;
719      final PropertyType type;
720
721      MutableProperty(String name, PropertyType type) {
722         this.name = name;
723         this.type = type;
724      }
725
726      static MutableProperty create(String name, Object value) {
727         int i = name.lastIndexOf('.');
728         String type = i == -1 ? "s" : name.substring(i+1);
729         PropertyType pt = SUFFIX_MAP.get(type);
730
731         if (pt == null)
732            throw new ConfigException("Invalid type specified on property ''{0}''", name);
733
734         switch (pt) {
735            case STRING:
736            case BOOLEAN:
737            case INTEGER:
738            case CLASS:
739            case OBJECT:  return new MutableSimpleProperty(name, pt, value);
740            case SET_STRING:
741            case SET_INTEGER:
742            case SET_CLASS: return new MutableSetProperty(name, pt, value);
743            case LIST_STRING:
744            case LIST_INTEGER:
745            case LIST_CLASS:
746            case LIST_OBJECT: return new MutableListProperty(name, pt, value);
747            case SORTED_MAP_STRING:
748            case SORTED_MAP_INTEGER:
749            case SORTED_MAP_CLASS:
750            case SORTED_MAP_OBJECT: return new MutableMapProperty(name, pt, value);
751            case ORDERED_MAP_STRING:
752            case ORDERED_MAP_INTEGER:
753            case ORDERED_MAP_CLASS:
754            case ORDERED_MAP_OBJECT: return new MutableLinkedMapProperty(name, pt, value);
755            default: return new MutableSimpleProperty(name, PropertyType.STRING, value);
756         }
757      }
758
759      abstract Property build();
760
761      abstract boolean isEmpty();
762
763      abstract void set(Object value);
764
765      abstract void apply(Object value);
766
767      abstract Object peek();
768
769      void add(Object value) {
770         throw new ConfigException("Cannot add value {0} ({1}) to property ''{2}'' ({3}).", string(value), className(value), name, type);
771      }
772
773      void put(String key, Object value) {
774         throw new ConfigException("Cannot put all value {0} ({1}) to property ''{2}'' ({3}).", string(value), className(value), name, type);
775      }
776
777      void putAll(Object value) {
778         throw new ConfigException("Cannot put all value {0} ({1}) to property ''{2}'' ({3}).", string(value), className(value), name, type);
779      }
780
781      void append(Object value) {
782         throw new ConfigException("Cannot append value {0} ({1}) to property ''{2}'' ({3}).", string(value), className(value), name, type);
783      }
784
785      void prepend(Object value) {
786         throw new ConfigException("Cannot prepend value {0} ({1}) to property ''{2}'' ({3}).", string(value), className(value), name, type);
787      }
788
789      void remove(Object value) {
790         throw new ConfigException("Cannot remove value {0} ({1}) from property ''{2}'' ({3}).", string(value), className(value), name, type);
791      }
792
793      Object convert(Object value) {
794         return value == null ? null : type.converter.convert(value);
795      }
796
797      @Override /* Object */
798      public String toString() {
799         return StringUtils.stringify(peek());
800      }
801   }
802
803   //-------------------------------------------------------------------------------------------------------------------
804   // MutableSimpleProperty
805   //-------------------------------------------------------------------------------------------------------------------
806
807   static class MutableSimpleProperty extends MutableProperty {
808      private Object value;
809
810      MutableSimpleProperty(String name, PropertyType type, Object value) {
811         super(name, type);
812         set(value);
813      }
814
815      @Override /* MutableProperty */
816      synchronized Property build() {
817         return new Property(name, value, type);
818      }
819
820      @Override /* MutableProperty */
821      synchronized void set(Object value) {
822         this.value = convert(value);
823      }
824
825      @Override /* MutableProperty */
826      synchronized void apply(Object value) {
827         this.value = convert(value);
828      }
829
830      @Override /* MutableProperty */
831      synchronized boolean isEmpty() {
832         return this.value == null;
833      }
834
835      @Override /* MutableProperty */
836      synchronized Object peek() {
837         return value;
838      }
839   }
840
841   //-------------------------------------------------------------------------------------------------------------------
842   // MutableSetProperty
843   //-------------------------------------------------------------------------------------------------------------------
844
845   static class MutableSetProperty extends MutableProperty {
846      private final ConcurrentSkipListSet<Object> set;
847
848      MutableSetProperty(String name, PropertyType type, Object value) {
849         super(name, type);
850         set = new ConcurrentSkipListSet<>(type.comparator());
851         set(value);
852      }
853
854      @Override /* MutableProperty */
855      synchronized Property build() {
856         return new Property(name, unmodifiableSet(new LinkedHashSet<>(set)), type);
857      }
858
859      @Override /* MutableProperty */
860      synchronized void set(Object value) {
861         try {
862            Set<Object> newSet = merge(set, type.converter, value);
863            set.clear();
864            set.addAll(newSet);
865         } catch (Exception e) {
866            throw new ConfigException(e, "Cannot set value {0} ({1}) on property ''{2}'' ({3}).", string(value), className(value), name, type);
867         }
868      }
869
870      @Override /* MutableProperty */
871      synchronized void apply(Object values) {
872         set.addAll((Set<?>)values);
873      }
874
875      @Override /* MutableProperty */
876      synchronized void add(Object o) {
877         try {
878            set.addAll(normalize(type.converter, o));
879         } catch (Exception e) {
880            throw new ConfigException(e, "Cannot add value {0} ({1}) to property ''{2}'' ({3}).", string(o), className(o), name, type);
881         }
882      }
883
884      @Override /* MutableProperty */
885      synchronized void remove(Object o) {
886         try {
887            set.removeAll(normalize(type.converter, o));
888         } catch (Exception e) {
889            throw new ConfigException(e, "Cannot remove value {0} ({1}) from property ''{2}'' ({3}).", string(o), className(o), name, type);
890         }
891      }
892
893      @Override /* MutableProperty */
894      synchronized boolean isEmpty() {
895         return set.isEmpty();
896      }
897
898      @Override /* MutableProperty */
899      synchronized Object peek() {
900         return set;
901      }
902   }
903
904   //-------------------------------------------------------------------------------------------------------------------
905   // MutableListProperty
906   //-------------------------------------------------------------------------------------------------------------------
907
908   static class MutableListProperty extends MutableProperty {
909      private final List<Object> list = synchronizedList(new LinkedList<>());
910
911      MutableListProperty(String name, PropertyType type, Object value) {
912         super(name, type);
913         set(value);
914      }
915
916      @Override /* MutableProperty */
917      synchronized Property build() {
918         return new Property(name, unmodifiableList(new ArrayList<>(list)), type);
919      }
920
921      @Override /* MutableProperty */
922      synchronized void set(Object value) {
923         try {
924            List<Object> newList = merge(list, type.converter, value);
925            list.clear();
926            list.addAll(newList);
927         } catch (Exception e) {
928            throw new ConfigException(e, "Cannot set value {0} ({1}) on property ''{2}'' ({3}).", string(value), className(value), name, type);
929         }
930      }
931
932      @Override /* MutableProperty */
933      synchronized void apply(Object values) {
934         list.addAll((List<?>)values);
935      }
936
937      @Override /* MutableProperty */
938      synchronized void append(Object o) {
939         try {
940            List<Object> l = normalize(type.converter, o);
941            list.removeAll(l);
942            list.addAll(l);
943         } catch (Exception e) {
944            throw new ConfigException(e, "Cannot append value {0} ({1}) to property ''{2}'' ({3}).", string(o), className(o), name, type);
945         }
946      }
947
948      @Override /* MutableProperty */
949      synchronized void prepend(Object o) {
950         try {
951            List<Object> l = normalize(type.converter, o);
952            list.removeAll(l);
953            list.addAll(0, l);
954         } catch (Exception e) {
955            throw new ConfigException(e, "Cannot prepend value {0} ({1}) to property ''{2}'' ({3}).", string(o), className(o), name, type);
956         }
957      }
958
959      @Override /* MutableProperty */
960      synchronized void remove(Object o) {
961         try {
962            list.removeAll(normalize(type.converter, o));
963         } catch (Exception e) {
964            throw new ConfigException(e, "Cannot remove value {0} ({1}) from property ''{2}'' ({3}).", string(o), className(o), name, type);
965         }
966      }
967
968      @Override /* MutableProperty */
969      synchronized boolean isEmpty() {
970         return list.isEmpty();
971      }
972
973      @Override /* MutableProperty */
974      synchronized Object peek() {
975         return list;
976      }
977   }
978
979   //-------------------------------------------------------------------------------------------------------------------
980   // MutableMapProperty
981   //-------------------------------------------------------------------------------------------------------------------
982
983   @SuppressWarnings("rawtypes")
984   static class MutableMapProperty extends MutableProperty {
985      protected Map<String,Object> map;
986
987      MutableMapProperty(String name, PropertyType type, Object value) {
988         super(name, type);
989         this.map = createMap();
990         set(value);
991      }
992
993      protected Map<String,Object> createMap() {
994         return new ConcurrentHashMap<>();
995      }
996
997      @Override /* MutableProperty */
998      synchronized Property build() {
999         return new Property(name, unmodifiableMap(new TreeMap<>(map)), type);
1000      }
1001
1002      @Override /* MutableProperty */
1003      synchronized void set(Object value) {
1004         this.map.clear();
1005         putAll(value);
1006      }
1007
1008      @SuppressWarnings("unchecked")
1009      @Override /* MutableProperty */
1010      synchronized void apply(Object values) {
1011         for (Map.Entry<String,Object> e : ((Map<String,Object>)values).entrySet())
1012            put(e.getKey(), e.getValue());
1013      }
1014
1015      @Override /* MutableProperty */
1016      synchronized void put(String key, Object o) {
1017         if (key == null)
1018            return;
1019         o = convert(o);
1020         if (o == null)
1021            this.map.remove(key);
1022         else
1023            this.map.put(key, o);
1024      }
1025
1026      @Override /* MutableProperty */
1027      synchronized void putAll(Object o) {
1028         if (o instanceof Map) {
1029            Map m = (Map)o;
1030            for (Map.Entry e : (Set<Map.Entry>)m.entrySet())
1031               if (e.getKey() != null)
1032                  put(e.getKey().toString(), e.getValue());
1033         } else if (isJsonObject(o)) {
1034            try {
1035               putAll(OMap.ofJson(o.toString()));
1036            } catch (Exception e) {
1037               throw new ConfigException(e, "Cannot put {0} ({1}) to property ''{2}'' ({3}).", string(o), className(o), name, type);
1038            }
1039         } else if (o != null) {
1040            throw new ConfigException("Cannot put {0} ({1}) to property ''{2}'' ({3}).", string(o), className(o), name, type);
1041         }
1042      }
1043
1044      @Override /* MutableProperty */
1045      synchronized boolean isEmpty() {
1046         return this.map.isEmpty();
1047      }
1048
1049      @Override /* MutableProperty */
1050      synchronized Object peek() {
1051         return map;
1052      }
1053   }
1054
1055   //-------------------------------------------------------------------------------------------------------------------
1056   // MutableLinkedMapProperty
1057   //-------------------------------------------------------------------------------------------------------------------
1058
1059   static class MutableLinkedMapProperty extends MutableMapProperty {
1060
1061      MutableLinkedMapProperty(String name, PropertyType type, Object value) {
1062         super(name, type, value);
1063         set(value);
1064      }
1065
1066      @Override
1067      protected Map<String,Object> createMap() {
1068         return synchronizedMap(new LinkedHashMap<String,Object>());
1069      }
1070
1071      @Override /* MutableProperty */
1072      synchronized Property build() {
1073         return new Property(name, unmodifiableMap(new LinkedHashMap<>(map)), type);
1074      }
1075   }
1076
1077
1078   //-------------------------------------------------------------------------------------------------------------------
1079   // Utility methods
1080   //-------------------------------------------------------------------------------------------------------------------
1081
1082   static Set<Object> merge(Set<Object> oldSet, PropertyConverter<?> pc, Object o) throws Exception {
1083      return merge(oldSet, new LinkedHashSet<>(), normalize(pc, o));
1084   }
1085
1086   private static Set<Object> merge(Set<Object> oldSet, Set<Object> newSet, List<Object> l) {
1087      for (Object o : l) {
1088         if (isNone(o))
1089            newSet.clear();
1090         else if (isInherit(o))
1091            newSet.addAll(oldSet);
1092         else
1093            newSet.add(o);
1094      }
1095      return newSet;
1096   }
1097
1098   static List<Object> merge(List<Object> oldList, PropertyConverter<?> pc, Object o) throws Exception {
1099      return merge(oldList, new ArrayList<>(), normalize(pc, o));
1100   }
1101
1102   private static List<Object> merge(List<Object> oldList, List<Object> newList, List<Object> l) {
1103      for (Object o : l) {
1104         if (isIndexed(o)) {
1105            Matcher lm = INDEXED_LINK_PATTERN.matcher(o.toString());
1106            lm.matches();
1107            String key = lm.group(1);
1108            int i2 = Math.min(newList.size(), Integer.parseInt(lm.group(2)));
1109            String remainder = lm.group(3);
1110            newList.add(i2, key.isEmpty() ? remainder : key + ":" + remainder);
1111         } else if (isNone(o)) {
1112            newList.clear();
1113         } else if (isInherit(o)) {
1114            if (oldList != null)
1115               for (Object o2 : oldList)
1116                  newList.add(o2);
1117         } else {
1118            newList.remove(o);
1119            newList.add(o);
1120         }
1121      }
1122
1123      return newList;
1124   }
1125
1126   static List<Object> normalize(PropertyConverter<?> pc, Object o) throws Exception {
1127      return normalize(new ArrayList<>(), pc, o);
1128   }
1129
1130   @SuppressWarnings("unchecked")
1131   static List<Object> normalize(List<Object> l, PropertyConverter<?> pc, Object o) throws Exception {
1132      if (o != null) {
1133         if (o.getClass().isArray()) {
1134            for (int i = 0; i < Array.getLength(o); i++)
1135               normalize(l, pc, Array.get(o, i));
1136         } else if (o instanceof Collection) {
1137            for (Object o2 : (Collection<Object>)o)
1138               normalize(l, pc, o2);
1139         } else if (isJsonArray(o)) {
1140            normalize(l, pc, new OList(o.toString()));
1141         } else {
1142            l.add(pc == null ? o : pc.convert(o));
1143         }
1144      }
1145      return l;
1146   }
1147
1148   static String string(Object value) {
1149      return SimpleJsonSerializer.DEFAULT.toString(value);
1150   }
1151
1152   static String className(Object value) {
1153      return value.getClass().getSimpleName();
1154   }
1155
1156   static boolean isJsonObject(Object o) {
1157      if (o instanceof CharSequence) {
1158         String s = o.toString();
1159         return (s.startsWith("{") && s.endsWith("}") && BeanContext.DEFAULT != null);
1160      }
1161      return false;
1162   }
1163
1164   private static String group(String key) {
1165      if (key == null || key.indexOf('.') == -1)
1166         return "";
1167      return key.substring(0, key.indexOf('.'));
1168   }
1169
1170   static boolean isJsonArray(Object o) {
1171      if (o instanceof CharSequence) {
1172         String s = o.toString();
1173         return (s.startsWith("[") && s.endsWith("]") && BeanContext.DEFAULT != null);
1174      }
1175      return false;
1176   }
1177
1178   private static boolean isNone(Object o) {
1179      if (o instanceof CharSequence) {
1180         String s = o.toString();
1181         return "NONE".equals(s);
1182      }
1183      return false;
1184   }
1185
1186   private static boolean isIndexed(Object o) {
1187      if (o instanceof CharSequence) {
1188         String s = o.toString();
1189         return s.indexOf('[') != -1 && INDEXED_LINK_PATTERN.matcher(s).matches();
1190      }
1191      return false;
1192   }
1193
1194   private static final Pattern INDEXED_LINK_PATTERN = Pattern.compile("(?s)(\\S*)\\[(\\d+)\\]\\:(.*)");
1195
1196   private static boolean isInherit(Object o) {
1197      if (o instanceof CharSequence) {
1198         String s = o.toString();
1199         return "INHERIT".equals(s);
1200      }
1201      return false;
1202   }
1203
1204   @Override /* Object */
1205   public String toString() {
1206      return SimpleJson.DEFAULT_READABLE.toString(groups);
1207   }
1208}