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