blob: 39b796e5ffca7f66661e1cc07443f5b7ca3f9f3b [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau;
import static org.apache.juneau.PropertyType.*;
import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.internal.ClassUtils.*;
import java.lang.reflect.*;
import java.util.*;
import org.apache.juneau.PropertyStoreBuilder.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.json.*;
import org.apache.juneau.marshall.*;
import org.apache.juneau.reflect.*;
/**
* Represents an immutable collection of properties.
*
* <p>
* The general idea behind a property store is to serve as a reusable configuration of an artifact (e.g. a Serializer)
* such that the artifact can be cached and reused if the property stores are 'equal'.
*
* <h5 class='topic'>Concept</h5>
*
* <p>
* For example, two serializers of the same type created with the same configuration will always end up being
* the same serializer:
*
* <p class='bcode w800'>
* WriterSerializer s1 = JsonSerializer.<jsm>create</jsm>().pojoSwaps(MySwap.<jk>class</jk>).simple().build();
* WriterSerializer s2 = JsonSerializer.<jsm>create</jsm>().simple().pojoSwaps(MySwap.<jk>class</jk>).build();
* <jk>assert</jk>(s1 == s2);
* </p>
*
* <p>
* This has the effect of significantly improving performance, especially if you're creating many Serializers and
* Parsers.
*
* <h5 class='topic'>PropertyStoreBuilder</h5>
*
* <p>
* The {@link PropertyStoreBuilder} class is used to build up and instantiate immutable <c>PropertyStore</c>
* objects.
*
* <p>
* In the example above, the property store being built looks like the following:
*
* <p class='bcode w800'>
* PropertyStore ps = PropertyStore
* .<jsm>create</jsm>()
* .set(<js>"BeanContext.pojoSwaps.lc"</js>, MySwap.<jk>class</jk>)
* .set(<js>"JsonSerializer.simpleMode.b"</js>, <jk>true</jk>)
* .build();
* </p>
*
* <p>
* Property stores are immutable, comparable, and their hashcodes are calculated exactly one time.
* That makes them particularly suited for use as hashmap keys, and thus for caching reusable serializers and parsers.
*
* <h5 class='topic'>Property naming convention</h5>
*
* <p>
* Property names must have the following format...
* <p class='bcode w800'>
* <js>"{class}.{name}.{type}"</js>
* </p>
* <p>
* ...where the parts consist of the following...
* <ul>
* <li><js>"{class}"</js> - The group name of the property (e.g. <js>"JsonSerializer"</js>).
* <br>It's always going to be the simple class name of the class it's associated with.
* <li><js>"{name}"</js> - The property name (e.g. <js>"useWhitespace"</js>).
* <li><js>"{type}"</js> - The property data type.
* <br>A 1 or 2 character string that identifies the data type of the property.
* <br>Valid values are:
* <ul>
* <li><js>"s"</js>: <c>String</c>
* <li><js>"b"</js>: <c>Boolean</c>
* <li><js>"i"</js>: <c>Integer</c>
* <li><js>"c"</js>: <c>Class</c>
* <li><js>"o"</js>: <c>Object</c>
* <li><js>"ss"</js>: <c>TreeSet&lt;String&gt;</c>
* <li><js>"si"</js>: <c>TreeSet&lt;Integer&gt;</c>
* <li><js>"sc"</js>: <c>TreeSet&lt;Class&gt;</c>
* <li><js>"ls"</js>: <c>Linkedlist&lt;String&gt;</c>
* <li><js>"li"</js>: <c>Linkedlist&lt;Integer&gt;</c>
* <li><js>"lc"</js>: <c>Linkedlist&lt;Class&gt;</c>
* <li><js>"lo"</js>: <c>Linkedlist&lt;Object&gt;</c>
* <li><js>"sms"</js>: <c>TreeMap&lt;String,String&gt;</c>
* <li><js>"smi"</js>: <c>TreeMap&lt;String,Integer&gt;</c>
* <li><js>"smc"</js>: <c>TreeMap&lt;String,Class&gt;</c>
* <li><js>"smo"</js>: <c>TreeMap&lt;String,Object&gt;</c>
* <li><js>"oms"</js>: <c>LinkedHashMap&lt;String,String&gt;</c>
* <li><js>"omi"</js>: <c>LinkedHashMap&lt;String,Integer&gt;</c>
* <li><js>"omc"</js>: <c>LinkedHashMap&lt;String,Class&gt;</c>
* <li><js>"omo"</js>: <c>LinkedHashMap&lt;String,Object&gt;</c>
* </ul>
* </ul>
*
* <p>
* For example, <js>"BeanContext.pojoSwaps.lc"</js> refers to a property on the <c>BeanContext</c> class
* called <c>pojoSwaps</c> that has a data type of <c>List&lt;Class&gt;</c>.
*
* <h5 class='topic'>Property value normalization</h5>
*
* <p>
* Property values get 'normalized' when they get set.
* For example, calling <code>propertyStore.set(<js>"BeanContext.debug.b"</js>, <js>"true"</js>)</code> will cause the property
* value to be converted to a boolean.
*
* <h5 class='topic'>Set types</h5>
*
* <p>
* The <js>"sX"</js> property types are sorted sets.
* <br>Use these for collections of objects where the order is not important.
* <br>Internally, a <c>TreeSet</c> is used so that the order in which you add elements does not affect the
* resulting order of the property.
*
* <h5 class='topic'>List types</h5>
*
* <p>
* The <js>"lX"</js> property types are ordered lists.
* <br>Use these in cases where the order in which entries are added is important.
*
* <p>
* Adding to a list property will cause the new entries to be added to the BEGINNING of the list.
* <br>This ensures that the resulting order of the list is in most-to-least importance.
*
* <p>
* For example, multiple calls to <c>pojoSwaps()</c> causes new entries to be added to the beginning of the list
* so that previous values can be 'overridden':
* <p class='bcode w800'>
* <jc>// Swap order: [MySwap2.class, MySwap1.class]</jc>
* JsonSerializer.create().pojoSwaps(MySwap1.<jk>class</jk>).pojoSwaps(MySwap2.<jk>class</jk>).build();
* </p>
*
* <p>
* Note that the order is different when passing multiple values into the <c>pojoSwaps()</c> method, in which
* case the order should be first-match-wins:
* <p class='bcode w800'>
* <jc>// Swap order: [MySwap1.class, MySwap2.class]</jc>
* JsonSerializer.create().pojoSwaps(MySwap1.<jk>class</jk>,MySwap2.<jk>class</jk>).build();
* </p>
*
* <p>
* Combined, the results look like this:
* <p class='bcode w800'>
* <jc>// Swap order: [MySwap4.class, MySwap3.class, MySwap1.class, MySwap2.class]</jc>
* JsonSerializer
* .create()
* .pojoSwaps(MySwap1.<jk>class</jk>,MySwap2.<jk>class</jk>)
* .pojoSwaps(MySwap3.<jk>class</jk>)
* .pojoSwaps(MySwap4.<jk>class</jk>)
* .build();
* </p>
*
* <h5 class='topic'>Map types</h5>
*
* <p>
* The <js>"smX"</js> and <js>"omX"</js> are sorted and order maps respectively.
*
* <h5 class='topic'>Command properties</h5>
*
* <p>
* Set and list properties have the additional convenience 'command' names for adding and removing entries:
* <p class='bcode w800'>
* <js>"{class}.{name}.{type}/add"</js> <jc>// Add a value to the set/list.</jc>
* <js>"{class}.{name}.{type}/remove"</js> <jc>// Remove a value from the set/list.</jc>
* </p>
*
* <p>
* Map properties have the additional convenience property name for adding and removing map entries:
* <p class='bcode w800'>
* <js>"{class}.{name}.{type}/add.{key}"</js> <jc>// Add a map entry (or delete if the value is null).</jc>
* </p>
*/
@SuppressWarnings("unchecked")
public final class PropertyStore {
/**
* A default empty property store.
*/
public static PropertyStore DEFAULT = PropertyStore.create().build();
final SortedMap<String,PropertyGroup> groups;
private final int hashCode;
// Created by PropertyStoreBuilder.build()
PropertyStore(Map<String,PropertyGroupBuilder> propertyMaps) {
TreeMap<String,PropertyGroup> m = new TreeMap<>();
for (Map.Entry<String,PropertyGroupBuilder> p : propertyMaps.entrySet())
m.put(p.getKey(), p.getValue().build());
this.groups = Collections.unmodifiableSortedMap(m);
this.hashCode = groups.hashCode();
}
/**
* Creates a new empty builder for a property store.
*
* @return A new empty builder for a property store.
*/
public static PropertyStoreBuilder create() {
return new PropertyStoreBuilder();
}
/**
* Creates a new property store builder initialized with the values in this property store.
*
* @return A new property store builder.
*/
public PropertyStoreBuilder builder() {
return new PropertyStoreBuilder(this);
}
private Property findProperty(String key) {
String g = group(key);
String k = key.substring(g.length()+1);
PropertyGroup pm = groups.get(g);
if (pm != null) {
Property p = pm.get(k);
if (p != null)
return p;
}
String s = null;
String k1 = key, k2 = key.indexOf('.') == -1 ? key : key.substring(0, key.lastIndexOf('.'));
s = System.getProperty(k1);
if (s == null)
s = System.getProperty(k2);
try {
if (s == null)
s = System.getenv(k1.replace('.', '_').replace('-', '_').toUpperCase());
if (s == null)
s = System.getenv(k2.replace('.', '_').replace('-', '_').toUpperCase());
} catch (SecurityException e) {}
return s == null ? null : PropertyStoreBuilder.MutableProperty.create(k, s).build();
}
/**
* Returns the raw property value with the specified name.
*
* @param key The property name.
* @return The property value, or <jk>null</jk> if it doesn't exist.
*/
public Object getProperty(String key) {
Property p = findProperty(key);
return p == null ? null : p.value;
}
/**
* Returns the property value with the specified name.
*
* @param key The property name.
* @param c The class to cast or convert the value to.
* @param def The default value.
* @return The property value, or the default value if it doesn't exist.
*/
public <T> T getProperty(String key, Class<T> c, T def) {
Property p = findProperty(key);
return p == null ? def : p.as(c);
}
/**
* Returns the class property with the specified name.
*
* @param key The property name.
* @param type The class type of the property.
* @param def The default value.
* @return The property value, or the default value if it doesn't exist.
*/
public <T> Class<? extends T> getClassProperty(String key, Class<T> type, Class<? extends T> def) {
Property p = findProperty(key);
return p == null ? def : (Class<T>)p.as(Class.class);
}
/**
* Returns the array property value with the specified name.
*
* @param key The property name.
* @param eType The class type of the elements in the property.
* @return The property value, or an empty array if it doesn't exist.
*/
public <T> T[] getArrayProperty(String key, Class<T> eType) {
Property p = findProperty(key);
return (T[]) (p == null ? Array.newInstance(eType, 0) : p.asArray(eType));
}
/**
* Returns the array property value with the specified name.
*
* @param key The property name.
* @param eType The class type of the elements in the property.
* @param def The default value.
* @return The property value, or an empty array if it doesn't exist.
*/
public <T> T[] getArrayProperty(String key, Class<T> eType, T[] def) {
Property p = findProperty(key);
return p == null ? def : p.asArray(eType);
}
/**
* Returns the class array property with the specified name.
*
* @param key The property name.
* @return The property value, or an empty array if it doesn't exist.
*/
public Class<?>[] getClassArrayProperty(String key) {
Property p = findProperty(key);
return p == null ? new Class[0] : p.as(Class[].class);
}
/**
* Returns the class array property with the specified name.
*
* @param key The property name.
* @param def The default value.
* @return The property value, or an empty array if it doesn't exist.
*/
public Class<?>[] getClassArrayProperty(String key, Class<?>[] def) {
Property p = findProperty(key);
return p == null ? def : p.as(Class[].class);
}
/**
* Returns the class array property with the specified name.
*
* @param key The property name.
* @param eType The class type of the elements in the property.
* @return The property value, or an empty array if it doesn't exist.
*/
public <T> Class<T>[] getClassArrayProperty(String key, Class<T> eType) {
Property p = findProperty(key);
return p == null ? new Class[0] : p.as(Class[].class);
}
/**
* Returns the set property with the specified name.
*
* @param key The property name.
* @param eType The class type of the elements in the property.
* @return The property value as an unmodifiable <c>LinkedHashSet</c>, or an empty set if it doesn't exist.
*/
public <T> Set<T> getSetProperty(String key, Class<T> eType) {
Property p = findProperty(key);
return p == null ? Collections.EMPTY_SET : p.asSet(eType);
}
/**
* Returns the set property with the specified name.
*
* @param key The property name.
* @param eType The class type of the elements in the property.
* @param def The default value if the property doesn't exist or is empty.
* @return The property value as an unmodifiable <c>LinkedHashSet</c>, or the default value if it doesn't exist or is empty.
*/
public <T> Set<T> getSetProperty(String key, Class<T> eType, Set<T> def) {
Set<T> l = getSetProperty(key, eType);
return (l.isEmpty() ? def : l);
}
/**
* Returns the class set property with the specified name.
*
* @param key The property name.
* @return The property value as an unmodifiable <c>LinkedHashSet</c>, or an empty set if it doesn't exist.
*/
@SuppressWarnings("rawtypes")
public Set<Class<?>> getClassSetProperty(String key) {
Property p = findProperty(key);
return p == null ? Collections.EMPTY_SET : (Set)p.asSet(Class.class);
}
/**
* Returns the class set property with the specified name.
*
* @param key The property name.
* @param eType The class type of the elements in the property.
* @return The property value as an unmodifiable <c>LinkedHashSet</c>, or an empty set if it doesn't exist.
*/
@SuppressWarnings("rawtypes")
public <T> Set<Class<T>> getClassSetProperty(String key, Class<T> eType) {
Property p = findProperty(key);
return p == null ? Collections.EMPTY_SET : (Set)p.asSet(Class.class);
}
/**
* Returns the list property with the specified name.
*
* @param key The property name.
* @param eType The class type of the elements in the property.
* @return The property value as an unmodifiable <c>ArrayList</c>, or an empty list if it doesn't exist.
*/
public <T> List<T> getListProperty(String key, Class<T> eType) {
Property p = findProperty(key);
return p == null ? Collections.EMPTY_LIST : p.asList(eType);
}
/**
* Returns the list property with the specified name.
*
* @param key The property name.
* @param eType The class type of the elements in the property.
* @param def The default value if the property doesn't exist or is empty.
* @return The property value as an unmodifiable <c>ArrayList</c>, or the default value if it doesn't exist or is empty.
*/
public <T> List<T> getListProperty(String key, Class<T> eType, List<T> def) {
List<T> l = getListProperty(key, eType);
return (l.isEmpty() ? def : l);
}
/**
* Returns the class list property with the specified name.
*
* @param key The property name.
* @return The property value as an unmodifiable <c>ArrayList</c>, or an empty list if it doesn't exist.
*/
@SuppressWarnings("rawtypes")
public List<Class<?>> getClassListProperty(String key) {
Property p = findProperty(key);
return p == null ? Collections.EMPTY_LIST : (List)p.asList(Class.class);
}
/**
* Returns the class list property with the specified name.
*
* @param key The property name.
* @param eType The class type of the elements in the property.
* @return The property value as an unmodifiable <c>ArrayList</c>, or an empty list if it doesn't exist.
*/
@SuppressWarnings("rawtypes")
public <T> List<Class<T>> getClassListProperty(String key, Class<T> eType) {
Property p = findProperty(key);
return p == null ? Collections.EMPTY_LIST : (List)p.asList(Class.class);
}
/**
* Returns the map property with the specified name.
*
* @param key The property name.
* @param eType The class type of the elements in the property.
* @return The property value as an unmodifiable <c>LinkedHashMap</c>, or an empty map if it doesn't exist.
*/
public <T> Map<String,T> getMapProperty(String key, Class<T> eType) {
Property p = findProperty(key);
return p == null ? Collections.EMPTY_MAP : p.asMap(eType);
}
/**
* Returns the class map property with the specified name.
*
* @param key The property name.
* @return The property value as an unmodifiable <c>LinkedHashMap</c>, or an empty map if it doesn't exist.
*/
@SuppressWarnings("rawtypes")
public Map<String,Class<?>> getClassMapProperty(String key) {
Property p = findProperty(key);
return p == null ? Collections.EMPTY_MAP : (Map)p.asMap(Class.class);
}
/**
* Returns the class map property with the specified name.
*
* @param key The property name.
* @param eType The class type of the elements in the property.
* @return The property value as an unmodifiable <c>LinkedHashMap</c>, or an empty map if it doesn't exist.
*/
@SuppressWarnings("rawtypes")
public <T> Map<String,Class<T>> getClassMapProperty(String key, Class<T> eType) {
Property p = findProperty(key);
return p == null ? Collections.EMPTY_MAP : (Map)p.asMap(Class.class);
}
/**
* Returns an instance of the specified class, string, or object property.
*
* <p>
* If instantiating a class, assumes the class has a no-arg constructor.
* Otherwise, throws a runtime exception.
*
* @param key The property name.
* @param type The class type of the property.
* @param def
* The default value if the property doesn't exist.
* <br>Can either be an instance of <c>T</c>, or a <code>Class&lt;? <jk>extends</jk> T&gt;</code>, or <jk>null</jk>.
* @return A new property instance.
*/
public <T> T getInstanceProperty(String key, Class<T> type, Object def) {
return getInstanceProperty(key, type, def, ResourceResolver.BASIC);
}
/**
* Returns an instance of the specified class, string, or object property.
*
* @param key The property name.
* @param type The class type of the property.
* @param def
* The default value if the property doesn't exist.
* <br>Can either be an instance of <c>T</c>, or a <code>Class&lt;? <jk>extends</jk> T&gt;</code>.
* @param resolver
* The resolver to use for instantiating objects.
* @param args
* Arguments to pass to the constructor.
* Constructors matching the arguments are always used before no-arg constructors.
* @return A new property instance.
*/
public <T> T getInstanceProperty(String key, Class<T> type, Object def, ResourceResolver resolver, Object...args) {
return getInstanceProperty(key, null, type, def, resolver, args);
}
/**
* Returns an instance of the specified class, string, or object property.
*
* @param key The property name.
* @param outer The outer object if the class we're instantiating is an inner class.
* @param type The class type of the property.
* @param def
* The default value if the property doesn't exist.
* <br>Can either be an instance of <c>T</c>, or a <code>Class&lt;? <jk>extends</jk> T&gt;</code>.
* @param resolver
* The resolver to use for instantiating objects.
* @param args
* Arguments to pass to the constructor.
* Constructors matching the arguments are always used before no-arg constructors.
* @return A new property instance.
*/
public <T> T getInstanceProperty(String key, Object outer, Class<T> type, Object def, ResourceResolver resolver, Object...args) {
Property p = findProperty(key);
if (p != null)
return p.asInstance(outer, type, resolver, args);
if (def == null)
return null;
if (def instanceof Class)
return resolver.resolve(outer, (Class<T>)def, args);
if (type.isInstance(def))
return (T)def;
throw new ConfigException("Could not instantiate property ''{0}'' as type ''{1}'' with default value ''{2}''", key, type, def);
}
/**
* Returns the specified property as an array of instantiated objects.
*
* @param key The property name.
* @param type The class type of the property.
* @param def The default object to return if the property doesn't exist.
* @return A new property instance.
*/
public <T> T[] getInstanceArrayProperty(String key, Class<T> type, T[] def) {
return getInstanceArrayProperty(key, type, def, ResourceResolver.BASIC);
}
/**
* Returns the specified property as an array of instantiated objects.
*
* @param key The property name.
* @param type The class type of the property.
* @param def The default object to return if the property doesn't exist.
* @param resolver
* The resolver to use for instantiating objects.
* @param args
* Arguments to pass to the constructor.
* Constructors matching the arguments are always used before no-arg constructors.
* @return A new property instance.
*/
public <T> T[] getInstanceArrayProperty(String key, Class<T> type, T[] def, ResourceResolver resolver, Object...args) {
return getInstanceArrayProperty(key, null, type, def, resolver, args);
}
/**
* Returns the specified property as an array of instantiated objects.
*
* @param key The property name.
* @param outer The outer object if the class we're instantiating is an inner class.
* @param type The class type of the property.
* @param def The default object to return if the property doesn't exist.
* @param resolver
* The resolver to use for instantiating objects.
* @param args
* Arguments to pass to the constructor.
* Constructors matching the arguments are always used before no-arg constructors.
* @return A new property instance.
*/
public <T> T[] getInstanceArrayProperty(String key, Object outer, Class<T> type, T[] def, ResourceResolver resolver, Object...args) {
Property p = findProperty(key);
return p == null ? def : p.asInstanceArray(outer, type, resolver, args);
}
/**
* Returns the keys found in the specified property group.
*
* <p>
* The keys are NOT prefixed with group names.
*
* @param group The group name.
* @return The set of property keys, or an empty set if the group was not found.
*/
public Set<String> getPropertyKeys(String group) {
if (group == null)
return Collections.EMPTY_SET;
PropertyGroup g = groups.get(group);
return g == null ? Collections.EMPTY_SET : g.keySet();
}
@Override /* Object */
public int hashCode() {
return hashCode;
}
/**
* Returns a hashcode of this property store using only the specified group names.
*
* @param groups The names of the property groups to use in the calculation.
* @return The hashcode.
*/
public Integer hashCode(String...groups) {
HashCode c = new HashCode();
for (String p : groups)
if (p != null)
c.add(p).add(this.groups.get(p));
return c.get();
}
@Override /* Object */
public boolean equals(Object o) {
if (this == o)
return true;
if (o instanceof PropertyStore)
return (this.groups.equals(((PropertyStore)o).groups));
return false;
}
/**
* Compares two property stores, but only based on the specified group names.
*
* @param ps The property store to compare to.
* @param groups The groups to compare.
* @return <jk>true</jk> if the two property stores are equal in the specified groups.
*/
public boolean equals(PropertyStore ps, String...groups) {
if (this == ps)
return true;
for (String p : groups) {
if (p != null) {
PropertyGroup pg1 = this.groups.get(p), pg2 = ps.groups.get(p);
if (pg1 == null && pg2 == null)
continue;
if (pg1 == null || pg2 == null)
return false;
if (! pg1.equals(pg2))
return false;
}
}
return true;
}
/**
* Used for debugging.
*
* <p>
* Allows property stores to be serialized to easy-to-read JSON objects.
*
* @param beanSession The bean session.
* @return The property groups.
*/
public Map<String,PropertyGroup> swap(BeanSession beanSession) {
return groups;
}
//-------------------------------------------------------------------------------------------------------------------
// PropertyGroup
//-------------------------------------------------------------------------------------------------------------------
/**
* A group of properties with the same prefixes.
*/
public static class PropertyGroup {
final SortedMap<String,Property> properties;
private final int hashCode;
PropertyGroup(Map<String,MutableProperty> properties) {
TreeMap<String,Property> m = new TreeMap<>();
for (Map.Entry<String,MutableProperty> p : properties.entrySet())
m.put(p.getKey(), p.getValue().build());
this.properties = Collections.unmodifiableSortedMap(m);
this.hashCode = this.properties.hashCode();
}
PropertyGroupBuilder builder() {
return new PropertyGroupBuilder(properties);
}
Property get(String key) {
return properties.get(key);
}
@Override /* Object */
public int hashCode() {
return hashCode;
}
@Override /* Object */
public boolean equals(Object o) {
if (o instanceof PropertyGroup)
return properties.equals(((PropertyGroup)o).properties);
return false;
}
Set<String> keySet() {
return properties.keySet();
}
/**
* Converts this object to serializable form.
*
* @return The serializable form of this object.
*/
public Map<String,Property> swap() {
return properties;
}
@Override /* Object */
public String toString() {
return "[hash="+hashCode()+"]" + (SimpleJson.DEFAULT == null ? "" : SimpleJson.DEFAULT.toString(properties));
}
}
//-------------------------------------------------------------------------------------------------------------------
// Property
//-------------------------------------------------------------------------------------------------------------------
/**
* A property in a property store group.
*/
public static class Property {
private final String name;
final Object value;
private final int hashCode;
private final PropertyType type;
Property(String name, Object value, PropertyType type) {
this.name = name;
this.value = value;
this.type = type;
this.hashCode = value.hashCode();
}
MutableProperty mutable() {
switch(type) {
case STRING:
case BOOLEAN:
case INTEGER:
case CLASS:
case OBJECT: return new MutableSimpleProperty(name, type, value);
case SET_STRING:
case SET_INTEGER:
case SET_CLASS: return new MutableSetProperty(name, type, value);
case LIST_STRING:
case LIST_INTEGER:
case LIST_CLASS:
case LIST_OBJECT: return new MutableListProperty(name, type, value);
case SORTED_MAP_STRING:
case SORTED_MAP_INTEGER:
case SORTED_MAP_CLASS:
case SORTED_MAP_OBJECT: return new MutableMapProperty(name, type, value);
case ORDERED_MAP_STRING:
case ORDERED_MAP_INTEGER:
case ORDERED_MAP_CLASS:
case ORDERED_MAP_OBJECT: return new MutableLinkedMapProperty(name, type, value);
}
throw new ConfigException("Invalid type specified: ''{0}''", type);
}
/**
* Converts this property to the specified type.
*
* @param <T> The type to convert the property to.
* @param c The type to convert the property to.
* @return The converted type.
* @throws ConfigException If value could not be converted.
*/
public <T> T as(Class<T> c) {
Class<?> c2 = ClassInfo.of(c).getPrimitiveWrapper();
if (c2 != null)
c = (Class<T>)c2;
if (c.isInstance(value))
return (T)value;
if (c.isArray() && value instanceof Collection)
return (T)asArray(c.getComponentType());
if (type == STRING) {
T t = fromString(c, value.toString());
if (t != null)
return t;
}
throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}'' on property ''{2}''", type, c, name);
}
/**
* Converts this property to the specified array type.
*
* @param <T> The element type to convert the property to.
* @param eType The element type to convert the property to.
* @return The converted type.
* @throws ConfigException If value could not be converted.
*/
public <T> T[] asArray(Class<T> eType) {
if (value instanceof Collection) {
Collection<?> l = (Collection<?>)value;
Object t = Array.newInstance(eType, l.size());
int i = 0;
for (Object o : l) {
Object o2 = null;
if (eType.isInstance(o))
o2 = o;
else if (type == SET_STRING || type == LIST_STRING) {
o2 = fromString(eType, o.toString());
if (o2 == null)
throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
} else {
throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
}
Array.set(t, i++, o2);
}
return (T[])t;
}
throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
}
/**
* Converts this property to the specified set type.
*
* @param <T> The element type to convert the property to.
* @param eType The element type to convert the property to.
* @return The converted type.
* @throws ConfigException If value could not be converted.
*/
public <T> Set<T> asSet(Class<T> eType) {
if (type == SET_STRING && eType == String.class || type == SET_INTEGER && eType == Integer.class || type == SET_CLASS && eType == Class.class) {
return (Set<T>)value;
} else if (type == SET_STRING) {
Set<T> s = new LinkedHashSet<>();
for (Object o : (Set<?>)value) {
T t = fromString(eType, o.toString());
if (t == null)
throw new ConfigException("Invalid property conversion ''{0}'' to ''Set<{1}>'' on property ''{2}''", type, eType, name);
s.add(t);
}
return Collections.unmodifiableSet(s);
} else {
throw new ConfigException("Invalid property conversion ''{0}'' to ''Set<{1}>'' on property ''{2}''", type, eType, name);
}
}
/**
* Converts this property to the specified list type.
*
* @param <T> The element type to convert the property to.
* @param eType The element type to convert the property to.
* @return The converted type.
* @throws ConfigException If value could not be converted.
*/
public <T> List<T> asList(Class<T> eType) {
if (type == LIST_STRING && eType == String.class || type == LIST_INTEGER && eType == Integer.class || type == LIST_CLASS && eType == Class.class || type == LIST_OBJECT) {
return (List<T>)value;
} else if (type == PropertyType.LIST_STRING) {
List<T> l = new ArrayList<>();
for (Object o : (List<?>)value) {
T t = fromString(eType, o.toString());
if (t == null)
throw new ConfigException("Invalid property conversion ''{0}'' to ''List<{1}>'' on property ''{2}''", type, eType, name);
l.add(t);
}
return unmodifiableList(l);
} else {
throw new ConfigException("Invalid property conversion ''{0}'' to ''List<{1}>'' on property ''{2}''", type, eType, name);
}
}
/**
* Converts this property to the specified map type.
*
* @param <T> The element type to convert the property to.
* @param eType The element type to convert the property to.
* @return The converted type.
* @throws ConfigException If value could not be converted.
*/
public <T> Map<String,T> asMap(Class<T> eType) {
if (
eType == String.class && (type == SORTED_MAP_STRING || type == ORDERED_MAP_STRING)
|| eType == Integer.class && (type == SORTED_MAP_INTEGER || type == ORDERED_MAP_INTEGER)
|| eType == Class.class && (type == SORTED_MAP_CLASS || type == ORDERED_MAP_CLASS)
|| (type == SORTED_MAP_OBJECT || type == ORDERED_MAP_OBJECT)) {
return (Map<String,T>)value;
} else if (type == SORTED_MAP_STRING || type == ORDERED_MAP_STRING) {
Map<String,T> m = new LinkedHashMap<>();
for (Map.Entry<String,String> e : ((Map<String,String>)value).entrySet()) {
T t = fromString(eType, e.getValue());
if (t == null)
throw new ConfigException("Invalid property conversion ''{0}'' to ''Map<String,{1}>'' on property ''{2}''", type, eType, name);
m.put(e.getKey(), t);
}
return unmodifiableMap(m);
} else {
throw new ConfigException("Invalid property conversion ''{0}'' to ''Map<String,{1}>'' on property ''{2}''", type, eType, name);
}
}
/**
* Converts this property to the specified instance type.
*
* @param outer The outer class if this is a member type.
* @param iType The type to instantiate.
* @param resolver The resource resolver.
* @param args The arguments to pass to the constructor.
* @param <T> The type to instantiate.
* @return The instantiated object.
* @throws ConfigException If value could not be instantiated.
*/
public <T> T asInstance(Object outer, Class<T> iType, ResourceResolver resolver, Object...args) {
if (value == null)
return null;
if (type == STRING)
return fromString(iType, value.toString());
else if (type == OBJECT || type == CLASS) {
T t = instantiate(resolver, outer, iType, value, args);
if (t != null)
return t;
}
throw new ConfigException("Invalid property instantiation ''{0}'' to ''{1}'' on property ''{2}''", type, iType, name);
}
/**
* Converts this property to an array of specified instance type.
*
* @param outer The outer class if this is a member type.
* @param eType The entry type to instantiate.
* @param resolver The resource resolver.
* @param args The arguments to pass to the constructor.
* @param <T> The type to instantiate.
* @return The instantiated object.
* @throws ConfigException If value could not be instantiated.
*/
public <T> T[] asInstanceArray(Object outer, Class<T> eType, ResourceResolver resolver, Object...args) {
if (value instanceof Collection) {
Collection<?> l = (Collection<?>)value;
Object t = Array.newInstance(eType, l.size());
int i = 0;
for (Object o : l) {
Object o2 = null;
if (eType.isInstance(o))
o2 = o;
else if (type == SET_STRING || type == LIST_STRING)
o2 = fromString(eType, o.toString());
else if (type == SET_CLASS || type == LIST_CLASS || type == LIST_OBJECT)
o2 = instantiate(resolver, outer, eType, o, args);
if (o2 == null)
throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
Array.set(t, i++, o2);
}
return (T[])t;
}
throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''", type, eType, name);
}
@Override /* Object */
public int hashCode() {
return hashCode;
}
@Override /* Object */
public boolean equals(Object o) {
if (o instanceof Property)
return ((Property)o).value.equals(value);
return false;
}
/**
* Converts this object to serializable form.
*
* @return The serializable form of this object.
*/
public Object swap() {
return value;
}
}
//-------------------------------------------------------------------------------------------------------------------
// Utility methods
//-------------------------------------------------------------------------------------------------------------------
static <T> T instantiate(ResourceResolver resolver, Object outer, Class<T> c, Object value, Object...args) {
if (ClassInfo.of(c).isParentOf(value.getClass()))
return (T)value;
if (ClassInfo.of(value.getClass()).isChildOf(Class.class))
return resolver.resolve(outer, (Class<T>)value, args);
return null;
}
private static String group(String key) {
if (key == null || key.indexOf('.') == -1 || key.charAt(key.length()-1) == '.')
throw new ConfigException("Invalid property name specified: ''{0}''", key);
String g = key.substring(0, key.indexOf('.'));
if (g.isEmpty())
throw new ConfigException("Invalid property name specified: ''{0}''", key);
return g;
}
@Override /* Object */
public String toString() {
return SimpleJsonSerializer.DEFAULT.toString(this);
}
}