| // *************************************************************************************************************************** |
| // * 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.ClassUtils.*; |
| import static org.apache.juneau.internal.ObjectUtils.*; |
| |
| import java.lang.reflect.*; |
| import java.util.*; |
| |
| import org.apache.juneau.ContextPropertiesBuilder.*; |
| import org.apache.juneau.collections.*; |
| import org.apache.juneau.cp.*; |
| 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>().swaps(MySwap.<jk>class</jk>).simple().build(); |
| * WriterSerializer s2 = JsonSerializer.<jsm>create</jsm>().simple().swaps(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'>ContextPropertiesBuilder</h5> |
| * |
| * <p> |
| * The {@link ContextPropertiesBuilder} class is used to build up and instantiate immutable <c>ContextProperties</c> |
| * objects. |
| * |
| * <p> |
| * In the example above, the property store being built looks like the following: |
| * |
| * <p class='bcode w800'> |
| * ContextProperties cp = ContextProperties |
| * .<jsm>create</jsm>() |
| * .set(<js>"BeanContext.swaps.lc"</js>, MySwap.<jk>class</jk>) |
| * .set(<js>"JsonSerializer.simpleMode.b"</js>) |
| * .build(); |
| * </p> |
| * |
| * <p> |
| * <c>ContextProperties</c> objects 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<String></c> |
| * <li><js>"si"</js>: <c>TreeSet<Integer></c> |
| * <li><js>"sc"</js>: <c>TreeSet<Class></c> |
| * <li><js>"ls"</js>: <c>Linkedlist<String></c> |
| * <li><js>"li"</js>: <c>Linkedlist<Integer></c> |
| * <li><js>"lc"</js>: <c>Linkedlist<Class></c> |
| * <li><js>"lo"</js>: <c>Linkedlist<Object></c> |
| * <li><js>"sms"</js>: <c>TreeMap<String,String></c> |
| * <li><js>"smi"</js>: <c>TreeMap<String,Integer></c> |
| * <li><js>"smc"</js>: <c>TreeMap<String,Class></c> |
| * <li><js>"smo"</js>: <c>TreeMap<String,Object></c> |
| * <li><js>"oms"</js>: <c>LinkedHashMap<String,String></c> |
| * <li><js>"omi"</js>: <c>LinkedHashMap<String,Integer></c> |
| * <li><js>"omc"</js>: <c>LinkedHashMap<String,Class></c> |
| * <li><js>"omo"</js>: <c>LinkedHashMap<String,Object></c> |
| * </ul> |
| * </ul> |
| * |
| * <p> |
| * For example, <js>"BeanContext.swaps.lc"</js> refers to a property on the <c>BeanContext</c> class |
| * called <c>swaps</c> that has a data type of <c>List<Class></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>swaps()</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().swaps(MySwap1.<jk>class</jk>).swaps(MySwap2.<jk>class</jk>).build(); |
| * </p> |
| * |
| * <p> |
| * Note that the order is different when passing multiple values into the <c>swaps()</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().swaps(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() |
| * .swaps(MySwap1.<jk>class</jk>,MySwap2.<jk>class</jk>) |
| * .swaps(MySwap3.<jk>class</jk>) |
| * .swaps(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 ContextProperties { |
| |
| /** |
| * A default empty property store. |
| */ |
| public static ContextProperties DEFAULT = ContextProperties.create().build(); |
| |
| final Map<String,PropertyGroup> groups; |
| private final int hashCode; |
| |
| // Created by ContextPropertiesBuilder.build() |
| ContextProperties(Map<String,PropertyGroupBuilder> propertyMaps) { |
| Map<String,PropertyGroup> m = new LinkedHashMap<>(); |
| for (Map.Entry<String,PropertyGroupBuilder> p : propertyMaps.entrySet()) |
| m.put(p.getKey(), p.getValue().build()); |
| this.groups = Collections.unmodifiableMap(m); |
| this.hashCode = groups.hashCode(); |
| } |
| |
| /** |
| * Returns a subset of this property store consisting of just the specified group names. |
| * |
| * @param groups The group names to include. |
| * @return A new property store. |
| */ |
| public ContextProperties subset(String[] groups) { |
| TreeMap<String,PropertyGroup> m = new TreeMap<>(); |
| for (String g : groups) { |
| PropertyGroup g2 = this.groups.get(g); |
| if (g2 != null) |
| m.put(g, g2); |
| } |
| |
| return new ContextProperties(m); |
| } |
| |
| private ContextProperties(SortedMap<String,PropertyGroup> propertyMaps) { |
| this.groups = Collections.unmodifiableMap(new LinkedHashMap<>(propertyMaps)); |
| this.hashCode = groups.hashCode(); |
| } |
| |
| |
| /** |
| * Creates a new empty builder for a property store. |
| * |
| * @return A new empty builder for a property store. |
| */ |
| public static ContextPropertiesBuilder create() { |
| return new ContextPropertiesBuilder(); |
| } |
| |
| /** |
| * Creates a new property store builder initialized with the values in this property store. |
| * |
| * @return A new property store builder. |
| */ |
| public ContextPropertiesBuilder copy() { |
| return new ContextPropertiesBuilder(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 = SystemProperties.getProperty(k1, SystemProperties.getProperty(k2)); |
| |
| return s == null ? null : ContextPropertiesBuilder.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 Optional<Object> get(String key) { |
| Property p = findProperty(key); |
| return Optional.ofNullable(p == null ? null : p.value); |
| } |
| |
| /** |
| * Returns the raw property value with the specified name if the property value type is the one specified. |
| * |
| * @param key The property name. |
| * @param c The expected property type. |
| * @return The property value, never <jk>null</jk>j. |
| */ |
| public <T> Optional<T> getIfType(String key, Class<T> c) { |
| Property p = findProperty(key); |
| if (p != null) { |
| Object o = p.value; |
| if (c.isInstance(o)) |
| Optional.of((T)o); |
| } |
| return Optional.empty(); |
| } |
| |
| /** |
| * Returns the raw property value with the specified name if the property value is a class. |
| * |
| * @param key The property name. |
| * @param c The expected property type. |
| * @return The property value, never <jk>null</jk>j. |
| */ |
| public <T> Optional<Class<T>> getIfClass(String key, Class<T> c) { |
| Property p = findProperty(key); |
| if (p != null) { |
| Object o = p.value; |
| if (Class.class.isInstance(o)) |
| return Optional.of((Class<T>)o); |
| } |
| return Optional.empty(); |
| } |
| |
| private <T> T find(String key, Class<T> c) { |
| Property p = findProperty(key); |
| return p == null ? null : p.as(c); |
| } |
| |
| /** |
| * Returns the property value with the specified name. |
| * |
| * @param key The property name. |
| * @param c The class to cast or convert the value to. |
| * @return The property value, never <jk>null</jk> |
| */ |
| public <T> Optional<T> get(String key, Class<T> c) { |
| Property p = findProperty(key); |
| return Optional.ofNullable(p == null ? null : p.as(c)); |
| } |
| |
| /** |
| * Shortcut for calling <code>get(key, Boolean.<jk>class</jk>)</code>. |
| * |
| * @param key The property name. |
| * @return The property value, never <jk>null</jk>. |
| */ |
| public final Optional<Boolean> getBoolean(String key) { |
| return Optional.ofNullable(find(key, Boolean.class)); |
| } |
| |
| /** |
| * Similar to {@link #getBoolean(String)} but looks for multiple keys and returns the value of the first one present. |
| * |
| * @param keys The property names. |
| * @return The property value, never <jk>null</jk>. |
| */ |
| public final Optional<Boolean> getFirstBoolean(String...keys) { |
| for (String k : keys) { |
| Boolean o = find(k, Boolean.class); |
| if (o != null) |
| return Optional.of(o); |
| } |
| return Optional.empty(); |
| } |
| |
| /** |
| * Shortcut for calling <code>get(key, Integer.<jk>class</jk>)</code>. |
| * |
| * @param key The property name. |
| * @return The property value, never <jk>null</jk>. |
| */ |
| public final Optional<Integer> getInteger(String key) { |
| return Optional.ofNullable(find(key, Integer.class)); |
| } |
| |
| /** |
| * Shortcut for calling <code>get(key, Long.<jk>class</jk>)</code>. |
| * |
| * @param key The property name. |
| * @return The property value, never <jk>null</jk>. |
| */ |
| public final Optional<Long> getLong(String key) { |
| return Optional.ofNullable(find(key, Long.class)); |
| } |
| |
| /** |
| * Shortcut for calling <code>get(key, String.<jk>class</jk>)</code>. |
| * |
| * @param key The property name. |
| * @return The property value, never <jk>null</jk>. |
| */ |
| public final Optional<String> getString(String key) { |
| return Optional.ofNullable(find(key, String.class)); |
| } |
| |
| /** |
| * Returns a property as a parsed comma-delimited list of strings. |
| * |
| * @param key The property name. |
| * @return The property value, never <jk>null</jk>. |
| */ |
| public final Optional<String[]> getCdl(String key) { |
| String s = find(key, String.class); |
| if (s != null) |
| return Optional.of(StringUtils.split(s)); |
| return Optional.empty(); |
| } |
| |
| /** |
| * Returns the class property with the specified name. |
| * |
| * @param key The property name. |
| * @param type The class type of the property. |
| * @return The property value, never <jk>null</jk>. |
| */ |
| public <T> Optional<Class<? extends T>> getClass(String key, Class<T> type) { |
| Property p = findProperty(key); |
| return Optional.ofNullable(p == null ? null : (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, never <jk>null</jk>. |
| */ |
| public <T> Optional<T[]> getArray(String key, Class<T> eType) { |
| Property p = findProperty(key); |
| return Optional.ofNullable(p == null ? null : p.asArray(eType)); |
| } |
| |
| /** |
| * Returns the class array property with the specified name. |
| * |
| * @param key The property name. |
| * @return The property value, never <jk>null</jk>. |
| */ |
| public Optional<Class<?>[]> getClassArray(String key) { |
| Property p = findProperty(key); |
| return Optional.ofNullable(p == null ? null : 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>, never <jk>null</jk>. |
| */ |
| public <T> Optional<Set<T>> getSet(String key, Class<T> eType) { |
| Property p = findProperty(key); |
| return Optional.ofNullable(p == null ? null : p.asSet(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. |
| * @return The property value as an unmodifiable <c>ArrayList</c>, never <jk>null</jk>. |
| */ |
| public <T> Optional<List<T>> getList(String key, Class<T> eType) { |
| Property p = findProperty(key); |
| return Optional.ofNullable(p == null ? null : p.asList(eType)); |
| } |
| |
| /** |
| * 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>, never <jk>null</jk>. |
| */ |
| public <T> Optional<Map<String,T>> getMap(String key, Class<T> eType) { |
| Property p = findProperty(key); |
| return Optional.ofNullable(p == null ? null : p.asMap(eType)); |
| } |
| |
| /** |
| * 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 beanStore The bean store to use for instantiating the bean. |
| * @return A new property instance. |
| */ |
| public <T> Optional<T> getInstance(String key, Class<T> type, BeanStore beanStore) { |
| Property p = findProperty(key); |
| return Optional.ofNullable(p == null ? null : p.asInstance(type, beanStore)); |
| } |
| |
| /** |
| * Returns an instance of the specified class, string, or object property. |
| * |
| * @param key The property name. |
| * @param type The class type of the property. |
| * @return A new property instance. |
| */ |
| public <T> Optional<T> getInstance(String key, Class<T> type) { |
| return getInstance(key, type, null); |
| } |
| |
| /** |
| * Returns an instance array of the specified class, string, or object property. |
| * |
| * @param key The property name. |
| * @param type The class type of the property. |
| * @param beanStore The bean store to use for instantiating the bean. |
| * @return A new property instance array. |
| */ |
| public <T> Optional<T[]> getInstanceArray(String key, Class<T> type, BeanStore beanStore) { |
| Property p = findProperty(key); |
| return Optional.ofNullable(p == null ? null : p.asInstanceArray(type, beanStore)); |
| } |
| |
| /** |
| * Returns an instance array of the specified class, string, or object property. |
| * |
| * @param key The property name. |
| * @param type The class type of the property. |
| * @return A new property instance array. |
| */ |
| public <T> Optional<T[]> getInstanceArray(String key, Class<T> type) { |
| return getInstanceArray(key, type, null); |
| } |
| |
| /** |
| * 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> getKeys(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) { |
| return (o instanceof ContextProperties) && eq(this, (ContextProperties)o, (x,y)->eq(x.groups, y.groups)); |
| } |
| |
| /** |
| * Compares two property stores, but only based on the specified group names. |
| * |
| * @param cp 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(ContextProperties cp, String...groups) { |
| if (this == cp) |
| return true; |
| for (String g : groups) { |
| if (g != null) { |
| PropertyGroup pg1 = this.groups.get(g), pg2 = cp.groups.get(g); |
| 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 Map<String,Property> properties; |
| private final int hashCode; |
| |
| PropertyGroup(Map<String,MutableProperty> properties) { |
| Map<String,Property> m = new LinkedHashMap<>(); |
| for (Map.Entry<String,MutableProperty> p : properties.entrySet()) |
| m.put(p.getKey(), p.getValue().build()); |
| this.properties = Collections.unmodifiableMap(m); |
| this.hashCode = this.properties.hashCode(); |
| } |
| |
| PropertyGroupBuilder copy() { |
| 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) { |
| return (o instanceof PropertyGroup) && eq(this, (PropertyGroup)o, (x,y)->eq(x.properties, y.properties)); |
| } |
| |
| 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)); |
| } |
| |
| void hashCodes(StringBuilder sb) { |
| for (Map.Entry<String,Property> e : this.properties.entrySet()) { |
| sb.append("\n\t\t["+Integer.toHexString(e.hashCode())+"] - " + e.getKey()); |
| } |
| } |
| } |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // 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) { |
| AList<T> l = AList.create(); |
| 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 l.unmodifiable(); |
| } 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) { |
| AMap<String,T> m = AMap.create(); |
| 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 m.unmodifiable(); |
| } 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 iType The type to instantiate. |
| * @param beanStore The bean store to use for instantiating beans. |
| * @param <T> The type to instantiate. |
| * @return The instantiated object. |
| * @throws ConfigException If value could not be instantiated. |
| */ |
| public <T> T asInstance(Class<T> iType, BeanStore beanStore) { |
| if (value == null) |
| return null; |
| if (type == STRING) |
| return fromString(iType, value.toString()); |
| else if (type == OBJECT || type == CLASS) { |
| T t = instantiate(beanStore, iType, value); |
| 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 eType The entry type to instantiate. |
| * @param beanStore The bean store to use to instantiate beans. |
| * @param <T> The type to instantiate. |
| * @return The instantiated object. |
| * @throws ConfigException If value could not be instantiated. |
| */ |
| public <T> T[] asInstanceArray(Class<T> eType, BeanStore beanStore) { |
| 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(beanStore, eType, o); |
| if (o2 == null) |
| throw new ConfigException("Invalid property conversion ''{0}'' to ''{1}[]'' on property ''{2}''. Entry type: ''{3}''", type, eType, name, className(o)); |
| 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) { |
| return (o instanceof Property) && eq(this, (Property)o, (x,y)->eq(x.value, y.value)); |
| } |
| |
| /** |
| * Converts this object to serializable form. |
| * |
| * @return The serializable form of this object. |
| */ |
| public Object swap() { |
| return value; |
| } |
| } |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // Utility methods |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| static BeanStore DEFAULT_BEAN_STORE = BeanStore.create().build(); |
| |
| static <T> T instantiate(BeanStore beanStore, Class<T> c, Object value) { |
| if (ClassInfo.of(c).isParentOf(value.getClass())) |
| return (T)value; |
| try { |
| if (ClassInfo.of(value.getClass()).isChildOf(Class.class)) { |
| if (beanStore == null) |
| beanStore = DEFAULT_BEAN_STORE; |
| return beanStore.createBean((Class<T>)value); |
| } |
| } catch (ExecutableException e) { |
| throw new ConfigException(e, "Could not create bean of type ''{0}''.", value); |
| } |
| 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); |
| } |
| |
| String hashCodes() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("\n["+Integer.toHexString(hashCode)+"]"); |
| for (Map.Entry<String,PropertyGroup> e : groups.entrySet()) { |
| sb.append("\n\t["+Integer.toHexString(e.hashCode())+"] - " + e.getKey()); |
| e.getValue().hashCodes(sb); |
| } |
| return sb.toString(); |
| } |
| } |