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 w800'> 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 w800'> 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 w800'> 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 w800'> 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 w800'> 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 w800'> 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 w800'> 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 w800'> 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@SuppressWarnings("unchecked") 188public final class PropertyStore { 189 190 /** 191 * A default empty property store. 192 */ 193 public static PropertyStore DEFAULT = PropertyStore.create().build(); 194 195 final SortedMap<String,PropertyGroup> groups; 196 private final int hashCode; 197 198 // Created by PropertyStoreBuilder.build() 199 PropertyStore(Map<String,PropertyGroupBuilder> propertyMaps) { 200 TreeMap<String,PropertyGroup> m = new TreeMap<>(); 201 for (Map.Entry<String,PropertyGroupBuilder> p : propertyMaps.entrySet()) 202 m.put(p.getKey(), p.getValue().build()); 203 this.groups = Collections.unmodifiableSortedMap(m); 204 this.hashCode = groups.hashCode(); 205 } 206 207 /** 208 * Creates a new empty builder for a property store. 209 * 210 * @return A new empty builder for a property store. 211 */ 212 public static PropertyStoreBuilder create() { 213 return new PropertyStoreBuilder(); 214 } 215 216 /** 217 * Creates a new property store builder initialized with the values in this property store. 218 * 219 * @return A new property store builder. 220 */ 221 public PropertyStoreBuilder builder() { 222 return new PropertyStoreBuilder(this); 223 } 224 225 private Property findProperty(String key) { 226 String g = group(key); 227 String k = key.substring(g.length()+1); 228 PropertyGroup pm = groups.get(g); 229 230 if (pm != null) { 231 Property p = pm.get(k); 232 if (p != null) 233 return p; 234 } 235 236 String s = System.getProperty(key); 237 if (s != null) 238 return PropertyStoreBuilder.MutableProperty.create(k, s).build(); 239 240 return null; 241 } 242 243 /** 244 * Returns the raw property value with the specified name. 245 * 246 * @param key The property name. 247 * @return The property value, or <jk>null</jk> if it doesn't exist. 248 */ 249 public Object getProperty(String key) { 250 Property p = findProperty(key); 251 return p == null ? null : p.value; 252 } 253 254 /** 255 * Returns the property value with the specified name. 256 * 257 * @param key The property name. 258 * @param c The class to cast or convert the value to. 259 * @param def The default value. 260 * @return The property value, or the default value if it doesn't exist. 261 */ 262 public <T> T getProperty(String key, Class<T> c, T def) { 263 Property p = findProperty(key); 264 return p == null ? def : p.as(c); 265 } 266 267 /** 268 * Returns the class property with the specified name. 269 * 270 * @param key The property name. 271 * @param type The class type of the property. 272 * @param def The default value. 273 * @return The property value, or the default value if it doesn't exist. 274 */ 275 public <T> Class<? extends T> getClassProperty(String key, Class<T> type, Class<? extends T> def) { 276 Property p = findProperty(key); 277 return p == null ? def : (Class<T>)p.as(Class.class); 278 } 279 280 /** 281 * Returns the array property value with the specified name. 282 * 283 * @param key The property name. 284 * @param eType The class type of the elements in the property. 285 * @return The property value, or an empty array if it doesn't exist. 286 */ 287 public <T> T[] getArrayProperty(String key, Class<T> eType) { 288 Property p = findProperty(key); 289 return (T[]) (p == null ? Array.newInstance(eType, 0) : p.asArray(eType)); 290 } 291 292 /** 293 * Returns the array property value with the specified name. 294 * 295 * @param key The property name. 296 * @param eType The class type of the elements in the property. 297 * @param def The default value. 298 * @return The property value, or an empty array if it doesn't exist. 299 */ 300 public <T> T[] getArrayProperty(String key, Class<T> eType, T[] def) { 301 Property p = findProperty(key); 302 return p == null ? def : p.asArray(eType); 303 } 304 305 /** 306 * Returns the class array property with the specified name. 307 * 308 * @param key The property name. 309 * @return The property value, or an empty array if it doesn't exist. 310 */ 311 public Class<?>[] getClassArrayProperty(String key) { 312 Property p = findProperty(key); 313 return p == null ? new Class[0] : p.as(Class[].class); 314 } 315 316 /** 317 * Returns the class array property with the specified name. 318 * 319 * @param key The property name. 320 * @param def The default value. 321 * @return The property value, or an empty array if it doesn't exist. 322 */ 323 public Class<?>[] getClassArrayProperty(String key, Class<?>[] def) { 324 Property p = findProperty(key); 325 return p == null ? def : p.as(Class[].class); 326 } 327 328 /** 329 * Returns the class array property with the specified name. 330 * 331 * @param key The property name. 332 * @param eType The class type of the elements in the property. 333 * @return The property value, or an empty array if it doesn't exist. 334 */ 335 public <T> Class<T>[] getClassArrayProperty(String key, Class<T> eType) { 336 Property p = findProperty(key); 337 return p == null ? new Class[0] : p.as(Class[].class); 338 } 339 340 /** 341 * Returns the set property with the specified name. 342 * 343 * @param key The property name. 344 * @param eType The class type of the elements in the property. 345 * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or an empty set if it doesn't exist. 346 */ 347 public <T> Set<T> getSetProperty(String key, Class<T> eType) { 348 Property p = findProperty(key); 349 return p == null ? Collections.EMPTY_SET : p.asSet(eType); 350 } 351 352 /** 353 * Returns the set property with the specified name. 354 * 355 * @param key The property name. 356 * @param eType The class type of the elements in the property. 357 * @param def The default value if the property doesn't exist or is empty. 358 * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or the default value if it doesn't exist or is empty. 359 */ 360 public <T> Set<T> getSetProperty(String key, Class<T> eType, Set<T> def) { 361 Set<T> l = getSetProperty(key, eType); 362 return (l.isEmpty() ? def : l); 363 } 364 365 /** 366 * Returns the class set property with the specified name. 367 * 368 * @param key The property name. 369 * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or an empty set if it doesn't exist. 370 */ 371 public Set<Class<?>> getClassSetProperty(String key) { 372 Property p = findProperty(key); 373 return p == null ? Collections.EMPTY_SET : p.asSet(Class.class); 374 } 375 376 /** 377 * Returns the class set property with the specified name. 378 * 379 * @param key The property name. 380 * @param eType The class type of the elements in the property. 381 * @return The property value as an unmodifiable <code>LinkedHashSet</code>, or an empty set if it doesn't exist. 382 */ 383 public <T> Set<Class<T>> getClassSetProperty(String key, Class<T> eType) { 384 Property p = findProperty(key); 385 return p == null ? Collections.EMPTY_SET : p.asSet(Class.class); 386 } 387 388 /** 389 * Returns the list property with the specified name. 390 * 391 * @param key The property name. 392 * @param eType The class type of the elements in the property. 393 * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist. 394 */ 395 public <T> List<T> getListProperty(String key, Class<T> eType) { 396 Property p = findProperty(key); 397 return p == null ? Collections.EMPTY_LIST : p.asList(eType); 398 } 399 400 /** 401 * Returns the list property with the specified name. 402 * 403 * @param key The property name. 404 * @param eType The class type of the elements in the property. 405 * @param def The default value if the property doesn't exist or is empty. 406 * @return The property value as an unmodifiable <code>ArrayList</code>, or the default value if it doesn't exist or is empty. 407 */ 408 public <T> List<T> getListProperty(String key, Class<T> eType, List<T> def) { 409 List<T> l = getListProperty(key, eType); 410 return (l.isEmpty() ? def : l); 411 } 412 413 /** 414 * Returns the class list property with the specified name. 415 * 416 * @param key The property name. 417 * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist. 418 */ 419 public List<Class<?>> getClassListProperty(String key) { 420 Property p = findProperty(key); 421 return p == null ? Collections.EMPTY_LIST : p.asList(Class.class); 422 } 423 424 /** 425 * Returns the class list property with the specified name. 426 * 427 * @param key The property name. 428 * @param eType The class type of the elements in the property. 429 * @return The property value as an unmodifiable <code>ArrayList</code>, or an empty list if it doesn't exist. 430 */ 431 public <T> List<Class<T>> getClassListProperty(String key, Class<T> eType) { 432 Property p = findProperty(key); 433 return p == null ? Collections.EMPTY_LIST : p.asList(Class.class); 434 } 435 436 /** 437 * Returns the map property with the specified name. 438 * 439 * @param key The property name. 440 * @param eType The class type of the elements in the property. 441 * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist. 442 */ 443 public <T> Map<String,T> getMapProperty(String key, Class<T> eType) { 444 Property p = findProperty(key); 445 return p == null ? Collections.EMPTY_MAP : p.asMap(eType); 446 } 447 448 /** 449 * Returns the class map property with the specified name. 450 * 451 * @param key The property name. 452 * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist. 453 */ 454 public Map<String,Class<?>> getClassMapProperty(String key) { 455 Property p = findProperty(key); 456 return p == null ? Collections.EMPTY_MAP : p.asMap(Class.class); 457 } 458 459 /** 460 * Returns the class map property with the specified name. 461 * 462 * @param key The property name. 463 * @param eType The class type of the elements in the property. 464 * @return The property value as an unmodifiable <code>LinkedHashMap</code>, or an empty map if it doesn't exist. 465 */ 466 public <T> Map<String,Class<T>> getClassMapProperty(String key, Class<T> eType) { 467 Property p = findProperty(key); 468 return p == null ? Collections.EMPTY_MAP : p.asMap(Class.class); 469 } 470 471 /** 472 * Returns an instance of the specified class, string, or object property. 473 * 474 * <p> 475 * If instantiating a class, assumes the class has a no-arg constructor. 476 * Otherwise, throws a runtime exception. 477 * 478 * @param key The property name. 479 * @param type The class type of the property. 480 * @param def 481 * The default value if the property doesn't exist. 482 * <br>Can either be an instance of <code>T</code>, or a <code>Class<? <jk>extends</jk> T></code>, or <jk>null</jk>. 483 * @return A new property instance. 484 */ 485 public <T> T getInstanceProperty(String key, Class<T> type, Object def) { 486 return getInstanceProperty(key, type, def, false); 487 } 488 489 /** 490 * Returns an instance of the specified class, string, or object property. 491 * 492 * @param key The property name. 493 * @param type The class type of the property. 494 * @param def 495 * The default value if the property doesn't exist. 496 * <br>Can either be an instance of <code>T</code>, or a <code>Class<? <jk>extends</jk> T></code>. 497 * @param fuzzyArgs 498 * Use fuzzy constructor arg matching. 499 * <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored. 500 * <br>No-arg constructors are also used if no other constructors are found. 501 * @param args 502 * Arguments to pass to the constructor. 503 * Constructors matching the arguments are always used before no-arg constructors. 504 * @return A new property instance. 505 */ 506 public <T> T getInstanceProperty(String key, Class<T> type, Object def, boolean fuzzyArgs, Object...args) { 507 return getInstanceProperty(key, null, type, def, fuzzyArgs, args); 508 } 509 510 /** 511 * Returns an instance of the specified class, string, or object property. 512 * 513 * @param key The property name. 514 * @param outer The outer object if the class we're instantiating is an inner class. 515 * @param type The class type of the property. 516 * @param def 517 * The default value if the property doesn't exist. 518 * <br>Can either be an instance of <code>T</code>, or a <code>Class<? <jk>extends</jk> T></code>. 519 * @param fuzzyArgs 520 * Use fuzzy constructor arg matching. 521 * <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored. 522 * <br>No-arg constructors are also used if no other constructors are found. 523 * @param args 524 * Arguments to pass to the constructor. 525 * Constructors matching the arguments are always used before no-arg constructors. 526 * @return A new property instance. 527 */ 528 public <T> T getInstanceProperty(String key, Object outer, Class<T> type, Object def, boolean fuzzyArgs, Object...args) { 529 Property p = findProperty(key); 530 if (p != null) 531 return p.asInstance(outer, type, fuzzyArgs, args); 532 if (def == null) 533 return null; 534 if (def instanceof Class) 535 return ClassUtils.newInstance(type, def, fuzzyArgs, args); 536 if (type.isInstance(def)) 537 return (T)def; 538 throw new ConfigException("Could not instantiate property ''{0}'' as type ''{1}'' with default value ''{2}''", key, type, def); 539 } 540 541 /** 542 * Returns the specified property as an array of instantiated objects. 543 * 544 * @param key The property name. 545 * @param type The class type of the property. 546 * @param def The default object to return if the property doesn't exist. 547 * @return A new property instance. 548 */ 549 public <T> T[] getInstanceArrayProperty(String key, Class<T> type, T[] def) { 550 return getInstanceArrayProperty(key, type, def, false); 551 } 552 553 /** 554 * Returns the specified property as an array of instantiated objects. 555 * 556 * @param key The property name. 557 * @param type The class type of the property. 558 * @param def The default object to return if the property doesn't exist. 559 * @param fuzzyArgs 560 * Use fuzzy constructor arg matching. 561 * <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored. 562 * <br>No-arg constructors are also used if no other constructors are found. 563 * @param args 564 * Arguments to pass to the constructor. 565 * Constructors matching the arguments are always used before no-arg constructors. 566 * @return A new property instance. 567 */ 568 public <T> T[] getInstanceArrayProperty(String key, Class<T> type, T[] def, boolean fuzzyArgs, Object...args) { 569 return getInstanceArrayProperty(key, null, type, def, fuzzyArgs, args); 570 } 571 572 /** 573 * Returns the specified property as an array of instantiated objects. 574 * 575 * @param key The property name. 576 * @param outer The outer object if the class we're instantiating is an inner class. 577 * @param type The class type of the property. 578 * @param def The default object to return if the property doesn't exist. 579 * @param fuzzyArgs 580 * Use fuzzy constructor arg matching. 581 * <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored. 582 * <br>No-arg constructors are also used if no other constructors are found. 583 * @param args 584 * Arguments to pass to the constructor. 585 * Constructors matching the arguments are always used before no-arg constructors. 586 * @return A new property instance. 587 */ 588 public <T> T[] getInstanceArrayProperty(String key, Object outer, Class<T> type, T[] def, boolean fuzzyArgs, Object...args) { 589 Property p = findProperty(key); 590 return p == null ? def : p.asInstanceArray(outer, type, fuzzyArgs, args); 591 } 592 593 /** 594 * Returns the keys found in the specified property group. 595 * 596 * <p> 597 * The keys are NOT prefixed with group names. 598 * 599 * @param group The group name. 600 * @return The set of property keys, or an empty set if the group was not found. 601 */ 602 public Set<String> getPropertyKeys(String group) { 603 if (group == null) 604 return Collections.EMPTY_SET; 605 PropertyGroup g = groups.get(group); 606 return g == null ? Collections.EMPTY_SET : g.keySet(); 607 } 608 609 @Override /* Object */ 610 public int hashCode() { 611 return hashCode; 612 } 613 614 /** 615 * Returns a hashcode of this property store using only the specified group names. 616 * 617 * @param groups The names of the property groups to use in the calculation. 618 * @return The hashcode. 619 */ 620 public Integer hashCode(String...groups) { 621 HashCode c = new HashCode(); 622 for (String p : groups) 623 if (p != null) 624 c.add(p).add(this.groups.get(p)); 625 return c.get(); 626 } 627 628 @Override /* Object */ 629 public boolean equals(Object o) { 630 if (this == o) 631 return true; 632 if (o instanceof PropertyStore) 633 return (this.groups.equals(((PropertyStore)o).groups)); 634 return false; 635 } 636 637 /** 638 * Compares two property stores, but only based on the specified group names. 639 * 640 * @param ps The property store to compare to. 641 * @param groups The groups to compare. 642 * @return <jk>true</jk> if the two property stores are equal in the specified groups. 643 */ 644 public boolean equals(PropertyStore ps, String...groups) { 645 if (this == ps) 646 return true; 647 for (String p : groups) { 648 if (p != null) { 649 PropertyGroup pg1 = this.groups.get(p), pg2 = ps.groups.get(p); 650 if (pg1 == null && pg2 == null) 651 continue; 652 if (pg1 == null || pg2 == null) 653 return false; 654 if (! pg1.equals(pg2)) 655 return false; 656 } 657 } 658 return true; 659 } 660 661 /** 662 * Used for debugging. 663 * 664 * <p> 665 * Allows property stores to be serialized to easy-to-read JSON objects. 666 * 667 * @param beanSession The bean session. 668 * @return The property groups. 669 */ 670 public Map<String,PropertyGroup> swap(BeanSession beanSession) { 671 return groups; 672 } 673 674 //------------------------------------------------------------------------------------------------------------------- 675 // PropertyGroup 676 //------------------------------------------------------------------------------------------------------------------- 677 678 static class PropertyGroup { 679 final SortedMap<String,Property> properties; 680 private final int hashCode; 681 682 PropertyGroup(Map<String,MutableProperty> properties) { 683 TreeMap<String,Property> m = new TreeMap<>(); 684 for (Map.Entry<String,MutableProperty> p : properties.entrySet()) 685 m.put(p.getKey(), p.getValue().build()); 686 this.properties = Collections.unmodifiableSortedMap(m); 687 this.hashCode = this.properties.hashCode(); 688 } 689 690 PropertyGroupBuilder builder() { 691 return new PropertyGroupBuilder(properties); 692 } 693 694 Property get(String key) { 695 return properties.get(key); 696 } 697 698 @Override /* Object */ 699 public int hashCode() { 700 return hashCode; 701 } 702 703 @Override /* Object */ 704 public boolean equals(Object o) { 705 if (o instanceof PropertyGroup) 706 return properties.equals(((PropertyGroup)o).properties); 707 return false; 708 } 709 710 Set<String> keySet() { 711 return properties.keySet(); 712 } 713 714 public Map<String,Property> swap(BeanSession beanSession) { 715 return properties; 716 } 717 } 718 719 //------------------------------------------------------------------------------------------------------------------- 720 // Property 721 //------------------------------------------------------------------------------------------------------------------- 722 723 static class Property { 724 private final String name; 725 final Object value; 726 private final int hashCode; 727 private final PropertyType type; 728 729 Property(String name, Object value, PropertyType type) { 730 this.name = name; 731 this.value = value; 732 this.type = type; 733 this.hashCode = value.hashCode(); 734 } 735 736 MutableProperty mutable() { 737 switch(type) { 738 case STRING: 739 case BOOLEAN: 740 case INTEGER: 741 case CLASS: 742 case OBJECT: return new MutableSimpleProperty(name, type, value); 743 case SET_STRING: 744 case SET_INTEGER: 745 case SET_CLASS: return new MutableSetProperty(name, type, value); 746 case LIST_STRING: 747 case LIST_INTEGER: 748 case LIST_CLASS: 749 case LIST_OBJECT: return new MutableListProperty(name, type, value); 750 case SORTED_MAP_STRING: 751 case SORTED_MAP_INTEGER: 752 case SORTED_MAP_CLASS: 753 case SORTED_MAP_OBJECT: return new MutableMapProperty(name, type, value); 754 case ORDERED_MAP_STRING: 755 case ORDERED_MAP_INTEGER: 756 case ORDERED_MAP_CLASS: 757 case ORDERED_MAP_OBJECT: return new MutableLinkedMapProperty(name, type, value); 758 } 759 throw new ConfigException("Invalid type specified: ''{0}''", type); 760 } 761 762 public <T> T as(Class<T> c) { 763 Class<?> c2 = ClassUtils.getPrimitiveWrapper(c); 764 if (c2 != null) 765 c = (Class<T>)c2; 766 if (c.isInstance(value)) 767 return (T)value; 768 if (c.isArray() && value instanceof Collection) 769 return (T)asArray(c.getComponentType()); 770 if (type == STRING) { 771 T t = ClassUtils.fromString(c, value.toString()); 772 if (t != null) 773 return t; 774 } 775 throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}'' on property ''{2}''", type, c, name); 776 } 777 778 public <T> T[] asArray(Class<T> eType) { 779 if (value instanceof Collection) { 780 Collection<?> l = (Collection<?>)value; 781 Object t = Array.newInstance(eType, l.size()); 782 int i = 0; 783 for (Object o : l) { 784 Object o2 = null; 785 if (eType.isInstance(o)) 786 o2 = o; 787 else if (type == SET_STRING || type == LIST_STRING) { 788 o2 = ClassUtils.fromString(eType, o.toString()); 789 if (o2 == null) 790 throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name); 791 } else { 792 throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name); 793 } 794 Array.set(t, i++, o2); 795 } 796 return (T[])t; 797 } 798 throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name); 799 } 800 801 public <T> Set<T> asSet(Class<T> eType) { 802 if (type == SET_STRING && eType == String.class || type == SET_INTEGER && eType == Integer.class || type == SET_CLASS && eType == Class.class) { 803 return (Set<T>)value; 804 } else if (type == SET_STRING) { 805 Set<T> s = new LinkedHashSet<>(); 806 for (Object o : (Set<?>)value) { 807 T t = ClassUtils.fromString(eType, o.toString()); 808 if (t == null) 809 throw new ConfigException("Invalid property conversion ''{0}'' to ''Set<{1}>'' on property ''{2}''", type, eType, name); 810 s.add(t); 811 } 812 return Collections.unmodifiableSet(s); 813 } else { 814 throw new ConfigException("Invalid property conversion ''{0}'' to ''Set<{1}>'' on property ''{2}''", type, eType, name); 815 } 816 } 817 818 public <T> List<T> asList(Class<T> eType) { 819 if (type == LIST_STRING && eType == String.class || type == LIST_INTEGER && eType == Integer.class || type == LIST_CLASS && eType == Class.class || type == LIST_OBJECT) { 820 return (List<T>)value; 821 } else if (type == PropertyType.LIST_STRING) { 822 List<T> l = new ArrayList<>(); 823 for (Object o : (List<?>)value) { 824 T t = ClassUtils.fromString(eType, o.toString()); 825 if (t == null) 826 throw new ConfigException("Invalid property conversion ''{0}'' to ''List<{1}>'' on property ''{2}''", type, eType, name); 827 l.add(t); 828 } 829 return unmodifiableList(l); 830 } else { 831 throw new ConfigException("Invalid property conversion ''{0}'' to ''List<{1}>'' on property ''{2}''", type, eType, name); 832 } 833 } 834 835 public <T> Map<String,T> asMap(Class<T> eType) { 836 if ( 837 eType == String.class && (type == SORTED_MAP_STRING || type == ORDERED_MAP_STRING) 838 || eType == Integer.class && (type == SORTED_MAP_INTEGER || type == ORDERED_MAP_INTEGER) 839 || eType == Class.class && (type == SORTED_MAP_CLASS || type == ORDERED_MAP_CLASS) 840 || (type == SORTED_MAP_OBJECT || type == ORDERED_MAP_OBJECT)) { 841 return (Map<String,T>)value; 842 } else if (type == SORTED_MAP_STRING || type == ORDERED_MAP_STRING) { 843 Map<String,T> m = new LinkedHashMap<>(); 844 for (Map.Entry<String,String> e : ((Map<String,String>)value).entrySet()) { 845 T t = ClassUtils.fromString(eType, e.getValue()); 846 if (t == null) 847 throw new ConfigException("Invalid property conversion ''{0}'' to ''Map<String,{1}>'' on property ''{2}''", type, eType, name); 848 m.put(e.getKey(), t); 849 } 850 return unmodifiableMap(m); 851 } else { 852 throw new ConfigException("Invalid property conversion ''{0}'' to ''Map<String,{1}>'' on property ''{2}''", type, eType, name); 853 } 854 } 855 856 public <T> T asInstance(Object outer, Class<T> iType, boolean fuzzyArgs, Object...args) { 857 if (type == STRING) 858 return ClassUtils.fromString(iType, value.toString()); 859 else if (type == OBJECT || type == CLASS) 860 return ClassUtils.newInstanceFromOuter(outer, iType, value, fuzzyArgs, args); 861 throw new ConfigException("Invalid property instantiation ''{0}'' to ''{1}'' on property ''{2}''", type, iType, name); 862 } 863 864 public <T> T[] asInstanceArray(Object outer, Class<T> eType, boolean fuzzyArgs, Object...args) { 865 if (value instanceof Collection) { 866 Collection<?> l = (Collection<?>)value; 867 Object t = Array.newInstance(eType, l.size()); 868 int i = 0; 869 for (Object o : l) { 870 Object o2 = null; 871 if (eType.isInstance(o)) 872 o2 = o; 873 else if (type == SET_STRING || type == LIST_STRING) 874 o2 = ClassUtils.fromString(eType, o.toString()); 875 else if (type == SET_CLASS || type == LIST_CLASS || type == LIST_OBJECT) 876 o2 = ClassUtils.newInstanceFromOuter(outer, eType, o, fuzzyArgs, args); 877 if (o2 == null) 878 throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name); 879 Array.set(t, i++, o2); 880 } 881 return (T[])t; 882 } 883 throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name); 884 } 885 886 @Override /* Object */ 887 public int hashCode() { 888 return hashCode; 889 } 890 891 @Override /* Object */ 892 public boolean equals(Object o) { 893 if (o instanceof Property) 894 return ((Property)o).value.equals(value); 895 return false; 896 } 897 898 public Object swap(BeanSession beanSession) { 899 return value; 900 } 901 } 902 903 //------------------------------------------------------------------------------------------------------------------- 904 // Utility methods 905 //------------------------------------------------------------------------------------------------------------------- 906 907 private static String group(String key) { 908 if (key == null || key.indexOf('.') == -1 || key.charAt(key.length()-1) == '.') 909 throw new ConfigException("Invalid property name specified: ''{0}''", key); 910 String g = key.substring(0, key.indexOf('.')); 911 if (g.isEmpty()) 912 throw new ConfigException("Invalid property name specified: ''{0}''", key); 913 return g; 914 } 915 916 @Override /* Object */ 917 public String toString() { 918 return SimpleJsonSerializer.DEFAULT.toString(this); 919 } 920}