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