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