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) { 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>For MAPs, this can be <jk>null</jk> if we're adding a map, or a string key if we're adding an entry. 345 * @param value 346 * The new value to add to the property. 347 * <br>For SETs and LISTs, this can be a single value, Collection, array, or JSON array string. 348 * <br>For MAPs, this can be a single value, Map, or JSON object string. 349 * <br><jk>null</jk> values have the effect of removing entries. 350 * @return This object (for method chaining). 351 * @throws ConfigException If property is not a SET/LIST/MAP property, or the argument is invalid. 352 */ 353 public synchronized PropertyStoreBuilder addTo(String key, String arg, Object value) { 354 propertyStore = null; 355 String g = group(key); 356 String n = g.isEmpty() ? key : key.substring(g.length()+1); 357 358 PropertyGroupBuilder gb = groups.get(g); 359 if (gb == null) { 360 gb = new PropertyGroupBuilder(); 361 groups.put(g, gb); 362 } 363 364 gb.addTo(n, arg, value); 365 366 if (gb.isEmpty()) 367 groups.remove(g); 368 369 return this; 370 } 371 372 /** 373 * Adds a value to a SET, LIST, or MAP property. 374 * 375 * <p> 376 * Shortcut for calling <code>addTo(key, <jk>null</jk>, value);</code>. 377 * 378 * @param key The property key. 379 * @param value 380 * The new value to add to the property. 381 * <br>For SETs and LISTs, this can be a single value, Collection, array, or JSON array string. 382 * <br>for MAPs, this can be a single value, Map, or JSON object string. 383 * <br><jk>null</jk> values have the effect of removing entries. 384 * @return This object (for method chaining). 385 * @throws ConfigException If property is not a SET/LIST/MAP property, or the argument is invalid. 386 */ 387 public synchronized PropertyStoreBuilder addTo(String key, Object value) { 388 propertyStore = null; 389 return addTo(key, null, value); 390 } 391 392 /** 393 * Removes a value from a SET or LIST property. 394 * 395 * @param key The property key. 396 * @param value The property value in the property. 397 * @return This object (for method chaining). 398 * @throws ConfigException If property is not a SET or LIST property. 399 */ 400 public synchronized PropertyStoreBuilder removeFrom(String key, Object value) { 401 propertyStore = null; 402 String g = group(key); 403 String n = g.isEmpty() ? key : key.substring(g.length()+1); 404 405 PropertyGroupBuilder gb = groups.get(g); 406 407 // Create property group anyway to generate a good error message. 408 if (gb == null) 409 gb = new PropertyGroupBuilder(); 410 411 gb.removeFrom(n, value); 412 413 if (gb.isEmpty()) 414 groups.remove(g); 415 416 return this; 417 } 418 419 /** 420 * Peeks at a property value. 421 * 422 * <p> 423 * Used for debugging purposes. 424 * 425 * @param key The property key. 426 * @return The property value, or <jk>null</jk> if it doesn't exist. 427 */ 428 public Object peek(String key) { 429 String g = group(key); 430 String n = g.isEmpty() ? key : key.substring(g.length()+1); 431 432 PropertyGroupBuilder gb = groups.get(g); 433 434 // Create property group anyway to generate a good error message. 435 if (gb == null) 436 return null; 437 438 MutableProperty bp = gb.properties.get(n); 439 if (bp == null) 440 return null; 441 442 return bp.peek(); 443 } 444 445 /** 446 * Same as {@link #peek(String)} but converts the value to the specified type. 447 * 448 * @param <T> The type to convert to. 449 * @param c The type to convert to. 450 * @param key The property key. 451 * @return The property value, or <jk>null</jk> if it doesn't exist. 452 */ 453 public <T> T peek(Class<T> c, String key) { 454 Object o = peek(key); 455 if (o == null) 456 return null; 457 return BeanContext.DEFAULT.createBeanSession().convertToType(o, c); 458 } 459 460 /** 461 * Clears all entries in this property store. 462 */ 463 public synchronized void clear() { 464 propertyStore = null; 465 this.groups.clear(); 466 } 467 468 //------------------------------------------------------------------------------------------------------------------- 469 // PropertyGroupBuilder 470 //------------------------------------------------------------------------------------------------------------------- 471 472 static class PropertyGroupBuilder { 473 final Map<String,MutableProperty> properties = new ConcurrentSkipListMap<>(); 474 475 PropertyGroupBuilder() { 476 this(emptyMap()); 477 } 478 479 synchronized void apply(PropertyGroup copyFrom) { 480 for (Map.Entry<String,Property> e : copyFrom.properties.entrySet()) { 481 String pName = e.getKey(); 482 MutableProperty p1 = properties.get(pName); 483 Property p2 = e.getValue(); 484 if (p1 == null) 485 properties.put(pName, p2.mutable()); 486 else 487 p1.apply(p2.value); 488 } 489 } 490 491 PropertyGroupBuilder(Map<String,Property> properties) { 492 for (Map.Entry<String,Property> p : properties.entrySet()) 493 this.properties.put(p.getKey(), p.getValue().mutable()); 494 } 495 496 synchronized void set(String key, Object value) { 497 MutableProperty p = properties.get(key); 498 if (p == null) { 499 p = MutableProperty.create(key, value); 500 if (! p.isEmpty()) 501 properties.put(key, p); 502 } else { 503 p.set(value); 504 if (p.isEmpty()) 505 properties.remove(key); 506 else 507 properties.put(key, p); 508 } 509 } 510 511 synchronized void addTo(String key, String arg, Object value) { 512 MutableProperty p = properties.get(key); 513 if (p == null) { 514 p = MutableProperty.create(key, null); 515 p.add(arg, value); 516 if (! p.isEmpty()) 517 properties.put(key, p); 518 } else { 519 p.add(arg, value); 520 if (p.isEmpty()) 521 properties.remove(key); 522 else 523 properties.put(key, p); 524 } 525 } 526 527 synchronized void removeFrom(String key, Object value) { 528 MutableProperty p = properties.get(key); 529 if (p == null) { 530 // Create property anyway to generate a good error message. 531 p = MutableProperty.create(key, null); 532 p.remove(value); 533 } else { 534 p.remove(value); 535 if (p.isEmpty()) 536 properties.remove(key); 537 else 538 properties.put(key, p); 539 } 540 } 541 542 synchronized boolean isEmpty() { 543 return properties.isEmpty(); 544 } 545 546 synchronized PropertyGroup build() { 547 return new PropertyGroup(properties); 548 } 549 550 /** 551 * Used by the <c>toString()</c> method for debugging. 552 * 553 * @param bs The bean session. 554 * @return This object converted to a map. 555 */ 556 public Map<String,MutableProperty> swap(BeanSession bs) { 557 return properties; 558 } 559 } 560 561 //------------------------------------------------------------------------------------------------------------------- 562 // MutableProperty 563 //------------------------------------------------------------------------------------------------------------------- 564 565 static abstract class MutableProperty { 566 final String name; 567 final PropertyType type; 568 569 MutableProperty(String name, PropertyType type) { 570 this.name = name; 571 this.type = type; 572 } 573 574 static MutableProperty create(String name, Object value) { 575 int i = name.lastIndexOf('.'); 576 String type = i == -1 ? "s" : name.substring(i+1); 577 PropertyType pt = SUFFIX_MAP.get(type); 578 579 if (pt == null) 580 throw new ConfigException("Invalid type specified on property ''{0}''", name); 581 582 switch (pt) { 583 case STRING: 584 case BOOLEAN: 585 case INTEGER: 586 case CLASS: 587 case OBJECT: return new MutableSimpleProperty(name, pt, value); 588 case SET_STRING: 589 case SET_INTEGER: 590 case SET_CLASS: return new MutableSetProperty(name, pt, value); 591 case LIST_STRING: 592 case LIST_INTEGER: 593 case LIST_CLASS: 594 case LIST_OBJECT: return new MutableListProperty(name, pt, value); 595 case SORTED_MAP_STRING: 596 case SORTED_MAP_INTEGER: 597 case SORTED_MAP_CLASS: 598 case SORTED_MAP_OBJECT: return new MutableMapProperty(name, pt, value); 599 case ORDERED_MAP_STRING: 600 case ORDERED_MAP_INTEGER: 601 case ORDERED_MAP_CLASS: 602 case ORDERED_MAP_OBJECT: return new MutableLinkedMapProperty(name, pt, value); 603 default: return new MutableSimpleProperty(name, PropertyType.STRING, value); 604 } 605 } 606 607 abstract Property build(); 608 609 abstract boolean isEmpty(); 610 611 abstract void set(Object value); 612 613 abstract void apply(Object value); 614 615 abstract Object peek(); 616 617 void add(String arg, Object value) { 618 throw new ConfigException("Cannot add value {0} ({1}) to property ''{2}'' ({3}).", string(value), className(value), name, type); 619 } 620 621 void remove(Object value) { 622 throw new ConfigException("Cannot remove value {0} ({1}) from property ''{2}'' ({3}).", string(value), className(value), name, type); 623 } 624 625 Object convert(Object value) { 626 return value == null ? null : type.converter.convert(value); 627 } 628 629 @Override /* Object */ 630 public String toString() { 631 return StringUtils.stringify(peek()); 632 } 633 } 634 635 //------------------------------------------------------------------------------------------------------------------- 636 // MutableSimpleProperty 637 //------------------------------------------------------------------------------------------------------------------- 638 639 static class MutableSimpleProperty extends MutableProperty { 640 private Object value; 641 642 MutableSimpleProperty(String name, PropertyType type, Object value) { 643 super(name, type); 644 set(value); 645 } 646 647 @Override /* MutableProperty */ 648 synchronized Property build() { 649 return new Property(name, value, type); 650 } 651 652 @Override /* MutableProperty */ 653 synchronized void set(Object value) { 654 this.value = convert(value); 655 } 656 657 @Override /* MutableProperty */ 658 synchronized void apply(Object value) { 659 this.value = convert(value); 660 } 661 662 @Override /* MutableProperty */ 663 synchronized boolean isEmpty() { 664 return this.value == null; 665 } 666 667 @Override /* MutableProperty */ 668 synchronized Object peek() { 669 return value; 670 } 671 } 672 673 //------------------------------------------------------------------------------------------------------------------- 674 // MutableSetProperty 675 //------------------------------------------------------------------------------------------------------------------- 676 677 static class MutableSetProperty extends MutableProperty { 678 private final Set<Object> set; 679 680 MutableSetProperty(String name, PropertyType type, Object value) { 681 super(name, type); 682 set = new ConcurrentSkipListSet<>(type.comparator()); 683 set(value); 684 } 685 686 @Override /* MutableProperty */ 687 synchronized Property build() { 688 return new Property(name, unmodifiableSet(new LinkedHashSet<>(set)), type); 689 } 690 691 @Override /* MutableProperty */ 692 synchronized void set(Object value) { 693 try { 694 Set<Object> newSet = merge(set, type.converter, value); 695 set.clear(); 696 set.addAll(newSet); 697 } catch (Exception e) { 698 throw new ConfigException(e, "Cannot set value {0} ({1}) on property ''{2}'' ({3}).", string(value), className(value), name, type); 699 } 700 } 701 702 @Override /* MutableProperty */ 703 synchronized void apply(Object values) { 704 set.addAll((Set<?>)values); 705 } 706 707 @Override /* MutableProperty */ 708 synchronized void add(String arg, Object o) { 709 if (arg != null) 710 throw new ConfigException("Cannot use argument ''{0}'' on add command for property ''{1}'' ({2})", arg, name, type); 711 try { 712 set.addAll(normalize(type.converter, o)); 713 } catch (Exception e) { 714 throw new ConfigException(e, "Cannot add value {0} ({1}) to property ''{2}'' ({3}).", string(o), className(o), name, type); 715 } 716 } 717 718 @Override /* MutableProperty */ 719 synchronized void remove(Object o) { 720 try { 721 set.removeAll(normalize(type.converter, o)); 722 } catch (Exception e) { 723 throw new ConfigException(e, "Cannot remove value {0} ({1}) from property ''{2}'' ({3}).", string(o), className(o), name, type); 724 } 725 } 726 727 @Override /* MutableProperty */ 728 synchronized boolean isEmpty() { 729 return set.isEmpty(); 730 } 731 732 @Override /* MutableProperty */ 733 synchronized Object peek() { 734 return set; 735 } 736 } 737 738 //------------------------------------------------------------------------------------------------------------------- 739 // MutableListProperty 740 //------------------------------------------------------------------------------------------------------------------- 741 742 static class MutableListProperty extends MutableProperty { 743 private final List<Object> list = synchronizedList(new LinkedList<>()); 744 745 MutableListProperty(String name, PropertyType type, Object value) { 746 super(name, type); 747 set(value); 748 } 749 750 @Override /* MutableProperty */ 751 synchronized Property build() { 752 return new Property(name, unmodifiableList(new ArrayList<>(list)), type); 753 } 754 755 @Override /* MutableProperty */ 756 synchronized void set(Object value) { 757 try { 758 List<Object> newList = merge(list, type.converter, value); 759 list.clear(); 760 list.addAll(newList); 761 } catch (Exception e) { 762 throw new ConfigException(e, "Cannot set value {0} ({1}) on property ''{2}'' ({3}).", string(value), className(value), name, type); 763 } 764 } 765 766 @Override /* MutableProperty */ 767 synchronized void apply(Object values) { 768 list.addAll((List<?>)values); 769 } 770 771 @Override /* MutableProperty */ 772 synchronized void add(String arg, Object o) { 773 if (arg != null && ! StringUtils.isNumeric(arg)) 774 throw new ConfigException("Invalid argument ''{0}'' on add command for property ''{1}'' ({2})", arg, name, type); 775 776 int index = arg == null ? 0 : Integer.parseInt(arg); 777 if (index < 0) 778 index = 0; 779 else if (index > list.size()) 780 index = list.size(); 781 782 try { 783 List<Object> l = normalize(type.converter, o); 784 list.removeAll(l); 785 list.addAll(index, l); 786 } catch (Exception e) { 787 throw new ConfigException(e, "Cannot add value {0} ({1}) to property ''{2}'' ({3}).", string(o), className(o), name, type); 788 } 789 } 790 791 @Override /* MutableProperty */ 792 synchronized void remove(Object o) { 793 try { 794 list.removeAll(normalize(type.converter, o)); 795 } catch (Exception e) { 796 throw new ConfigException(e, "Cannot remove value {0} ({1}) from property ''{2}'' ({3}).", string(o), className(o), name, type); 797 } 798 } 799 800 @Override /* MutableProperty */ 801 synchronized boolean isEmpty() { 802 return list.isEmpty(); 803 } 804 805 @Override /* MutableProperty */ 806 synchronized Object peek() { 807 return list; 808 } 809 } 810 811 //------------------------------------------------------------------------------------------------------------------- 812 // MutableMapProperty 813 //------------------------------------------------------------------------------------------------------------------- 814 815 @SuppressWarnings("rawtypes") 816 static class MutableMapProperty extends MutableProperty { 817 protected Map<String,Object> map; 818 819 MutableMapProperty(String name, PropertyType type, Object value) { 820 super(name, type); 821 this.map = createMap(); 822 set(value); 823 } 824 825 protected Map<String,Object> createMap() { 826 return new ConcurrentHashMap<>(); 827 } 828 829 @Override /* MutableProperty */ 830 synchronized Property build() { 831 return new Property(name, unmodifiableMap(new TreeMap<>(map)), type); 832 } 833 834 @Override /* MutableProperty */ 835 synchronized void set(Object value) { 836 this.map.clear(); 837 add(null, value); 838 } 839 840 @SuppressWarnings("unchecked") 841 @Override /* MutableProperty */ 842 synchronized void apply(Object values) { 843 for (Map.Entry<String,Object> e : ((Map<String,Object>)values).entrySet()) 844 add(e.getKey(), e.getValue()); 845 } 846 847 @Override /* MutableProperty */ 848 synchronized void add(String arg, Object o) { 849 if (arg != null) { 850 o = convert(o); 851 if (o == null) 852 this.map.remove(arg); 853 else 854 this.map.put(arg, o); 855 856 } else if (o != null) { 857 if (o instanceof Map) { 858 Map m = (Map)o; 859 for (Map.Entry e : (Set<Map.Entry>)m.entrySet()) 860 if (e.getKey() != null) 861 add(e.getKey().toString(), e.getValue()); 862 } else if (isObjectMap(o)) { 863 try { 864 add(null, new ObjectMap(o.toString())); 865 } catch (Exception e) { 866 throw new ConfigException(e, "Cannot add {0} ({1}) to property ''{2}'' ({3}).", string(o), className(o), name, type); 867 } 868 } else { 869 throw new ConfigException("Cannot add {0} ({1}) to property ''{2}'' ({3}).", string(o), className(o), name, type); 870 } 871 } 872 } 873 874 @Override /* MutableProperty */ 875 synchronized boolean isEmpty() { 876 return this.map.isEmpty(); 877 } 878 879 @Override /* MutableProperty */ 880 synchronized Object peek() { 881 return map; 882 } 883 } 884 885 //------------------------------------------------------------------------------------------------------------------- 886 // MutableLinkedMapProperty 887 //------------------------------------------------------------------------------------------------------------------- 888 889 static class MutableLinkedMapProperty extends MutableMapProperty { 890 891 MutableLinkedMapProperty(String name, PropertyType type, Object value) { 892 super(name, type, value); 893 set(value); 894 } 895 896 @Override 897 protected Map<String,Object> createMap() { 898 return synchronizedMap(new LinkedHashMap<String,Object>()); 899 } 900 901 @Override /* MutableProperty */ 902 synchronized Property build() { 903 return new Property(name, unmodifiableMap(new LinkedHashMap<>(map)), type); 904 } 905 } 906 907 908 //------------------------------------------------------------------------------------------------------------------- 909 // Utility methods 910 //------------------------------------------------------------------------------------------------------------------- 911 912 static Set<Object> merge(Set<Object> oldSet, PropertyConverter<?> pc, Object o) throws Exception { 913 return merge(oldSet, new LinkedHashSet<>(), normalize(pc, o)); 914 } 915 916 private static Set<Object> merge(Set<Object> oldSet, Set<Object> newSet, List<Object> l) { 917 for (Object o : l) { 918 if (isNone(o)) 919 newSet.clear(); 920 else if (isInherit(o)) 921 newSet.addAll(oldSet); 922 else 923 newSet.add(o); 924 } 925 return newSet; 926 } 927 928 static List<Object> merge(List<Object> oldList, PropertyConverter<?> pc, Object o) throws Exception { 929 return merge(oldList, new ArrayList<>(), normalize(pc, o)); 930 } 931 932 private static List<Object> merge(List<Object> oldList, List<Object> newList, List<Object> l) { 933 for (Object o : l) { 934 if (isIndexed(o)) { 935 Matcher lm = INDEXED_LINK_PATTERN.matcher(o.toString()); 936 lm.matches(); 937 String key = lm.group(1); 938 int i2 = Math.min(newList.size(), Integer.parseInt(lm.group(2))); 939 String remainder = lm.group(3); 940 newList.add(i2, key.isEmpty() ? remainder : key + ":" + remainder); 941 } else if (isNone(o)) { 942 newList.clear(); 943 } else if (isInherit(o)) { 944 if (oldList != null) 945 for (Object o2 : oldList) 946 newList.add(o2); 947 } else { 948 newList.remove(o); 949 newList.add(o); 950 } 951 } 952 953 return newList; 954 } 955 956 static List<Object> normalize(PropertyConverter<?> pc, Object o) throws Exception { 957 return normalize(new ArrayList<>(), pc, o); 958 } 959 960 @SuppressWarnings("unchecked") 961 static List<Object> normalize(List<Object> l, PropertyConverter<?> pc, Object o) throws Exception { 962 if (o != null) { 963 if (o.getClass().isArray()) { 964 for (int i = 0; i < Array.getLength(o); i++) 965 normalize(l, pc, Array.get(o, i)); 966 } else if (o instanceof Collection) { 967 for (Object o2 : (Collection<Object>)o) 968 normalize(l, pc, o2); 969 } else if (isObjectList(o)) { 970 normalize(l, pc, new ObjectList(o.toString())); 971 } else { 972 l.add(pc == null ? o : pc.convert(o)); 973 } 974 } 975 return l; 976 } 977 978 static String string(Object value) { 979 return SimpleJsonSerializer.DEFAULT.toString(value); 980 } 981 982 static String className(Object value) { 983 return value.getClass().getSimpleName(); 984 } 985 986 static boolean isObjectMap(Object o) { 987 if (o instanceof CharSequence) { 988 String s = o.toString(); 989 return (s.startsWith("{") && s.endsWith("}") && BeanContext.DEFAULT != null); 990 } 991 return false; 992 } 993 994 private static String group(String key) { 995 if (key == null || key.indexOf('.') == -1) 996 return ""; 997 return key.substring(0, key.indexOf('.')); 998 } 999 1000 static boolean isObjectList(Object o) { 1001 if (o instanceof CharSequence) { 1002 String s = o.toString(); 1003 return (s.startsWith("[") && s.endsWith("]") && BeanContext.DEFAULT != null); 1004 } 1005 return false; 1006 } 1007 1008 private static boolean isNone(Object o) { 1009 if (o instanceof CharSequence) { 1010 String s = o.toString(); 1011 return "NONE".equals(s); 1012 } 1013 return false; 1014 } 1015 1016 private static boolean isIndexed(Object o) { 1017 if (o instanceof CharSequence) { 1018 String s = o.toString(); 1019 return s.indexOf('[') != -1 && INDEXED_LINK_PATTERN.matcher(s).matches(); 1020 } 1021 return false; 1022 } 1023 1024 private static final Pattern INDEXED_LINK_PATTERN = Pattern.compile("(?s)(\\S*)\\[(\\d+)\\]\\:(.*)"); 1025 1026 private static boolean isInherit(Object o) { 1027 if (o instanceof CharSequence) { 1028 String s = o.toString(); 1029 return "INHERIT".equals(s); 1030 } 1031 return false; 1032 } 1033 1034 @Override /* Object */ 1035 public String toString() { 1036 return SimpleJson.DEFAULT_READABLE.toString(groups); 1037 } 1038}