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