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.reflect.*;
018import java.util.*;
019import java.util.concurrent.*;
020import java.util.regex.*;
021
022import org.apache.juneau.PropertyStore.*;
023import org.apache.juneau.internal.*;
024import org.apache.juneau.json.*;
025
026/**
027 * A builder for {@link PropertyStore} objects.
028 */
029public class PropertyStoreBuilder {
030
031   // Contains a cache of all created PropertyStore objects keyed by hashcode.
032   // Used to minimize memory consumption by reusing identical PropertyStores.
033   private static final Map<Integer,PropertyStore> CACHE = new ConcurrentHashMap<>();
034
035   // Maps property suffixes (e.g. "lc") to PropertyType (e.g. LIST_CLASS)
036   static final Map<String,PropertyType> SUFFIX_MAP = new ConcurrentHashMap<>();
037   static {
038      for (PropertyType pt : PropertyType.values())
039         SUFFIX_MAP.put(pt.getSuffix(), pt);
040   }
041
042   private final Map<String,PropertyGroupBuilder> groups = new ConcurrentSkipListMap<>();
043
044   // Previously-created property store.
045   private volatile PropertyStore propertyStore;
046
047   // Called by PropertyStore.builder()
048   PropertyStoreBuilder(PropertyStore ps) {
049      apply(ps);
050   }
051
052   // Called by PropertyStore.create()
053   PropertyStoreBuilder() {}
054
055   /**
056    * Creates a new {@link PropertyStore} based on the values in this builder.
057    *
058    * @return A new {@link PropertyStore} based on the values in this builder.
059    */
060   public synchronized PropertyStore build() {
061
062      // Reused the last one if we haven't change this builder.
063      if (propertyStore == null)
064         propertyStore = new PropertyStore(groups);
065
066      PropertyStore ps = CACHE.get(propertyStore.hashCode());
067      if (ps == null)
068         CACHE.put(propertyStore.hashCode(), propertyStore);
069      else if (! ps.equals(propertyStore))
070         throw new RuntimeException("Property store mismatch!  This shouldn't happen.");
071      else
072         propertyStore = ps;
073
074      return propertyStore;
075   }
076
077   /**
078    * Copies all the values in the specified property store into this builder.
079    *
080    * @param copyFrom The property store to copy the values from.
081    * @return This object (for method chaining).
082    */
083   public synchronized PropertyStoreBuilder apply(PropertyStore copyFrom) {
084      propertyStore = null;
085
086      if (copyFrom != null)
087         for (Map.Entry<String,PropertyGroup> e : copyFrom.groups.entrySet()) {
088            String gName = e.getKey();
089            PropertyGroupBuilder g1 = this.groups.get(gName);
090            PropertyGroup g2 = e.getValue();
091            if (g1 == null)
092               this.groups.put(gName, g2.builder());
093            else
094               g1.apply(g2);
095         }
096      return this;
097   }
098
099   /**
100    * Sets a configuration property value on this object.
101    *
102    * @param key
103    *    The configuration property key.
104    *    <br>(e.g <js>"BeanContext.foo.ss/add.1"</js>)
105    *    <br>If name ends with <l>/add</l>, then the specified value is added to the existing property value as an entry
106    *    in a SET or LIST property.
107    *    <br>If name ends with <l>/add.{key}</l>, then the specified value is added to the existing property value as a
108    *    key/value pair in a MAP property.
109    *    <br>If name ends with <l>/remove</l>, then the specified value is removed from the existing property property
110    *    value in a SET or LIST property.
111    * @param value
112    *    The new value.
113    *    If <jk>null</jk>, the property value is deleted.
114    *    In general, the value type can be anything.
115    * @return This object (for method chaining).
116    */
117   public synchronized PropertyStoreBuilder set(String key, Object value) {
118      propertyStore = null;
119
120      String g = group(key);
121
122      int i = key.indexOf('/');
123      if (i != -1) {
124         String command = key.substring(i+1), arg = null;
125         String key2 = key.substring(0, i);
126         int j = command.indexOf('.');
127         if (j != -1) {
128            arg = command.substring(j+1);
129            command = command.substring(0, j);
130         }
131
132         if ("add".equals(command)) {
133            return addTo(key2, arg, value);
134         } else if ("remove".equals(command)) {
135            if (arg != null)
136               throw new ConfigException("Invalid key specified: ''{0}''", key);
137            return removeFrom(key2, value);
138         } else {
139            throw new ConfigException("Invalid key specified: ''{0}''", key);
140         }
141      }
142
143      String n = g.isEmpty() ? key : key.substring(g.length()+1);
144
145      PropertyGroupBuilder gb = groups.get(g);
146      if (gb == null) {
147         gb = new PropertyGroupBuilder();
148         groups.put(g, gb);
149      }
150
151      gb.set(n, value);
152
153      if (gb.isEmpty())
154         groups.remove(g);
155
156      return this;
157   }
158
159   /**
160    * Removes the property with the specified key.
161    *
162    * <p>
163    * This is equivalent to calling <code>set(key, <jk>null</jk>);</code>
164    *
165    * @param key The property key.
166    * @return This object (for method chaining).
167    */
168   public synchronized PropertyStoreBuilder remove(String key) {
169      propertyStore = null;
170      return set(key, null);
171   }
172
173   /**
174    * Convenience method for setting multiple properties in one call.
175    *
176    * <p>
177    * This replaces any previous configuration properties set on this store.
178    *
179    * @param newProperties The new properties to set.
180    * @return This object (for method chaining).
181    */
182   public synchronized PropertyStoreBuilder set(Map<String,Object> newProperties) {
183      propertyStore = null;
184      clear();
185      add(newProperties);
186      return this;
187   }
188
189   /**
190    * Convenience method for setting multiple properties in one call.
191    *
192    * <p>
193    * This appends to any previous configuration properties set on this store.
194    *
195    * @param newProperties The new properties to set.
196    * @return This object (for method chaining).
197    */
198   public synchronized PropertyStoreBuilder add(Map<String,Object> newProperties) {
199      propertyStore = null;
200
201      if (newProperties != null)
202         for (Map.Entry<String,Object> e : newProperties.entrySet())
203            set(e.getKey(), e.getValue());
204
205      return this;
206   }
207
208   /**
209    * Adds one or more values to a SET, LIST, or MAP property.
210    *
211    * @param key The property key.
212    * @param arg
213    *    The argument.
214    *    <br>For SETs, this must always be <jk>null</jk>.
215    *    <br>For LISTs, this can be <jk>null</jk> or a numeric index.
216    *       Out-of-range indexes are simply 'adjusted' to the beginning or the end of the list.
217    *       So, for example, a value of <js>"-100"</js> will always just cause the entry to be added to the beginning
218    *       of the list.
219    *    <br>For MAPs, this can be <jk>null</jk> if we're adding a map, or a string key if we're adding an entry.
220    * @param value
221    *    The new value to add to the property.
222    *    <br>For SETs and LISTs, this can be a single value, Collection, array, or JSON array string.
223    *    <br>For MAPs, this can be a single value, Map, or JSON object string.
224    *    <br><jk>null</jk> values have the effect of removing entries.
225    * @return This object (for method chaining).
226    * @throws ConfigException If property is not a SET/LIST/MAP property, or the argument is invalid.
227    */
228   public synchronized PropertyStoreBuilder addTo(String key, String arg, Object value) {
229      propertyStore = null;
230      String g = group(key);
231      String n = g.isEmpty() ? key : key.substring(g.length()+1);
232
233      PropertyGroupBuilder gb = groups.get(g);
234      if (gb == null) {
235         gb = new PropertyGroupBuilder();
236         groups.put(g, gb);
237      }
238
239      gb.addTo(n, arg, value);
240
241      if (gb.isEmpty())
242         groups.remove(g);
243
244      return this;
245   }
246
247   /**
248    * Adds a value to a SET, LIST, or MAP property.
249    *
250    * <p>
251    * Shortcut for calling <code>addTo(key, <jk>null</jk>, value);</code>.
252    *
253    * @param key The property key.
254    * @param value
255    *    The new value to add to the property.
256    *    <br>For SETs and LISTs, this can be a single value, Collection, array, or JSON array string.
257    *    <br>for MAPs, this can be a single value, Map, or JSON object string.
258    *    <br><jk>null</jk> values have the effect of removing entries.
259    * @return This object (for method chaining).
260    * @throws ConfigException If property is not a SET/LIST/MAP property, or the argument is invalid.
261    */
262   public synchronized PropertyStoreBuilder addTo(String key, Object value) {
263      propertyStore = null;
264      return addTo(key, null, value);
265   }
266
267   /**
268    * Removes a value from a SET or LIST property.
269    *
270    * @param key The property key.
271    * @param value The property value in the property.
272    * @return This object (for method chaining).
273    * @throws ConfigException If property is not a SET or LIST property.
274    */
275   public synchronized PropertyStoreBuilder removeFrom(String key, Object value) {
276      propertyStore = null;
277      String g = group(key);
278      String n = g.isEmpty() ? key : key.substring(g.length()+1);
279
280      PropertyGroupBuilder gb = groups.get(g);
281
282      // Create property group anyway to generate a good error message.
283      if (gb == null)
284         gb = new PropertyGroupBuilder();
285
286      gb.removeFrom(n, value);
287
288      if (gb.isEmpty())
289         groups.remove(g);
290
291      return this;
292   }
293
294   /**
295    * Peeks at a property value.
296    *
297    * <p>
298    * Used for debugging purposes.
299    *
300    * @param key The property key.
301    * @return The property value, or <jk>null</jk> if it doesn't exist.
302    */
303   public Object peek(String key) {
304      String g = group(key);
305      String n = g.isEmpty() ? key : key.substring(g.length()+1);
306
307      PropertyGroupBuilder gb = groups.get(g);
308
309      // Create property group anyway to generate a good error message.
310      if (gb == null)
311         return null;
312
313      MutableProperty bp = gb.properties.get(n);
314      if (bp == null)
315         return null;
316
317      return bp.peek();
318   }
319
320   /**
321    * Clears all entries in this property store.
322    */
323   public synchronized void clear() {
324      propertyStore = null;
325      this.groups.clear();
326   }
327
328   //-------------------------------------------------------------------------------------------------------------------
329   // PropertyGroupBuilder
330   //-------------------------------------------------------------------------------------------------------------------
331
332   static class PropertyGroupBuilder {
333      final Map<String,MutableProperty> properties = new ConcurrentSkipListMap<>();
334
335      PropertyGroupBuilder() {
336         this(EMPTY_MAP);
337      }
338
339      synchronized void apply(PropertyGroup copyFrom) {
340         for (Map.Entry<String,Property> e : copyFrom.properties.entrySet()) {
341            String pName = e.getKey();
342            MutableProperty p1 = properties.get(pName);
343            Property p2 = e.getValue();
344            if (p1 == null)
345               properties.put(pName, p2.mutable());
346            else
347               p1.apply(p2.value);
348         }
349      }
350
351      PropertyGroupBuilder(Map<String,Property> properties) {
352         for (Map.Entry<String,Property> p : properties.entrySet())
353            this.properties.put(p.getKey(), p.getValue().mutable());
354      }
355
356      synchronized void set(String key, Object value) {
357         MutableProperty p = properties.get(key);
358         if (p == null) {
359            p = MutableProperty.create(key, value);
360            if (! p.isEmpty())
361               properties.put(key, p);
362         } else {
363            p.set(value);
364            if (p.isEmpty())
365               properties.remove(key);
366            else
367               properties.put(key, p);
368         }
369      }
370
371      synchronized void addTo(String key, String arg, Object value) {
372         MutableProperty p = properties.get(key);
373         if (p == null) {
374            p = MutableProperty.create(key, null);
375            p.add(arg, value);
376            if (! p.isEmpty())
377               properties.put(key, p);
378         } else {
379            p.add(arg, value);
380            if (p.isEmpty())
381               properties.remove(key);
382            else
383               properties.put(key, p);
384         }
385      }
386
387      synchronized void removeFrom(String key, Object value) {
388         MutableProperty p = properties.get(key);
389         if (p == null) {
390            // Create property anyway to generate a good error message.
391            p = MutableProperty.create(key, null);
392            p.remove(value);
393         } else {
394            p.remove(value);
395            if (p.isEmpty())
396               properties.remove(key);
397            else
398               properties.put(key, p);
399         }
400      }
401
402      synchronized boolean isEmpty() {
403         return properties.isEmpty();
404      }
405
406      synchronized PropertyGroup build() {
407         return new PropertyGroup(properties);
408      }
409   }
410
411   //-------------------------------------------------------------------------------------------------------------------
412   // MutableProperty
413   //-------------------------------------------------------------------------------------------------------------------
414
415   static abstract class MutableProperty {
416      final String name;
417      final PropertyType type;
418
419      MutableProperty(String name, PropertyType type) {
420         this.name = name;
421         this.type = type;
422      }
423
424      static MutableProperty create(String name, Object value) {
425         int i = name.lastIndexOf('.');
426         String type = i == -1 ? "s" : name.substring(i+1);
427         PropertyType pt = SUFFIX_MAP.get(type);
428
429         if (pt == null)
430            throw new ConfigException("Invalid type specified on property ''{0}''", name);
431
432         switch (pt) {
433            case STRING:
434            case BOOLEAN:
435            case INTEGER:
436            case CLASS:
437            case OBJECT:  return new MutableSimpleProperty(name, pt, value);
438            case SET_STRING:
439            case SET_INTEGER:
440            case SET_CLASS: return new MutableSetProperty(name, pt, value);
441            case LIST_STRING:
442            case LIST_INTEGER:
443            case LIST_CLASS:
444            case LIST_OBJECT: return new MutableListProperty(name, pt, value);
445            case SORTED_MAP_STRING:
446            case SORTED_MAP_INTEGER:
447            case SORTED_MAP_CLASS:
448            case SORTED_MAP_OBJECT: return new MutableMapProperty(name, pt, value);
449            case ORDERED_MAP_STRING:
450            case ORDERED_MAP_INTEGER:
451            case ORDERED_MAP_CLASS:
452            case ORDERED_MAP_OBJECT: return new MutableLinkedMapProperty(name, pt, value);
453            default: return new MutableSimpleProperty(name, PropertyType.STRING, value);
454         }
455      }
456
457      abstract Property build();
458
459      abstract boolean isEmpty();
460
461      abstract void set(Object value);
462
463      abstract void apply(Object value);
464
465      abstract Object peek();
466
467      void add(String arg, Object value) {
468         throw new ConfigException("Cannot add value {0} ({1}) to property ''{2}'' ({3}).", string(value), className(value), name, type);
469      }
470
471      void remove(Object value) {
472         throw new ConfigException("Cannot remove value {0} ({1}) from property ''{2}'' ({3}).", string(value), className(value), name, type);
473      }
474
475      Object convert(Object value) {
476         return value == null ? null : type.converter.convert(value);
477      }
478   }
479
480   //-------------------------------------------------------------------------------------------------------------------
481   // MutableSimpleProperty
482   //-------------------------------------------------------------------------------------------------------------------
483
484   static class MutableSimpleProperty extends MutableProperty {
485      private Object value;
486
487      MutableSimpleProperty(String name, PropertyType type, Object value) {
488         super(name, type);
489         set(value);
490      }
491
492      @Override /* MutableProperty */
493      synchronized Property build() {
494         return new Property(name, value, type);
495      }
496
497      @Override /* MutableProperty */
498      synchronized void set(Object value) {
499         this.value = convert(value);
500      }
501
502      @Override /* MutableProperty */
503      synchronized void apply(Object value) {
504         this.value = convert(value);
505      }
506
507      @Override /* MutableProperty */
508      synchronized boolean isEmpty() {
509         return this.value == null;
510      }
511
512      @Override /* MutableProperty */
513      synchronized Object peek() {
514         return value;
515      }
516   }
517
518   //-------------------------------------------------------------------------------------------------------------------
519   // MutableSetProperty
520   //-------------------------------------------------------------------------------------------------------------------
521
522   static class MutableSetProperty extends MutableProperty {
523      private final Set<Object> set;
524
525      MutableSetProperty(String name, PropertyType type, Object value) {
526         super(name, type);
527         set = new ConcurrentSkipListSet<>(type.comparator());
528         set(value);
529      }
530
531      @Override /* MutableProperty */
532      synchronized Property build() {
533         return new Property(name, unmodifiableSet(new LinkedHashSet<>(set)), type);
534      }
535
536      @Override /* MutableProperty */
537      synchronized void set(Object value) {
538         try {
539            Set<Object> newSet = merge(set, type.converter, value);
540            set.clear();
541            set.addAll(newSet);
542         } catch (Exception e) {
543            throw new ConfigException(e, "Cannot set value {0} ({1}) on property ''{2}'' ({3}).", string(value), className(value), name, type);
544         }
545      }
546
547      @Override /* MutableProperty */
548      synchronized void apply(Object values) {
549         set.addAll((Set<?>)values);
550      }
551
552      @Override /* MutableProperty */
553      synchronized void add(String arg, Object o) {
554         if (arg != null)
555            throw new ConfigException("Cannot use argument ''{0}'' on add command for property ''{1}'' ({2})", arg, name, type);
556         try {
557            set.addAll(normalize(type.converter, o));
558         } catch (Exception e) {
559            throw new ConfigException(e, "Cannot add value {0} ({1}) to property ''{2}'' ({3}).", string(o), className(o), name, type);
560         }
561      }
562
563      @Override /* MutableProperty */
564      synchronized void remove(Object o) {
565         try {
566            set.removeAll(normalize(type.converter, o));
567         } catch (Exception e) {
568            throw new ConfigException(e, "Cannot remove value {0} ({1}) from property ''{2}'' ({3}).", string(o), className(o), name, type);
569         }
570      }
571
572      @Override /* MutableProperty */
573      synchronized boolean isEmpty() {
574         return set.isEmpty();
575      }
576
577      @Override /* MutableProperty */
578      synchronized Object peek() {
579         return set;
580      }
581   }
582
583   //-------------------------------------------------------------------------------------------------------------------
584   // MutableListProperty
585   //-------------------------------------------------------------------------------------------------------------------
586
587   static class MutableListProperty extends MutableProperty {
588      private final List<Object> list = synchronizedList(new LinkedList<>());
589
590      MutableListProperty(String name, PropertyType type, Object value) {
591         super(name, type);
592         set(value);
593      }
594
595      @Override /* MutableProperty */
596      synchronized Property build() {
597         return new Property(name, unmodifiableList(new ArrayList<>(list)), type);
598      }
599
600      @Override /* MutableProperty */
601      synchronized void set(Object value) {
602         try {
603            List<Object> newList = merge(list, type.converter, value);
604            list.clear();
605            list.addAll(newList);
606         } catch (Exception e) {
607            throw new ConfigException(e, "Cannot set value {0} ({1}) on property ''{2}'' ({3}).", string(value), className(value), name, type);
608         }
609      }
610
611      @Override /* MutableProperty */
612      synchronized void apply(Object values) {
613         list.addAll((List<?>)values);
614      }
615
616      @Override /* MutableProperty */
617      synchronized void add(String arg, Object o) {
618         if (arg != null && ! StringUtils.isNumeric(arg))
619            throw new ConfigException("Invalid argument ''{0}'' on add command for property ''{1}'' ({2})", arg, name, type);
620
621         int index = arg == null ? 0 : Integer.parseInt(arg);
622         if (index < 0)
623            index = 0;
624         else if (index > list.size())
625            index = list.size();
626
627         try {
628            List<Object> l = normalize(type.converter, o);
629            list.removeAll(l);
630            list.addAll(index, l);
631         } catch (Exception e) {
632            throw new ConfigException(e, "Cannot add value {0} ({1}) to property ''{2}'' ({3}).", string(o), className(o), name, type);
633         }
634      }
635
636      @Override /* MutableProperty */
637      synchronized void remove(Object o) {
638         try {
639            list.removeAll(normalize(type.converter, o));
640         } catch (Exception e) {
641            throw new ConfigException(e, "Cannot remove value {0} ({1}) from property ''{2}'' ({3}).", string(o), className(o), name, type);
642         }
643      }
644
645      @Override /* MutableProperty */
646      synchronized boolean isEmpty() {
647         return list.isEmpty();
648      }
649
650      @Override /* MutableProperty */
651      synchronized Object peek() {
652         return list;
653      }
654   }
655
656   //-------------------------------------------------------------------------------------------------------------------
657   // MutableMapProperty
658   //-------------------------------------------------------------------------------------------------------------------
659
660   @SuppressWarnings("rawtypes")
661   static class MutableMapProperty extends MutableProperty {
662      protected Map<String,Object> map;
663
664      MutableMapProperty(String name, PropertyType type, Object value) {
665         super(name, type);
666         this.map = createMap();
667         set(value);
668      }
669
670      protected Map<String,Object> createMap() {
671         return new ConcurrentHashMap<>();
672      }
673
674      @Override /* MutableProperty */
675      synchronized Property build() {
676         return new Property(name, unmodifiableMap(new TreeMap<>(map)), type);
677      }
678
679      @Override /* MutableProperty */
680      synchronized void set(Object value) {
681         this.map.clear();
682         add(null, value);
683      }
684
685      @SuppressWarnings("unchecked")
686      @Override /* MutableProperty */
687      synchronized void apply(Object values) {
688         for (Map.Entry<String,Object> e : ((Map<String,Object>)values).entrySet())
689            add(e.getKey(), e.getValue());
690      }
691
692      @Override /* MutableProperty */
693      synchronized void add(String arg, Object o) {
694         if (arg != null) {
695            o = convert(o);
696            if (o == null)
697               this.map.remove(arg);
698            else
699               this.map.put(arg, o);
700
701         } else if (o != null) {
702            if (o instanceof Map) {
703               Map m = (Map)o;
704               for (Map.Entry e : (Set<Map.Entry>)m.entrySet())
705                  if (e.getKey() != null)
706                     add(e.getKey().toString(), e.getValue());
707            } else if (isObjectMap(o)) {
708               try {
709                  add(null, new ObjectMap(o.toString()));
710               } catch (Exception e) {
711                  throw new ConfigException(e, "Cannot add {0} ({1}) to property ''{2}'' ({3}).", string(o), className(o), name, type);
712               }
713            } else {
714               throw new ConfigException("Cannot add {0} ({1}) to property ''{2}'' ({3}).", string(o), className(o), name, type);
715            }
716         }
717      }
718
719      @Override /* MutableProperty */
720      synchronized boolean isEmpty() {
721         return this.map.isEmpty();
722      }
723
724      @Override /* MutableProperty */
725      synchronized Object peek() {
726         return map;
727      }
728   }
729
730   //-------------------------------------------------------------------------------------------------------------------
731   // MutableLinkedMapProperty
732   //-------------------------------------------------------------------------------------------------------------------
733
734   static class MutableLinkedMapProperty extends MutableMapProperty {
735
736      MutableLinkedMapProperty(String name, PropertyType type, Object value) {
737         super(name, type, value);
738         set(value);
739      }
740
741      @Override
742      protected Map<String,Object> createMap() {
743         return synchronizedMap(new LinkedHashMap<String,Object>());
744      }
745
746      @Override /* MutableProperty */
747      synchronized Property build() {
748         return new Property(name, unmodifiableMap(new LinkedHashMap<>(map)), type);
749      }
750   }
751
752
753   //-------------------------------------------------------------------------------------------------------------------
754   // Utility methods
755   //-------------------------------------------------------------------------------------------------------------------
756
757   static Set<Object> merge(Set<Object> oldSet, PropertyConverter<?> pc, Object o) throws Exception {
758      return merge(oldSet, new LinkedHashSet<>(), normalize(pc, o));
759   }
760
761   private static Set<Object> merge(Set<Object> oldSet, Set<Object> newSet, List<Object> l) {
762      for (Object o : l) {
763         if (isNone(o))
764            newSet.clear();
765         else if (isInherit(o))
766            newSet.addAll(oldSet);
767         else
768            newSet.add(o);
769      }
770      return newSet;
771   }
772
773   static List<Object> merge(List<Object> oldList, PropertyConverter<?> pc, Object o) throws Exception {
774      return merge(oldList, new ArrayList<>(), normalize(pc, o));
775   }
776
777   private static List<Object> merge(List<Object> oldList, List<Object> newList, List<Object> l) {
778      for (Object o : l) {
779         if (isIndexed(o)) {
780            Matcher lm = INDEXED_LINK_PATTERN.matcher(o.toString());
781            lm.matches();
782            String key = lm.group(1);
783            int i2 = Math.min(newList.size(), Integer.parseInt(lm.group(2)));
784            String remainder = lm.group(3);
785            newList.add(i2, key.isEmpty() ? remainder : key + ":" + remainder);
786         } else if (isNone(o)) {
787            newList.clear();
788         } else if (isInherit(o)) {
789            if (oldList != null)
790               for (Object o2 : oldList)
791                  newList.add(o2);
792         } else {
793            newList.remove(o);
794            newList.add(o);
795         }
796      }
797
798      return newList;
799   }
800
801   static List<Object> normalize(PropertyConverter<?> pc, Object o) throws Exception {
802      return normalize(new ArrayList<>(), pc, o);
803   }
804
805   @SuppressWarnings("unchecked")
806   static List<Object> normalize(List<Object> l, PropertyConverter<?> pc, Object o) throws Exception {
807      if (o != null) {
808         if (o.getClass().isArray()) {
809            for (int i = 0; i < Array.getLength(o); i++)
810               normalize(l, pc, Array.get(o, i));
811         } else if (o instanceof Collection) {
812            for (Object o2 : (Collection<Object>)o)
813               normalize(l, pc, o2);
814         } else if (isObjectList(o)) {
815            normalize(l, pc, new ObjectList(o.toString()));
816         } else {
817            l.add(pc == null ? o : pc.convert(o));
818         }
819      }
820      return l;
821   }
822
823   static String string(Object value) {
824      return SimpleJsonSerializer.DEFAULT.toString(value);
825   }
826
827   static String className(Object value) {
828      return value.getClass().getSimpleName();
829   }
830
831   static boolean isObjectMap(Object o) {
832      if (o instanceof CharSequence) {
833         String s = o.toString();
834         return (s.startsWith("{") && s.endsWith("}") && BeanContext.DEFAULT != null);
835      }
836      return false;
837   }
838
839   private static String group(String key) {
840      if (key == null || key.indexOf('.') == -1)
841         return "";
842      return key.substring(0, key.indexOf('.'));
843   }
844
845   static boolean isObjectList(Object o) {
846      if (o instanceof CharSequence) {
847         String s = o.toString();
848         return (s.startsWith("[") && s.endsWith("]") && BeanContext.DEFAULT != null);
849      }
850      return false;
851   }
852
853   private static boolean isNone(Object o) {
854      if (o instanceof CharSequence) {
855         String s = o.toString();
856         return "NONE".equals(s);
857      }
858      return false;
859   }
860
861   private static boolean isIndexed(Object o) {
862      if (o instanceof CharSequence) {
863         String s = o.toString();
864         return s.indexOf('[') != -1 && INDEXED_LINK_PATTERN.matcher(s).matches();
865      }
866      return false;
867   }
868
869   private static final Pattern INDEXED_LINK_PATTERN = Pattern.compile("(?s)(\\S*)\\[(\\d+)\\]\\:(.*)");
870
871   private static boolean isInherit(Object o) {
872      if (o instanceof CharSequence) {
873         String s = o.toString();
874         return "INHERIT".equals(s);
875      }
876      return false;
877   }
878}