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