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 JsonSerializer.DEFAULT_LAX.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}