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 org.apache.juneau.PropertyType.*; 016import static org.apache.juneau.internal.CollectionUtils.*; 017 018import java.lang.reflect.*; 019import java.util.*; 020 021import org.apache.juneau.PropertyStoreBuilder.*; 022import org.apache.juneau.internal.*; 023import org.apache.juneau.json.*; 024 025 026/** 027 * Represents an immutable collection of properties. 028 * 029 * <p> 030 * The general idea behind a property store is to serve as a reusable configuration of an artifact (e.g. a Serializer) 031 * such that the artifact can be cached and reused if the property stores are 'equal'. 032 * 033 * <h5 class='topic'>Concept</h5> 034 * 035 * <p> 036 * For example, two serializers of the same type created with the same configuration will always end up being 037 * the same serializer: 038 * 039 * <p class='bcode'> 040 * WriterSerializer s1 = JsonSerializer.<jsm>create</jsm>().pojoSwaps(MySwap.<jk>class</jk>).simple().build(); 041 * WriterSerializer s2 = JsonSerializer.<jsm>create</jsm>().simple().pojoSwaps(MySwap.<jk>class</jk>).build(); 042 * <jk>assert</jk>(s1 == s2); 043 * </p> 044 * 045 * <p> 046 * This has the effect of significantly improving performance, especially if you're creating many Serializers and 047 * Parsers. 048 * 049 * <h5 class='topic'>PropertyStoreBuilder</h5> 050 * 051 * <p> 052 * The {@link PropertyStoreBuilder} class is used to build up and instantiate immutable <code>PropertyStore</code> 053 * objects. 054 * 055 * <p> 056 * In the example above, the property store being built looks like the following: 057 * 058 * <p class='bcode'> 059 * PropertyStore ps = PropertyStore 060 * .<jsm>create</jsm>() 061 * .set(<js>"BeanContext.pojoSwaps.lc"</js>, MySwap.<jk>class</jk>) 062 * .set(<js>"JsonSerializer.simpleMode.b"</js>, <jk>true</jk>) 063 * .build(); 064 * </p> 065 * 066 * <p> 067 * Property stores are immutable, comparable, and their hashcodes are calculated exactly one time. 068 * That makes them particularly suited for use as hashmap keys, and thus for caching reusable serializers and parsers. 069 * 070 * <h5 class='topic'>Property naming convention</h5> 071 * 072 * <p> 073 * Property names must have the following format... 074 * <p class='bcode'> 075 * <js>"{class}.{name}.{type}"</js> 076 * </p> 077 * <p> 078 * ...where the parts consist of the following... 079 * <ul> 080 * <li><js>"{class}"</js> - The group name of the property (e.g. <js>"JsonSerializer"</js>). 081 * <br>It's always going to be the simple class name of the class it's associated with. 082 * <li><js>"{name}"</js> - The property name (e.g. <js>"useWhitespace"</js>). 083 * <li><js>"{type}"</js> - The property data type. 084 * <br>A 1 or 2 character string that identifies the data type of the property. 085 * <br>Valid values are: 086 * <ul> 087 * <li><js>"s"</js>: <code>String</code> 088 * <li><js>"b"</js>: <code>Boolean</code> 089 * <li><js>"i"</js>: <code>Integer</code> 090 * <li><js>"c"</js>: <code>Class</code> 091 * <li><js>"o"</js>: <code>Object</code> 092 * <li><js>"ss"</js>: <code>TreeSet<String></code> 093 * <li><js>"si"</js>: <code>TreeSet<Integer></code> 094 * <li><js>"sc"</js>: <code>TreeSet<Class></code> 095 * <li><js>"ls"</js>: <code>Linkedlist<String></code> 096 * <li><js>"li"</js>: <code>Linkedlist<Integer></code> 097 * <li><js>"lc"</js>: <code>Linkedlist<Class></code> 098 * <li><js>"lo"</js>: <code>Linkedlist<Object></code> 099 * <li><js>"sms"</js>: <code>TreeMap<String,String></code> 100 * <li><js>"smi"</js>: <code>TreeMap<String,Integer></code> 101 * <li><js>"smc"</js>: <code>TreeMap<String,Class></code> 102 * <li><js>"smo"</js>: <code>TreeMap<String,Object></code> 103 * <li><js>"oms"</js>: <code>LinkedHashMap<String,String></code> 104 * <li><js>"omi"</js>: <code>LinkedHashMap<String,Integer></code> 105 * <li><js>"omc"</js>: <code>LinkedHashMap<String,Class></code> 106 * <li><js>"omo"</js>: <code>LinkedHashMap<String,Object></code> 107 * </ul> 108 * </ul> 109 * 110 * <p> 111 * For example, <js>"BeanContext.pojoSwaps.lc"</js> refers to a property on the <code>BeanContext</code> class 112 * called <code>pojoSwaps</code> that has a data type of <code>List<Class></code>. 113 * 114 * <h5 class='topic'>Property value normalization</h5> 115 * 116 * <p> 117 * Property values get 'normalized' when they get set. 118 * For example, calling <code>propertyStore.set(<js>"BeanContext.debug.b"</js>, <js>"true"</js>)</code> will cause the property 119 * value to be converted to a boolean. 120 * 121 * <h5 class='topic'>Set types</h5> 122 * 123 * <p> 124 * The <js>"sX"</js> property types are sorted sets. 125 * <br>Use these for collections of objects where the order is not important. 126 * <br>Internally, a <code>TreeSet</code> is used so that the order in which you add elements does not affect the 127 * resulting order of the property. 128 * 129 * <h5 class='topic'>List types</h5> 130 * 131 * <p> 132 * The <js>"lX"</js> property types are ordered lists. 133 * <br>Use these in cases where the order in which entries are added is important. 134 * 135 * <p> 136 * Adding to a list property will cause the new entries to be added to the BEGINNING of the list. 137 * <br>This ensures that the resulting order of the list is in most-to-least importance. 138 * 139 * <p> 140 * For example, multiple calls to <code>pojoSwaps()</code> causes new entries to be added to the beginning of the list 141 * so that previous values can be 'overridden': 142 * <p class='bcode'> 143 * <jc>// Swap order: [MySwap2.class, MySwap1.class]</jc> 144 * JsonSerializer.create().pojoSwaps(MySwap1.<jk>class</jk>).pojoSwaps(MySwap2.<jk>class</jk>).build(); 145 * </p> 146 * 147 * <p> 148 * Note that the order is different when passing multiple values into the <code>pojoSwaps()</code> method, in which 149 * case the order should be first-match-wins: 150 * <p class='bcode'> 151 * <jc>// Swap order: [MySwap1.class, MySwap2.class]</jc> 152 * JsonSerializer.create().pojoSwaps(MySwap1.<jk>class</jk>,MySwap2.<jk>class</jk>).build(); 153 * </p> 154 * 155 * <p> 156 * Combined, the results look like this: 157 * <p class='bcode'> 158 * <jc>// Swap order: [MySwap4.class, MySwap3.class, MySwap1.class, MySwap2.class]</jc> 159 * JsonSerializer 160 * .create() 161 * .pojoSwaps(MySwap1.<jk>class</jk>,MySwap2.<jk>class</jk>) 162 * .pojoSwaps(MySwap3.<jk>class</jk>) 163 * .pojoSwaps(MySwap4.<jk>class</jk>) 164 * .build(); 165 * </p> 166 * 167 * <h5 class='topic'>Map types</h5> 168 * 169 * <p> 170 * The <js>"smX"</js> and <js>"omX"</js> are sorted and order maps respectively. 171 * 172 * <h5 class='topic'>Command properties</h5> 173 * 174 * <p> 175 * Set and list properties have the additional convenience 'command' names for adding and removing entries: 176 * <p class='bcode'> 177 * <js>"{class}.{name}.{type}/add"</js> <jc>// Add a value to the set/list.</jc> 178 * <js>"{class}.{name}.{type}/remove"</js> <jc>// Remove a value from the set/list.</jc> 179 * </p> 180 * 181 * <p> 182 * Map properties have the additional convenience property name for adding and removing map entries: 183 * <p class='bcode'> 184 * <js>"{class}.{name}.{type}/add.{key}"</js> <jc>// Add a map entry (or delete if the value is null).</jc> 185 * </p> 186 * 187 * <h5 class='topic'>Setting properties</h5> 188 * 189 * <p> 190 * TODO 191 * 192 * <h5 class='topic'>Retrieving properties</h5> 193 * 194 * <p> 195 * TODO 196 * 197 */ 198@SuppressWarnings("unchecked") 199public final class PropertyStore { 200 201 /** 202 * A default empty property store. 203 */ 204 public static PropertyStore DEFAULT = PropertyStore.create().build(); 205 206 final SortedMap<String,PropertyGroup> groups; 207 private final int hashCode; 208 209 // Created by PropertyStoreBuilder.build() 210 PropertyStore(Map<String,PropertyGroupBuilder> propertyMaps) { 211 TreeMap<String,PropertyGroup> m = new TreeMap<>(); 212 for (Map.Entry<String,PropertyGroupBuilder> p : propertyMaps.entrySet()) 213 m.put(p.getKey(), p.getValue().build()); 214 this.groups = Collections.unmodifiableSortedMap(m); 215 this.hashCode = groups.hashCode(); 216 } 217 218 /** 219 * Creates a new empty builder for a property store. 220 * 221 * @return A new empty builder for a property store. 222 */ 223 public static PropertyStoreBuilder create() { 224 return new PropertyStoreBuilder(); 225 } 226 227 /** 228 * Creates a new property store builder initialized with the values in this property store. 229 * 230 * @return A new property store builder. 231 */ 232 public PropertyStoreBuilder builder() { 233 return new PropertyStoreBuilder(this); 234 } 235 236 private Property findProperty(String key) { 237 String g = group(key); 238 String k = key.substring(g.length()+1); 239 PropertyGroup pm = groups.get(g); 240 241 if (pm != null) { 242 Property p = pm.get(k); 243 if (p != null) 244 return p; 245 } 246 247 String s = System.getProperty(key); 248 if (s != null) 249 return PropertyStoreBuilder.MutableProperty.create(k, s).build(); 250 251 return null; 252 } 253 254 /** 255 * Returns the raw property value with the specified name. 256 * 257 * @param key The property name. 258 * @return The property value, or <jk>null</jk> if it doesn't exist. 259 */ 260 public Object getProperty(String key) { 261 Property p = findProperty(key); 262 return p == null ? null : p.value; 263 } 264 265 /** 266 * Returns the property value with the specified name. 267 * 268 * @param key The property name. 269 * @param c The class to cast or convert the value to. 270 * @param def The default value. 271 * @return The property value, or the default value if it doesn't exist. 272 */ 273 public <T> T getProperty(String key, Class<T> c, T def) { 274 Property p = findProperty(key); 275 return p == null ? def : p.as(c); 276 } 277 278 /** 279 * Returns the class property with the specified name. 280 * 281 * @param key The property name. 282 * @param type The class type of the property. 283 * @param def The default value. 284 * @return The property value, or the default value if it doesn't exist. 285 */ 286 public <T> Class<? extends T> getClassProperty(String key, Class<T> type, Class<? extends T> def) { 287 Property p = findProperty(key); 288 return p == null ? def : (Class<T>)p.as(Class.class); 289 } 290 291 /** 292 * Returns the array property value with the specified name. 293 * 294 * @param key The property name. 295 * @param eType The class type of the elements in the property. 296 * @return The property value, or an empty array if it doesn't exist. 297 */ 298 public <T> T[] getArrayProperty(String key, Class<T> eType) { 299 Property p = findProperty(key); 300 return (T[]) (p == null ? Array.newInstance(eType, 0) : p.asArray(eType)); 301 } 302 303 /** 304 * Returns the array property value with the specified name. 305 * 306 * @param key The property name. 307 * @param eType The class type of the elements in the property. 308 * @param def The default value. 309 * @return The property value, or an empty array if it doesn't exist. 310 */ 311 public <T> T[] getArrayProperty(String key, Class<T> eType, T[] def) { 312 Property p = findProperty(key); 313 return p == null ? def : p.asArray(eType); 314 } 315 316 /** 317 * Returns the class array property with the specified name. 318 * 319 * @param key The property name. 320 * @return The property value, or an empty array if it doesn't exist. 321 */ 322 public Class<?>[] getClassArrayProperty(String key) { 323 Property p = findProperty(key); 324 return p == null ? new Class[0] : p.as(Class[].class); 325 } 326 327 /** 328 * Returns the class array property with the specified name. 329 * 330 * @param key The property name. 331 * @param def The default value. 332 * @return The property value, or an empty array if it doesn't exist. 333 */ 334 public Class<?>[] getClassArrayProperty(String key, Class<?>[] def) { 335 Property p = findProperty(key); 336 return p == null ? def : p.as(Class[].class); 337 } 338 339 /** 340 * Returns the class array property with the specified name. 341 * 342 * @param key The property name. 343 * @param eType The class type of the elements in the property. 344 * @return The property value, or an empty array if it doesn't exist. 345 */ 346 public <T> Class<T>[] getClassArrayProperty(String key, Class<T> eType) { 347 Property p = findProperty(key); 348 return p == null ? new Class[0] : p.as(Class[].class); 349 } 350 351 /** 352 * Returns the set property with the specified name. 353 * 354 * @param key The property name. 355 * @param eType The class type of the elements in the property. 356 * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or an empty set if it doesn't exist. 357 */ 358 public <T> Set<T> getSetProperty(String key, Class<T> eType) { 359 Property p = findProperty(key); 360 return p == null ? Collections.EMPTY_SET : p.asSet(eType); 361 } 362 363 /** 364 * Returns the set property with the specified name. 365 * 366 * @param key The property name. 367 * @param eType The class type of the elements in the property. 368 * @param def The default value if the property doesn't exist or is empty. 369 * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or the default value if it doesn't exist or is empty. 370 */ 371 public <T> Set<T> getSetProperty(String key, Class<T> eType, Set<T> def) { 372 Set<T> l = getSetProperty(key, eType); 373 return (l.isEmpty() ? def : l); 374 } 375 376 /** 377 * Returns the class set property with the specified name. 378 * 379 * @param key The property name. 380 * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or an empty set if it doesn't exist. 381 */ 382 public Set<Class<?>> getClassSetProperty(String key) { 383 Property p = findProperty(key); 384 return p == null ? Collections.EMPTY_SET : p.asSet(Class.class); 385 } 386 387 /** 388 * Returns the class set property with the specified name. 389 * 390 * @param key The property name. 391 * @param eType The class type of the elements in the property. 392 * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or an empty set if it doesn't exist. 393 */ 394 public <T> Set<Class<T>> getClassSetProperty(String key, Class<T> eType) { 395 Property p = findProperty(key); 396 return p == null ? Collections.EMPTY_SET : p.asSet(Class.class); 397 } 398 399 /** 400 * Returns the list property with the specified name. 401 * 402 * @param key The property name. 403 * @param eType The class type of the elements in the property. 404 * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist. 405 */ 406 public <T> List<T> getListProperty(String key, Class<T> eType) { 407 Property p = findProperty(key); 408 return p == null ? Collections.EMPTY_LIST : p.asList(eType); 409 } 410 411 /** 412 * Returns the list property with the specified name. 413 * 414 * @param key The property name. 415 * @param eType The class type of the elements in the property. 416 * @param def The default value if the property doesn't exist or is empty. 417 * @return The property value as an unmodifiable <code>ArrayList</code>, or the default value if it doesn't exist or is empty. 418 */ 419 public <T> List<T> getListProperty(String key, Class<T> eType, List<T> def) { 420 List<T> l = getListProperty(key, eType); 421 return (l.isEmpty() ? def : l); 422 } 423 424 /** 425 * Returns the class list property with the specified name. 426 * 427 * @param key The property name. 428 * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist. 429 */ 430 public List<Class<?>> getClassListProperty(String key) { 431 Property p = findProperty(key); 432 return p == null ? Collections.EMPTY_LIST : p.asList(Class.class); 433 } 434 435 /** 436 * Returns the class list property with the specified name. 437 * 438 * @param key The property name. 439 * @param eType The class type of the elements in the property. 440 * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist. 441 */ 442 public <T> List<Class<T>> getClassListProperty(String key, Class<T> eType) { 443 Property p = findProperty(key); 444 return p == null ? Collections.EMPTY_LIST : p.asList(Class.class); 445 } 446 447 /** 448 * Returns the map property with the specified name. 449 * 450 * @param key The property name. 451 * @param eType The class type of the elements in the property. 452 * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist. 453 */ 454 public <T> Map<String,T> getMapProperty(String key, Class<T> eType) { 455 Property p = findProperty(key); 456 return p == null ? Collections.EMPTY_MAP : p.asMap(eType); 457 } 458 459 /** 460 * Returns the class map property with the specified name. 461 * 462 * @param key The property name. 463 * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist. 464 */ 465 public Map<String,Class<?>> getClassMapProperty(String key) { 466 Property p = findProperty(key); 467 return p == null ? Collections.EMPTY_MAP : p.asMap(Class.class); 468 } 469 470 /** 471 * Returns the class map property with the specified name. 472 * 473 * @param key The property name. 474 * @param eType The class type of the elements in the property. 475 * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist. 476 */ 477 public <T> Map<String,Class<T>> getClassMapProperty(String key, Class<T> eType) { 478 Property p = findProperty(key); 479 return p == null ? Collections.EMPTY_MAP : p.asMap(Class.class); 480 } 481 482 /** 483 * Returns an instance of the specified class, string, or object property. 484 * 485 * <p> 486 * If instantiating a class, assumes the class has a no-arg constructor. 487 * Otherwise, throws a runtime exception. 488 * 489 * @param key The property name. 490 * @param type The class type of the property. 491 * @param def 492 * The default value if the property doesn't exist. 493 * <br>Can either be an instance of <code>T</code>, or a <code>Class<? <jk>extends</jk> T></code>, or <jk>null</jk>. 494 * @return A new property instance. 495 */ 496 public <T> T getInstanceProperty(String key, Class<T> type, Object def) { 497 return getInstanceProperty(key, type, def, false); 498 } 499 500 /** 501 * Returns an instance of the specified class, string, or object property. 502 * 503 * @param key The property name. 504 * @param type The class type of the property. 505 * @param def 506 * The default value if the property doesn't exist. 507 * <br>Can either be an instance of <code>T</code>, or a <code>Class<? <jk>extends</jk> T></code>. 508 * @param fuzzyArgs 509 * Use fuzzy constructor arg matching. 510 * <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored. 511 * <br>No-arg constructors are also used if no other constructors are found. 512 * @param args 513 * Arguments to pass to the constructor. 514 * Constructors matching the arguments are always used before no-arg constructors. 515 * @return A new property instance. 516 */ 517 public <T> T getInstanceProperty(String key, Class<T> type, Object def, boolean fuzzyArgs, Object...args) { 518 return getInstanceProperty(key, null, type, def, fuzzyArgs, args); 519 } 520 521 /** 522 * Returns an instance of the specified class, string, or object property. 523 * 524 * @param key The property name. 525 * @param outer The outer object if the class we're instantiating is an inner class. 526 * @param type The class type of the property. 527 * @param def 528 * The default value if the property doesn't exist. 529 * <br>Can either be an instance of <code>T</code>, or a <code>Class<? <jk>extends</jk> T></code>. 530 * @param fuzzyArgs 531 * Use fuzzy constructor arg matching. 532 * <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored. 533 * <br>No-arg constructors are also used if no other constructors are found. 534 * @param args 535 * Arguments to pass to the constructor. 536 * Constructors matching the arguments are always used before no-arg constructors. 537 * @return A new property instance. 538 */ 539 public <T> T getInstanceProperty(String key, Object outer, Class<T> type, Object def, boolean fuzzyArgs, Object...args) { 540 Property p = findProperty(key); 541 if (p != null) 542 return p.asInstance(outer, type, fuzzyArgs, args); 543 if (def == null) 544 return null; 545 if (def instanceof Class) 546 return ClassUtils.newInstance(type, def, fuzzyArgs, args); 547 if (type.isInstance(def)) 548 return (T)def; 549 throw new ConfigException("Could not instantiate property ''{0}'' as type ''{1}'' with default value ''{2}''", key, type, def); 550 } 551 552 /** 553 * Returns the specified property as an array of instantiated objects. 554 * 555 * @param key The property name. 556 * @param type The class type of the property. 557 * @param def The default object to return if the property doesn't exist. 558 * @return A new property instance. 559 */ 560 public <T> T[] getInstanceArrayProperty(String key, Class<T> type, T[] def) { 561 return getInstanceArrayProperty(key, type, def, false); 562 } 563 564 /** 565 * Returns the specified property as an array of instantiated objects. 566 * 567 * @param key The property name. 568 * @param type The class type of the property. 569 * @param def The default object to return if the property doesn't exist. 570 * @param fuzzyArgs 571 * Use fuzzy constructor arg matching. 572 * <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored. 573 * <br>No-arg constructors are also used if no other constructors are found. 574 * @param args 575 * Arguments to pass to the constructor. 576 * Constructors matching the arguments are always used before no-arg constructors. 577 * @return A new property instance. 578 */ 579 public <T> T[] getInstanceArrayProperty(String key, Class<T> type, T[] def, boolean fuzzyArgs, Object...args) { 580 return getInstanceArrayProperty(key, null, type, def, fuzzyArgs, args); 581 } 582 583 /** 584 * Returns the specified property as an array of instantiated objects. 585 * 586 * @param key The property name. 587 * @param outer The outer object if the class we're instantiating is an inner class. 588 * @param type The class type of the property. 589 * @param def The default object to return if the property doesn't exist. 590 * @param fuzzyArgs 591 * Use fuzzy constructor arg matching. 592 * <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored. 593 * <br>No-arg constructors are also used if no other constructors are found. 594 * @param args 595 * Arguments to pass to the constructor. 596 * Constructors matching the arguments are always used before no-arg constructors. 597 * @return A new property instance. 598 */ 599 public <T> T[] getInstanceArrayProperty(String key, Object outer, Class<T> type, T[] def, boolean fuzzyArgs, Object...args) { 600 Property p = findProperty(key); 601 return p == null ? def : p.asInstanceArray(outer, type, fuzzyArgs, args); 602 } 603 604 /** 605 * Returns the keys found in the specified property group. 606 * 607 * <p> 608 * The keys are NOT prefixed with group names. 609 * 610 * @param group The group name. 611 * @return The set of property keys, or an empty set if the group was not found. 612 */ 613 public Set<String> getPropertyKeys(String group) { 614 if (group == null) 615 return Collections.EMPTY_SET; 616 PropertyGroup g = groups.get(group); 617 return g == null ? Collections.EMPTY_SET : g.keySet(); 618 } 619 620 @Override /* Object */ 621 public int hashCode() { 622 return hashCode; 623 } 624 625 /** 626 * Returns a hashcode of this property store using only the specified group names. 627 * 628 * @param groups The names of the property groups to use in the calculation. 629 * @return The hashcode. 630 */ 631 public Integer hashCode(String...groups) { 632 HashCode c = new HashCode(); 633 for (String p : groups) 634 if (p != null) 635 c.add(p).add(this.groups.get(p)); 636 return c.get(); 637 } 638 639 @Override /* Object */ 640 public boolean equals(Object o) { 641 if (this == o) 642 return true; 643 if (o instanceof PropertyStore) 644 return (this.groups.equals(((PropertyStore)o).groups)); 645 return false; 646 } 647 648 /** 649 * Compares two property stores, but only based on the specified group names. 650 * 651 * @param ps The property store to compare to. 652 * @param groups The groups to compare. 653 * @return <jk>true</jk> if the two property stores are equal in the specified groups. 654 */ 655 public boolean equals(PropertyStore ps, String...groups) { 656 if (this == ps) 657 return true; 658 for (String p : groups) { 659 if (p != null) { 660 PropertyGroup pg1 = this.groups.get(p), pg2 = ps.groups.get(p); 661 if (pg1 == null && pg2 == null) 662 continue; 663 if (pg1 == null || pg2 == null) 664 return false; 665 if (! pg1.equals(pg2)) 666 return false; 667 } 668 } 669 return true; 670 } 671 672 /** 673 * Used for debugging. 674 * 675 * <p> 676 * Allows property stores to be serialized to easy-to-read JSON objects. 677 * 678 * @param beanSession The bean session. 679 * @return The property groups. 680 */ 681 public Map<String,PropertyGroup> swap(BeanSession beanSession) { 682 return groups; 683 } 684 685 //------------------------------------------------------------------------------------------------------------------- 686 // PropertyGroup 687 //------------------------------------------------------------------------------------------------------------------- 688 689 static class PropertyGroup { 690 final SortedMap<String,Property> properties; 691 private final int hashCode; 692 693 PropertyGroup(Map<String,MutableProperty> properties) { 694 TreeMap<String,Property> m = new TreeMap<>(); 695 for (Map.Entry<String,MutableProperty> p : properties.entrySet()) 696 m.put(p.getKey(), p.getValue().build()); 697 this.properties = Collections.unmodifiableSortedMap(m); 698 this.hashCode = this.properties.hashCode(); 699 } 700 701 PropertyGroupBuilder builder() { 702 return new PropertyGroupBuilder(properties); 703 } 704 705 Property get(String key) { 706 return properties.get(key); 707 } 708 709 @Override /* Object */ 710 public int hashCode() { 711 return hashCode; 712 } 713 714 @Override /* Object */ 715 public boolean equals(Object o) { 716 if (o instanceof PropertyGroup) 717 return properties.equals(((PropertyGroup)o).properties); 718 return false; 719 } 720 721 Set<String> keySet() { 722 return properties.keySet(); 723 } 724 725 public Map<String,Property> swap(BeanSession beanSession) { 726 return properties; 727 } 728 } 729 730 //------------------------------------------------------------------------------------------------------------------- 731 // Property 732 //------------------------------------------------------------------------------------------------------------------- 733 734 static class Property { 735 private final String name; 736 final Object value; 737 private final int hashCode; 738 private final PropertyType type; 739 740 Property(String name, Object value, PropertyType type) { 741 this.name = name; 742 this.value = value; 743 this.type = type; 744 this.hashCode = value.hashCode(); 745 } 746 747 MutableProperty mutable() { 748 switch(type) { 749 case STRING: 750 case BOOLEAN: 751 case INTEGER: 752 case CLASS: 753 case OBJECT: return new MutableSimpleProperty(name, type, value); 754 case SET_STRING: 755 case SET_INTEGER: 756 case SET_CLASS: return new MutableSetProperty(name, type, value); 757 case LIST_STRING: 758 case LIST_INTEGER: 759 case LIST_CLASS: 760 case LIST_OBJECT: return new MutableListProperty(name, type, value); 761 case SORTED_MAP_STRING: 762 case SORTED_MAP_INTEGER: 763 case SORTED_MAP_CLASS: 764 case SORTED_MAP_OBJECT: return new MutableMapProperty(name, type, value); 765 case ORDERED_MAP_STRING: 766 case ORDERED_MAP_INTEGER: 767 case ORDERED_MAP_CLASS: 768 case ORDERED_MAP_OBJECT: return new MutableLinkedMapProperty(name, type, value); 769 } 770 throw new ConfigException("Invalid type specified: ''{0}''", type); 771 } 772 773 public <T> T as(Class<T> c) { 774 Class<?> c2 = ClassUtils.getPrimitiveWrapper(c); 775 if (c2 != null) 776 c = (Class<T>)c2; 777 if (c.isInstance(value)) 778 return (T)value; 779 if (c.isArray() && value instanceof Collection) 780 return (T)asArray(c.getComponentType()); 781 if (type == STRING) { 782 T t = ClassUtils.fromString(c, value.toString()); 783 if (t != null) 784 return t; 785 } 786 throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}'' on property ''{2}''", type, c, name); 787 } 788 789 public <T> T[] asArray(Class<T> eType) { 790 if (value instanceof Collection) { 791 Collection<?> l = (Collection<?>)value; 792 Object t = Array.newInstance(eType, l.size()); 793 int i = 0; 794 for (Object o : l) { 795 Object o2 = null; 796 if (eType.isInstance(o)) 797 o2 = o; 798 else if (type == SET_STRING || type == LIST_STRING) { 799 o2 = ClassUtils.fromString(eType, o.toString()); 800 if (o2 == null) 801 throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name); 802 } else { 803 throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name); 804 } 805 Array.set(t, i++, o2); 806 } 807 return (T[])t; 808 } 809 throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name); 810 } 811 812 public <T> Set<T> asSet(Class<T> eType) { 813 if (type == SET_STRING && eType == String.class || type == SET_INTEGER && eType == Integer.class || type == SET_CLASS && eType == Class.class) { 814 return (Set<T>)value; 815 } else if (type == SET_STRING) { 816 Set<T> s = new LinkedHashSet<>(); 817 for (Object o : (Set<?>)value) { 818 T t = ClassUtils.fromString(eType, o.toString()); 819 if (t == null) 820 throw new ConfigException("Invalid property conversion ''{0}'' to ''Set<{1}>'' on property ''{2}''", type, eType, name); 821 s.add(t); 822 } 823 return Collections.unmodifiableSet(s); 824 } else { 825 throw new ConfigException("Invalid property conversion ''{0}'' to ''Set<{1}>'' on property ''{2}''", type, eType, name); 826 } 827 } 828 829 public <T> List<T> asList(Class<T> eType) { 830 if (type == LIST_STRING && eType == String.class || type == LIST_INTEGER && eType == Integer.class || type == LIST_CLASS && eType == Class.class || type == LIST_OBJECT) { 831 return (List<T>)value; 832 } else if (type == PropertyType.LIST_STRING) { 833 List<T> l = new ArrayList<>(); 834 for (Object o : (List<?>)value) { 835 T t = ClassUtils.fromString(eType, o.toString()); 836 if (t == null) 837 throw new ConfigException("Invalid property conversion ''{0}'' to ''List<{1}>'' on property ''{2}''", type, eType, name); 838 l.add(t); 839 } 840 return unmodifiableList(l); 841 } else { 842 throw new ConfigException("Invalid property conversion ''{0}'' to ''List<{1}>'' on property ''{2}''", type, eType, name); 843 } 844 } 845 846 public <T> Map<String,T> asMap(Class<T> eType) { 847 if ( 848 eType == String.class && (type == SORTED_MAP_STRING || type == ORDERED_MAP_STRING) 849 || eType == Integer.class && (type == SORTED_MAP_INTEGER || type == ORDERED_MAP_INTEGER) 850 || eType == Class.class && (type == SORTED_MAP_CLASS || type == ORDERED_MAP_CLASS) 851 || (type == SORTED_MAP_OBJECT || type == ORDERED_MAP_OBJECT)) { 852 return (Map<String,T>)value; 853 } else if (type == SORTED_MAP_STRING || type == ORDERED_MAP_STRING) { 854 Map<String,T> m = new LinkedHashMap<>(); 855 for (Map.Entry<String,String> e : ((Map<String,String>)value).entrySet()) { 856 T t = ClassUtils.fromString(eType, e.getValue()); 857 if (t == null) 858 throw new ConfigException("Invalid property conversion ''{0}'' to ''Map<String,{1}>'' on property ''{2}''", type, eType, name); 859 m.put(e.getKey(), t); 860 } 861 return unmodifiableMap(m); 862 } else { 863 throw new ConfigException("Invalid property conversion ''{0}'' to ''Map<String,{1}>'' on property ''{2}''", type, eType, name); 864 } 865 } 866 867 public <T> T asInstance(Object outer, Class<T> iType, boolean fuzzyArgs, Object...args) { 868 if (type == STRING) 869 return ClassUtils.fromString(iType, value.toString()); 870 else if (type == OBJECT || type == CLASS) 871 return ClassUtils.newInstanceFromOuter(outer, iType, value, fuzzyArgs, args); 872 throw new ConfigException("Invalid property instantiation ''{0}'' to ''{1}'' on property ''{2}''", type, iType, name); 873 } 874 875 public <T> T[] asInstanceArray(Object outer, Class<T> eType, boolean fuzzyArgs, Object...args) { 876 if (value instanceof Collection) { 877 Collection<?> l = (Collection<?>)value; 878 Object t = Array.newInstance(eType, l.size()); 879 int i = 0; 880 for (Object o : l) { 881 Object o2 = null; 882 if (eType.isInstance(o)) 883 o2 = o; 884 else if (type == SET_STRING || type == LIST_STRING) 885 o2 = ClassUtils.fromString(eType, o.toString()); 886 else if (type == SET_CLASS || type == LIST_CLASS || type == LIST_OBJECT) 887 o2 = ClassUtils.newInstanceFromOuter(outer, eType, o, fuzzyArgs, args); 888 if (o2 == null) 889 throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name); 890 Array.set(t, i++, o2); 891 } 892 return (T[])t; 893 } 894 throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name); 895 } 896 897 @Override /* Object */ 898 public int hashCode() { 899 return hashCode; 900 } 901 902 @Override /* Object */ 903 public boolean equals(Object o) { 904 if (o instanceof Property) 905 return ((Property)o).value.equals(value); 906 return false; 907 } 908 909 public Object swap(BeanSession beanSession) { 910 return value; 911 } 912 } 913 914 //------------------------------------------------------------------------------------------------------------------- 915 // Utility methods 916 //------------------------------------------------------------------------------------------------------------------- 917 918 private static String group(String key) { 919 if (key == null || key.indexOf('.') == -1 || key.charAt(key.length()-1) == '.') 920 throw new ConfigException("Invalid property name specified: ''{0}''", key); 921 String g = key.substring(0, key.indexOf('.')); 922 if (g.isEmpty()) 923 throw new ConfigException("Invalid property name specified: ''{0}''", key); 924 return g; 925 } 926 927 @Override /* Object */ 928 public String toString() { 929 return JsonSerializer.DEFAULT_LAX.toString(this); 930 } 931}