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