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