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