All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.config.Config Maven / Gradle / Ivy

There is a newer version: 9.0.1
Show newest version
// ***************************************************************************************************************************
// * 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.config;

import static org.apache.juneau.config.ConfigMod.*;
import static org.apache.juneau.internal.StringUtils.*;
import static org.apache.juneau.internal.ThrowableUtils.*;

import java.beans.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;

import org.apache.juneau.*;
import org.apache.juneau.config.encode.*;
import org.apache.juneau.config.encode.ConfigEncoder;
import org.apache.juneau.config.event.*;
import org.apache.juneau.config.internal.*;
import org.apache.juneau.config.store.*;
import org.apache.juneau.config.vars.*;
import org.apache.juneau.http.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.json.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.svl.*;

/**
 * Main configuration API class.
 *
 * 
See Also:
*
    *
*/ public final class Config extends Context implements ConfigEventListener, Writable { //------------------------------------------------------------------------------------------------------------------- // Configurable properties //------------------------------------------------------------------------------------------------------------------- private static final String PREFIX = "Config."; /** * Configuration property: Configuration name. * *
Property:
*
    *
  • Name: "Config.name.s" *
  • Data type: String *
  • Default: "Configuration.cfg" *
  • Methods: *
      *
    • {@link ConfigBuilder#name(String)} *
    *
* *
Description:
*

* Specifies the configuration name. *
This is typically the configuration file name, although * the name can be anything identifiable by the {@link ConfigStore} used for retrieving and storing the configuration. */ public static final String CONFIG_name = PREFIX + "name.s"; /** * Configuration property: Configuration store. * *

Property:
*
    *
  • Name: "Config.store.o" *
  • Data type: {@link ConfigStore} *
  • Default: {@link ConfigFileStore#DEFAULT} *
  • Methods: *
      *
    • {@link ConfigBuilder#store(ConfigStore)} *
    *
* *
Description:
*

* The configuration store used for retrieving and storing configurations. */ public static final String CONFIG_store = PREFIX + "store.o"; /** * Configuration property: POJO serializer. * *

Property:
*
    *
  • Name: "Config.serializer.o" *
  • Data type: {@link WriterSerializer} *
  • Default: {@link SimpleJsonSerializer#DEFAULT} *
  • Methods: *
      *
    • {@link ConfigBuilder#serializer(Class)} *
    • {@link ConfigBuilder#serializer(WriterSerializer)} *
    *
* *
Description:
*

* The serializer to use for serializing POJO values. */ public static final String CONFIG_serializer = PREFIX + "serializer.o"; /** * Configuration property: POJO parser. * *

Property:
*
    *
  • Name: "Config.parser.o" *
  • Data type: {@link ReaderParser} *
  • Default: {@link JsonParser#DEFAULT} *
  • Methods: *
      *
    • {@link ConfigBuilder#parser(Class)} *
    • {@link ConfigBuilder#parser(ReaderParser)} *
    *
* *
Description:
*

* The parser to use for parsing values to POJOs. */ public static final String CONFIG_parser = PREFIX + "parser.o"; /** * Configuration property: Value encoder. * *

Property:
*
    *
  • Name: "Config.encoder.o" *
  • Data type: {@link ConfigEncoder} *
  • Default: {@link ConfigXorEncoder#INSTANCE} *
  • Methods: *
      *
    • {@link ConfigBuilder#encoder(Class)} *
    • {@link ConfigBuilder#encoder(ConfigEncoder)} *
    *
* *
Description:
*

* The encoder to use for encoding encoded configuration values. */ public static final String CONFIG_encoder = PREFIX + "encoder.o"; /** * Configuration property: SVL variable resolver. * *

Property:
*
    *
  • Name: "Config.varResolver.o" *
  • Data type: {@link VarResolver} *
  • Default: {@link VarResolver#DEFAULT} *
  • Methods: *
      *
    • {@link ConfigBuilder#varResolver(Class)} *
    • {@link ConfigBuilder#varResolver(VarResolver)} *
    *
* *
Description:
*

* The resolver to use for resolving SVL variables. */ public static final String CONFIG_varResolver = PREFIX + "varResolver.o"; /** * Configuration property: Binary value line length. * *

Property:
*
    *
  • Name: "Config.binaryLineLength.i" *
  • Data type: Integer *
  • Default: -1 *
  • Methods: *
      *
    • {@link ConfigBuilder#binaryLineLength(int)} *
    *
* *
Description:
*

* When serializing binary values, lines will be split after this many characters. *
Use -1 to represent no line splitting. */ public static final String CONFIG_binaryLineLength = PREFIX + "binaryLineLength.i"; /** * Configuration property: Binary value format. * *

Property:
*
    *
  • Name: "Config.binaryFormat.s" *
  • Data type: {@link BinaryFormat} *
  • Default: {@link BinaryFormat#BASE64} *
  • Methods: *
      *
    • {@link ConfigBuilder#binaryFormat(BinaryFormat)} *
    *
* *
Description:
*

* The format to use when persisting byte arrays. * *

* Possible values: *

    *
  • {@link BinaryFormat#BASE64} - BASE64-encoded string. *
  • {@link BinaryFormat#HEX} - Hexadecimal. *
  • {@link BinaryFormat#SPACED_HEX} - Hexadecimal with spaces between bytes. *
*/ public static final String CONFIG_binaryFormat = PREFIX + "binaryFormat.s"; /** * Configuration property: Multi-line values should always be on separate lines. * *
Property:
*
    *
  • Name: "Config.multiLineValuesOnSeparateLines.b" *
  • Data type: Boolean *
  • Default: false *
  • Methods: *
      *
    • {@link ConfigBuilder#multiLineValuesOnSeparateLines()} *
    *
* *
Description:
*

* When enabled, multi-line values will always be placed on a separate line from the key. */ public static final String CONFIG_multiLineValuesOnSeparateLines = PREFIX + "multiLineValuesOnSeparateLines.b"; /** * Configuration property: Read-only. * *

Property:
*
    *
  • Name: "Config.readOnly.b" *
  • Data type: Boolean *
  • Default: false *
  • Methods: *
      *
    • {@link ConfigBuilder#readOnly()} *
    *
* *
Description:
*

* When enabled, attempts to call any setters on this object will throw an {@link UnsupportedOperationException}. */ public static final String CONFIG_readOnly = PREFIX + "readOnly.b"; //------------------------------------------------------------------------------------------------------------------- // Instance //------------------------------------------------------------------------------------------------------------------- private final String name; private final ConfigStore store; private final WriterSerializer serializer; private final ReaderParser parser; private final ConfigEncoder encoder; private final VarResolverSession varSession; private final int binaryLineLength; private final BinaryFormat binaryFormat; private final boolean multiLineValuesOnSeparateLines, readOnly; private final ConfigMap configMap; private final BeanSession beanSession; private final List listeners = Collections.synchronizedList(new LinkedList()); /** * Instantiates a new clean-slate {@link ConfigBuilder} object. * *

* This is equivalent to simply calling new ConfigBuilder(). * * @return A new {@link ConfigBuilder} object. */ public static ConfigBuilder create() { return new ConfigBuilder(); } /** * Same as {@link #create()} but initializes the builder with the specified config name. * *

* This is equivalent to simply calling new ConfigBuilder().name(name). * * @param name The configuration name. * @return A new {@link ConfigBuilder} object. */ public static ConfigBuilder create(String name) { return new ConfigBuilder().name(name); } @Override /* Context */ public ConfigBuilder builder() { return new ConfigBuilder(getPropertyStore()); } /** * Constructor. * * @param ps * The property store containing all the settings for this object. * @throws IOException */ public Config(PropertyStore ps) throws IOException { super(ps); name = getStringProperty(CONFIG_name, "Configuration.cfg"); store = getInstanceProperty(CONFIG_store, ConfigStore.class, ConfigFileStore.DEFAULT); configMap = store.getMap(name); configMap.register(this); serializer = getInstanceProperty(CONFIG_serializer, WriterSerializer.class, SimpleJsonSerializer.DEFAULT); parser = getInstanceProperty(CONFIG_parser, ReaderParser.class, JsonParser.DEFAULT); beanSession = parser.createBeanSession(); encoder = getInstanceProperty(CONFIG_encoder, ConfigEncoder.class, ConfigXorEncoder.INSTANCE); varSession = getInstanceProperty(CONFIG_varResolver, VarResolver.class, VarResolver.DEFAULT) .builder() .vars(ConfigVar.class) .contextObject(ConfigVar.SESSION_config, this) .build() .createSession(); binaryLineLength = getIntegerProperty(CONFIG_binaryLineLength, -1); binaryFormat = getProperty(CONFIG_binaryFormat, BinaryFormat.class, BinaryFormat.BASE64); multiLineValuesOnSeparateLines = getBooleanProperty(CONFIG_multiLineValuesOnSeparateLines, false); readOnly = getBooleanProperty(CONFIG_readOnly, false); } Config(Config copyFrom, VarResolverSession varSession) { super(null); name = copyFrom.name; store = copyFrom.store; configMap = copyFrom.configMap; configMap.register(this); serializer = copyFrom.serializer; parser = copyFrom.parser; encoder = copyFrom.encoder; this.varSession = varSession; binaryLineLength = copyFrom.binaryLineLength; binaryFormat = copyFrom.binaryFormat; multiLineValuesOnSeparateLines = copyFrom.multiLineValuesOnSeparateLines; readOnly = copyFrom.readOnly; beanSession = copyFrom.beanSession; } /** * Creates a copy of this config using the specified var session for resolving variables. * *

* This creates a shallow copy of the config but replacing the variable resolver. * * @param varSession The var session used for resolving string variables. * @return A new config object. */ public Config resolving(VarResolverSession varSession) { return new Config(this, varSession); } //----------------------------------------------------------------------------------------------------------------- // Workhorse getters //----------------------------------------------------------------------------------------------------------------- /** * Returns the specified value as a string from the config file. * *

* Unlike {@link #getString(String)}, this method doesn't replace SVL variables. * * @param key The key. * @return The value, or null if the section or value doesn't exist. */ public String get(String key) { String sname = sname(key); String skey = skey(key); ConfigEntry ce = configMap.getEntry(sname, skey); if (ce == null || ce.getValue() == null) return null; String val = ce.getValue(); for (ConfigMod m : ConfigMod.asModifiersReverse(ce.getModifiers())) { if (m == ENCODED) { if (encoder.isEncoded(val)) val = encoder.decode(key, val); } } return val; } //----------------------------------------------------------------------------------------------------------------- // Workhorse setters //----------------------------------------------------------------------------------------------------------------- /** * Sets a value in this config. * * @param key The key. * @param value The value. * @return This object (for method chaining). * @throws UnsupportedOperationException If configuration is read only. */ public Config set(String key, String value) { checkWrite(); assertFieldNotNull(key, "key"); String sname = sname(key); String skey = skey(key); ConfigEntry ce = configMap.getEntry(sname, skey); if (ce == null && value == null) return this; String mod = ce == null ? "" : ce.getModifiers(); String s = asString(value); for (ConfigMod m : ConfigMod.asModifiers(mod)) { if (m == ENCODED) { s = encoder.encode(key, s); } } configMap.setEntry(sname, skey, s, null, null, null); return this; } /** * Adds or replaces an entry with the specified key with a POJO serialized to a string using the registered * serializer. * *

* Equivalent to calling put(key, value, isEncoded(key)). * * @param key The key. * @param value The new value POJO. * @return The previous value, or null if the section or key did not previously exist. * @throws SerializeException * If serializer could not serialize the value or if a serializer is not registered with this config file. * @throws UnsupportedOperationException If configuration is read only. */ public Config set(String key, Object value) throws SerializeException { return set(key, value, null); } /** * Same as {@link #set(String, Object)} but allows you to specify the serializer to use to serialize the * value. * * @param key The key. * @param value The new value. * @param serializer * The serializer to use for serializing the object. * If null, then uses the predefined serializer on the config file. * @return The previous value, or null if the section or key did not previously exist. * @throws SerializeException * If serializer could not serialize the value or if a serializer is not registered with this config file. * @throws UnsupportedOperationException If configuration is read only. */ public Config set(String key, Object value, Serializer serializer) throws SerializeException { return set(key, serialize(value, serializer)); } /** * Same as {@link #set(String, Object)} but allows you to specify all aspects of a value. * * @param key The key. * @param value The new value. * @param serializer * The serializer to use for serializing the object. * If null, then uses the predefined serializer on the config file. * @param modifier * Optional modifier to apply to the value. *
If null, then previous value will not be replaced. * @param comment * Optional same-line comment to add to this value. *
If null, then previous value will not be replaced. * @param preLines * Optional comment or blank lines to add before this entry. *
If null, then previous value will not be replaced. * @return The previous value, or null if the section or key did not previously exist. * @throws SerializeException * If serializer could not serialize the value or if a serializer is not registered with this config file. * @throws UnsupportedOperationException If configuration is read only. */ public Config set(String key, Object value, Serializer serializer, ConfigMod modifier, String comment, List preLines) throws SerializeException { return set(key, value, serializer, modifier == null ? null : new ConfigMod[]{modifier}, comment, preLines); } /** * Same as {@link #set(String, Object)} but allows you to specify all aspects of a value. * * @param key The key. * @param value The new value. * @param serializer * The serializer to use for serializing the object. * If null, then uses the predefined serializer on the config file. * @param modifiers * Optional modifiers to apply to the value. *
If null, then previous value will not be replaced. * @param comment * Optional same-line comment to add to this value. *
If null, then previous value will not be replaced. * @param preLines * Optional comment or blank lines to add before this entry. *
If null, then previous value will not be replaced. * @return The previous value, or null if the section or key did not previously exist. * @throws SerializeException * If serializer could not serialize the value or if a serializer is not registered with this config file. * @throws UnsupportedOperationException If configuration is read only. */ public Config set(String key, Object value, Serializer serializer, ConfigMod[] modifiers, String comment, List preLines) throws SerializeException { checkWrite(); assertFieldNotNull(key, "key"); String sname = sname(key); String skey = skey(key); String s = serialize(value, serializer); if (modifiers != null) { for (ConfigMod m : modifiers) { if (m == ENCODED) { s = encoder.encode(key, s); } } } configMap.setEntry(sname, skey, s, modifiers == null ? null : ConfigMod.asString(modifiers), comment, preLines); return this; } /** * Removes an entry with the specified key. * * @param key The key. * @return The previous value, or null if the section or key did not previously exist. * @throws UnsupportedOperationException If configuration is read only. */ public Config remove(String key) { checkWrite(); String sname = sname(key); String skey = skey(key); configMap.removeEntry(sname, skey); return this; } /** * Encodes and unencoded entries in this config. * *

* If any entries in the config are marked as encoded but not actually encoded, * this will encode them. * * @return This object (for method chaining). * @throws UnsupportedOperationException If configuration is read only. */ public Config encodeEntries() { checkWrite(); for (String section : configMap.getSections()) { for (String key : configMap.getKeys(section)) { ConfigEntry ce = configMap.getEntry(section, key); if (ce != null && ce.hasModifier('*') && ! encoder.isEncoded(ce.getValue())) { configMap.setEntry(section, key, encoder.encode(section + '/' + key, ce.getValue()), null, null, null); } } } return this; } //----------------------------------------------------------------------------------------------------------------- // API methods //----------------------------------------------------------------------------------------------------------------- /** * Gets the entry with the specified key. * *

* The key can be in one of the following formats... *

    *
  • * "key" - A value in the default section (i.e. defined above any [section] header). *
  • * "section/key" - A value from the specified section. *
* * @param key The key. * @return The value, or null if the section or key does not exist. */ public String getString(String key) { String s = get(key); if (s == null) return null; if (varSession != null) s = varSession.resolve(s); return s; } /** * Gets the entry with the specified key. * *

* The key can be in one of the following formats... *

    *
  • * "key" - A value in the default section (i.e. defined above any [section] header). *
  • * "section/key" - A value from the specified section. *
* * @param key The key. * @param def The default value if the value does not exist. * @return The value, or the default value if the section or key does not exist. */ public String getString(String key, String def) { String s = get(key); if (isEmpty(s)) return def; if (varSession != null) s = varSession.resolve(s); return s; } /** * Gets the entry with the specified key, splits the value on commas, and returns the values as trimmed strings. * * @param key The key. * @return The value, or an empty array if the section or key does not exist. */ public String[] getStringArray(String key) { return getStringArray(key, new String[0]); } /** * Same as {@link #getStringArray(String)} but returns a default value if the value cannot be found. * * @param key The key. * @param def The default value if the value does not exist. * @return The value, or the default value if the section or key does not exist or is blank. */ public String[] getStringArray(String key, String[] def) { String s = getString(key); if (isEmpty(s)) return def; String[] r = split(s); return r.length == 0 ? def : r; } /** * Convenience method for getting int config values. * *

* "K", "M", and "G" can be used to identify kilo, mega, and giga. * *

Example:
*
    *
  • * "100K" => 1024000 *
  • * "100M" => 104857600 *
* *

* Uses {@link Integer#decode(String)} underneath, so any of the following integer formats are supported: *

    *
  • "0x..." *
  • "0X..." *
  • "#..." *
  • "0..." *
* * @param key The key. * @return The value, or 0 if the value does not exist or the value is empty. */ public int getInt(String key) { return getInt(key, 0); } /** * Same as {@link #getInt(String)} but returns a default value if not set. * * @param key The key. * @param def The default value if the value does not exist. * @return The value, or the default value if the value does not exist or the value is empty. */ public int getInt(String key, int def) { String s = getString(key); if (isEmpty(s)) return def; return parseIntWithSuffix(s); } /** * Convenience method for getting boolean config values. * * @param key The key. * @return The value, or false if the section or key does not exist or cannot be parsed as a boolean. */ public boolean getBoolean(String key) { return getBoolean(key, false); } /** * Convenience method for getting boolean config values. * * @param key The key. * @param def The default value if the value does not exist. * @return The value, or the default value if the section or key does not exist or cannot be parsed as a boolean. */ public boolean getBoolean(String key, boolean def) { String s = getString(key); return isEmpty(s) ? def : Boolean.parseBoolean(s); } /** * Convenience method for getting long config values. * *

* "K", "M", and "G" can be used to identify kilo, mega, and giga. * *

Example:
*
    *
  • * "100K" => 1024000 *
  • * "100M" => 104857600 *
* *

* Uses {@link Long#decode(String)} underneath, so any of the following number formats are supported: *

    *
  • "0x..." *
  • "0X..." *
  • "#..." *
  • "0..." *
* * @param key The key. * @return The value, or 0 if the value does not exist or the value is empty. */ public long getLong(String key) { return getLong(key, 0); } /** * Same as {@link #getLong(String)} but returns a default value if not set. * * @param key The key. * @param def The default value if the value does not exist. * @return The value, or the default value if the value does not exist or the value is empty. */ public long getLong(String key, long def) { String s = getString(key); if (isEmpty(s)) return def; return parseLongWithSuffix(s); } /** * Convenience method for getting double config values. * *

* Uses {@link Double#valueOf(String)} underneath, so any of the following number formats are supported: *

    *
  • "0x..." *
  • "0X..." *
  • "#..." *
  • "0..." *
* * @param key The key. * @return The value, or 0 if the value does not exist or the value is empty. */ public double getDouble(String key) { return getDouble(key, 0); } /** * Same as {@link #getDouble(String)} but returns a default value if not set. * * @param key The key. * @param def The default value if the value does not exist. * @return The value, or the default value if the value does not exist or the value is empty. */ public double getDouble(String key, double def) { String s = getString(key); if (isEmpty(s)) return def; return Double.valueOf(s); } /** * Convenience method for getting float config values. * *

* Uses {@link Float#valueOf(String)} underneath, so any of the following number formats are supported: *

    *
  • "0x..." *
  • "0X..." *
  • "#..." *
  • "0..." *
* * @param key The key. * @return The value, or 0 if the value does not exist or the value is empty. */ public float getFloat(String key) { return getFloat(key, 0); } /** * Same as {@link #getFloat(String)} but returns a default value if not set. * * @param key The key. * @param def The default value if the value does not exist. * @return The value, or the default value if the value does not exist or the value is empty. */ public float getFloat(String key, float def) { String s = getString(key); if (isEmpty(s)) return def; return Float.valueOf(s); } /** * Convenience method for getting byte array config values. * *

* This is equivalent to calling the following: *

* byte[] b = config.getObject(key, byte[].class); *

* * Byte arrays are stored as encoded strings, typically BASE64, but dependent on the {@link #CONFIG_binaryFormat} setting. * * @param key The key. * @return The value, or null if the section or key does not exist. * @throws ParseException If value could not be converted to a byte array. */ public byte[] getBytes(String key) throws ParseException { String s = get(key); if (s == null) return null; if (s.isEmpty()) return new byte[0]; return getObject(key, byte[].class); } /** * Same as {@link #getBytes(String)} but with a default value if the entry doesn't exist. * * @param key The key. * @param def The default value if the value does not exist. * @return The value, or the default value if the section or key does not exist. * @throws ParseException If value could not be converted to a byte array. */ public byte[] getBytes(String key, byte[] def) throws ParseException { String s = get(key); if (s == null) return def; if (s.isEmpty()) return def; return getObjectWithDefault(key, def, byte[].class); } /** * Gets the entry with the specified key and converts it to the specified value. * *

* The key can be in one of the following formats... *

    *
  • * "key" - A value in the default section (i.e. defined above any [section] header). *
  • * "section/key" - A value from the specified section. *
* *

* The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). * *

Examples:
*

* Config cf = Config.create().name("MyConfig.cfg").build(); * * // Parse into a linked-list of strings. * List l = cf.getObject("MySection/myListOfStrings", LinkedList.class, String.class); * * // Parse into a linked-list of beans. * List l = cf.getObject("MySection/myListOfBeans", LinkedList.class, MyBean.class); * * // Parse into a linked-list of linked-lists of strings. * List l = cf.getObject("MySection/my2dListOfStrings", LinkedList.class, * LinkedList.class, String.class); * * // Parse into a map of string keys/values. * Map m = cf.getObject("MySection/myMap", TreeMap.class, String.class, * String.class); * * // Parse into a map containing string keys and values of lists containing beans. * Map m = cf.getObject("MySection/myMapOfListsOfBeans", TreeMap.class, String.class, * List.class, MyBean.class); *

* *

* Collection classes are assumed to be followed by zero or one objects indicating the element type. * *

* Map classes are assumed to be followed by zero or two meta objects indicating the key and value * types. * *

* The array can be arbitrarily long to indicate arbitrarily complex data structures. * *

Notes:
*
    *
  • * Use the {@link #getObject(String, Class)} method instead if you don't need a parameterized map/collection. *
* * @param key The key. * @param type * The object type to create. *
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} * @param args * The type arguments of the class if it's a collection or map. *
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} *
Ignored if the main type is not a map or collection. * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. * @return The value, or null if the section or key does not exist. */ public T getObject(String key, Type type, Type...args) throws ParseException { return getObject(key, (Parser)null, type, args); } /** * Same as {@link #getObject(String, Type, Type...)} but allows you to specify the parser to use to parse the value. * * @param key The key. * @param parser * The parser to use for parsing the object. * If null, then uses the predefined parser on the config file. * @param type * The object type to create. *
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} * @param args * The type arguments of the class if it's a collection or map. *
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} *
Ignored if the main type is not a map or collection. * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. * @return The value, or null if the section or key does not exist. */ public T getObject(String key, Parser parser, Type type, Type...args) throws ParseException { assertFieldNotNull(type, "type"); return parse(getString(key), parser, type, args); } /** * Same as {@link #getObject(String, Type, Type...)} except optimized for a non-parameterized class. * *

* This is the preferred parse method for simple types since you don't need to cast the results. * *

Examples:
*

* Config cf = Config.create().name("MyConfig.cfg").build(); * * // Parse into a string. * String s = cf.getObject("MySection/mySimpleString", String.class); * * // Parse into a bean. * MyBean b = cf.getObject("MySection/myBean", MyBean.class); * * // Parse into a bean array. * MyBean[] b = cf.getObject("MySection/myBeanArray", MyBean[].class); * * // Parse into a linked-list of objects. * List l = cf.getObject("MySection/myList", LinkedList.class); * * // Parse into a map of object keys/values. * Map m = cf.getObject("MySection/myMap", TreeMap.class); *

* * @param The class type of the object being created. * @param key The key. * @param type The object type to create. * @return The parsed object. * @throws ParseException * If the input contains a syntax error or is malformed, or is not valid for the specified type. * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections. */ public T getObject(String key, Class type) throws ParseException { return getObject(key, (Parser)null, type); } /** * Same as {@link #getObject(String, Class)} but allows you to specify the parser to use to parse the value. * * @param The class type of the object being created. * @param key The key. * @param parser * The parser to use for parsing the object. * If null, then uses the predefined parser on the config file. * @param type The object type to create. * @return The parsed object. * @throws ParseException * If the input contains a syntax error or is malformed, or is not valid for the specified type. * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections. */ public T getObject(String key, Parser parser, Class type) throws ParseException { assertFieldNotNull(type, "c"); return parse(getString(key), parser, type); } /** * Gets the entry with the specified key and converts it to the specified value. * *

* Same as {@link #getObject(String, Class)}, but with a default value. * * @param key The key. * @param def The default value if the value does not exist. * @param type The class to convert the value to. * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. * @return The value, or null if the section or key does not exist. */ public T getObjectWithDefault(String key, T def, Class type) throws ParseException { return getObjectWithDefault(key, null, def, type); } /** * Same as {@link #getObjectWithDefault(String, Object, Class)} but allows you to specify the parser to use to parse * the value. * * @param key The key. * @param parser * The parser to use for parsing the object. * If null, then uses the predefined parser on the config file. * @param def The default value if the value does not exist. * @param type The class to convert the value to. * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. * @return The value, or null if the section or key does not exist. */ public T getObjectWithDefault(String key, Parser parser, T def, Class type) throws ParseException { assertFieldNotNull(type, "c"); T t = parse(getString(key), parser, type); return (t == null ? def : t); } /** * Gets the entry with the specified key and converts it to the specified value. * *

* Same as {@link #getObject(String, Type, Type...)}, but with a default value. * * @param key The key. * @param def The default value if the value does not exist. * @param type * The object type to create. *
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} * @param args * The type arguments of the class if it's a collection or map. *
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} *
Ignored if the main type is not a map or collection. * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. * @return The value, or null if the section or key does not exist. */ public T getObjectWithDefault(String key, T def, Type type, Type...args) throws ParseException { return getObjectWithDefault(key, null, def, type, args); } /** * Same as {@link #getObjectWithDefault(String, Object, Type, Type...)} but allows you to specify the parser to use * to parse the value. * * @param key The key. * @param parser * The parser to use for parsing the object. * If null, then uses the predefined parser on the config file. * @param def The default value if the value does not exist. * @param type * The object type to create. *
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} * @param args * The type arguments of the class if it's a collection or map. *
Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} *
Ignored if the main type is not a map or collection. * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. * @return The value, or null if the section or key does not exist. */ public T getObjectWithDefault(String key, Parser parser, T def, Type type, Type...args) throws ParseException { assertFieldNotNull(type, "type"); T t = parse(getString(key), parser, type, args); return (t == null ? def : t); } /** * Returns the keys of the entries in the specified section. * * @param section * The section name to write from. *
If empty, refers to the default section. *
Must not be null. * @return * An unmodifiable set of keys, or an empty set if the section doesn't exist. */ public Set getKeys(String section) { return configMap.getKeys(section(section)); } /** * Copies the entries in a section to the specified bean by calling the public setters on that bean. * * @param section * The section name to write from. *
If empty, refers to the default section. *
Must not be null. * @param bean The bean to set the properties on. * @param ignoreUnknownProperties * If true, don't throw an {@link IllegalArgumentException} if this section contains a key that doesn't * correspond to a setter method. * @return An object map of the changes made to the bean. * @throws ParseException If parser was not set on this config file or invalid properties were found in the section. * @throws IllegalArgumentException * @throws IllegalAccessException * @throws InvocationTargetException * @throws UnsupportedOperationException If configuration is read only. */ public Config writeProperties(String section, Object bean, boolean ignoreUnknownProperties) throws ParseException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { checkWrite(); assertFieldNotNull(bean, "bean"); section = section(section); Set keys = configMap.getKeys(section); if (keys == null) throw new IllegalArgumentException("Section '"+section+"' not found in configuration."); BeanMap bm = beanSession.toBeanMap(bean); for (String k : keys) { BeanPropertyMeta bpm = bm.getPropertyMeta(k); if (bpm == null) { if (! ignoreUnknownProperties) throw new ParseException("Unknown property ''{0}'' encountered in configuration section ''{1}''.", k, section); } else { bm.put(k, getObject(section + '/' + k, bpm.getClassMeta().getInnerClass())); } } return this; } /** * Shortcut for calling getSectionAsBean(sectionName, c, false). * * @param section * The section name to write from. *
If empty, refers to the default section. *
Must not be null. * @param c The bean class to create. * @return A new bean instance. * @throws ParseException */ public T getSectionAsBean(String section, Classc) throws ParseException { return getSectionAsBean(section, c, false); } /** * Converts this config file section to the specified bean instance. * *

* Key/value pairs in the config file section get copied as bean property values to the specified bean class. * *

Example config file
*

* [MyAddress] * name = John Smith * street = 123 Main Street * city = Anywhere * state = NY * zip = 12345 *

* *
Example bean
*

* public class Address { * public String name, street, city; * public StateEnum state; * public int zip; * } *

* *
Example usage
*

* Config cf = Config.create().name("MyConfig.cfg").build(); * Address myAddress = cf.getSectionAsBean("MySection", Address.class); *

* * @param section * The section name to write from. *
If empty, refers to the default section. *
Must not be null. * @param c The bean class to create. * @param ignoreUnknownProperties * If false, throws a {@link ParseException} if the section contains an entry that isn't a bean property * name. * @return A new bean instance, or null if the section doesn't exist. * @throws ParseException */ public T getSectionAsBean(String section, Class c, boolean ignoreUnknownProperties) throws ParseException { assertFieldNotNull(c, "c"); section = section(section); if (! configMap.hasSection(section)) return null; Set keys = configMap.getKeys(section); BeanMap bm = beanSession.newBeanMap(c); for (String k : keys) { BeanPropertyMeta bpm = bm.getPropertyMeta(k); if (bpm == null) { if (! ignoreUnknownProperties) throw new ParseException("Unknown property ''{0}'' encountered in configuration section ''{1}''.", k, section); } else { bm.put(k, getObject(section + '/' + k, bpm.getClassMeta().getInnerClass())); } } return bm.getBean(); } /** * Returns a section of this config copied into an {@link ObjectMap}. * * @param section * The section name to write from. *
If empty, refers to the default section. *
Must not be null. * @return A new {@link ObjectMap}, or null if the section doesn't exist. * @throws ParseException */ public ObjectMap getSectionAsMap(String section) throws ParseException { section = section(section); if (! configMap.hasSection(section)) return null; Set keys = configMap.getKeys(section); ObjectMap om = new ObjectMap(); for (String k : keys) om.put(k, getObject(section + '/' + k, Object.class)); return om; } /** * Wraps a config file section inside a Java interface so that values in the section can be read and * write using getters and setters. * *
Example config file
*

* [MySection] * string = foo * int = 123 * enum = ONE * bean = {foo:'bar',baz:123} * int3dArray = [[[123,null],null],null] * bean1d3dListMap = {key:[[[[{foo:'bar',baz:123}]]]]} *

* *
Example interface
*

* public interface MyConfigInterface { * * String getString(); * void setString(String x); * * int getInt(); * void setInt(int x); * * MyEnum getEnum(); * void setEnum(MyEnum x); * * MyBean getBean(); * void setBean(MyBean x); * * int[][][] getInt3dArray(); * void setInt3dArray(int[][][] x); * * Map<String,List<MyBean[][][]>> getBean1d3dListMap(); * void setBean1d3dListMap(Map<String,List<MyBean[][][]>> x); * } *

* *
Example usage
*

* Config cf = Config.create().name("MyConfig.cfg").build(); * * MyConfigInterface ci = cf.getSectionAsInterface("MySection", MyConfigInterface.class); * * int myInt = ci.getInt(); * * ci.setBean(new MyBean()); * * cf.save(); *

* *
*
    *
  • Calls to setters when the configuration is read-only will cause {@link UnsupportedOperationException} to be thrown. *
* * @param section * The section name to write from. *
If empty, refers to the default section. *
Must not be null. * @param c The proxy interface class. * @return The proxy interface. */ @SuppressWarnings("unchecked") public T getSectionAsInterface(String section, final Class c) { assertFieldNotNull(c, "c"); final String section2 = section(section); if (! c.isInterface()) throw new IllegalArgumentException("Class '"+c.getName()+"' passed to getSectionAsInterface() is not an interface."); InvocationHandler h = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { BeanInfo bi = Introspector.getBeanInfo(c, null); for (PropertyDescriptor pd : bi.getPropertyDescriptors()) { Method rm = pd.getReadMethod(), wm = pd.getWriteMethod(); if (method.equals(rm)) return Config.this.getObject(section2 + '/' + pd.getName(), rm.getGenericReturnType()); if (method.equals(wm)) return Config.this.set(section2 + '/' + pd.getName(), args[0]); } throw new UnsupportedOperationException("Unsupported interface method. method='" + method + "'"); } }; return (T)Proxy.newProxyInstance(c.getClassLoader(), new Class[] { c }, h); } /** * Returns true if this section contains the specified key and the key has a non-blank value. * * @param key The key. * @return true if this section contains the specified key and the key has a non-blank value. */ public boolean exists(String key) { return isNotEmpty(getString(key, null)); } /** * Creates the specified section if it doesn't exist. * *

* Returns the existing section if it already exists. * * @param name * The section name. *
Must not be null. *
Use blank for the default section. * @param preLines * Optional comment and blank lines to add immediately before the section. *
If null, previous pre-lines will not be replaced. * @return The appended or existing section. * @throws UnsupportedOperationException If configuration is read only. */ public Config setSection(String name, List preLines) { try { return setSection(section(name), preLines, null); } catch (SerializeException e) { throw new RuntimeException(e); // Impossible. } } /** * Creates the specified section if it doesn't exist. * * @param name * The section name. *
Must not be null. *
Use blank for the default section. * @param preLines * Optional comment and blank lines to add immediately before the section. *
If null, previous pre-lines will not be replaced. * @param contents * Values to set in the new section. *
Can be null. * @return The appended or existing section. * @throws SerializeException * @throws UnsupportedOperationException If configuration is read only. */ public Config setSection(String name, List preLines, Map contents) throws SerializeException { checkWrite(); configMap.setSection(section(name), preLines); if (contents != null) for (Map.Entry e : contents.entrySet()) set(section(name) + '/' + e.getKey(), e.getValue()); return this; } /** * Removes the section with the specified name. * * @param name The name of the section to remove * @return This object (for method chaining). * @throws UnsupportedOperationException If configuration is read only. */ public Config removeSection(String name) { checkWrite(); configMap.removeSection(name); return this; } /** * Loads the contents of the specified map of maps into this config. * * @param m The maps to load. * @return This object (for method chaining). * @throws SerializeException */ public Config load(Map> m) throws SerializeException { if (m != null) for (Map.Entry> e : m.entrySet()) { setSection(e.getKey(), null, e.getValue()); } return this; } /** * Commit the changes in this config to the store. * * @return This object (for method chaining). * @throws IOException * @throws UnsupportedOperationException If configuration is read only. */ public Config commit() throws IOException { checkWrite(); configMap.commit(); return this; } /** * Saves this config file to the specified writer as an INI file. * *

* The writer will automatically be closed. * * @param w The writer to send the output to. * @return This object (for method chaining). * @throws IOException If a problem occurred trying to send contents to the writer. */ @Override /* Writable */ public Writer writeTo(Writer w) throws IOException { return configMap.writeTo(w); } /** * Add a listener to this config to react to modification events. * *

* Listeners should be removed using {@link #removeListener(ConfigEventListener)}. * * @param listener The new listener to add. * @return This object (for method chaining). */ public Config addListener(ConfigEventListener listener) { listeners.add(listener); return this; } /** * Removes a listener from this config. * * @param listener The listener to remove. * @return This object (for method chaining). */ public Config removeListener(ConfigEventListener listener) { listeners.remove(listener); return this; } /** * Closes this configuration object by unregistering it from the underlying config map. * * @throws IOException */ public void close() throws IOException { configMap.unregister(this); } /** * Overwrites the contents of the config file. * * @param contents The new contents of the config file. * @param synchronous Wait until the change has been persisted before returning this map. * @return This object (for method chaining). * @throws IOException * @throws InterruptedException * @throws UnsupportedOperationException If configuration is read only. */ public Config load(Reader contents, boolean synchronous) throws IOException, InterruptedException { checkWrite(); configMap.load(IOUtils.read(contents), synchronous); return this; } /** * Overwrites the contents of the config file. * * @param contents The new contents of the config file. * @param synchronous Wait until the change has been persisted before returning this map. * @return This object (for method chaining). * @throws IOException * @throws InterruptedException * @throws UnsupportedOperationException If configuration is read only. */ public Config load(String contents, boolean synchronous) throws IOException, InterruptedException { checkWrite(); configMap.load(contents, synchronous); return this; } /** * Does a rollback of any changes on this config currently in memory. * * @return This object (for method chaining). * @throws UnsupportedOperationException If configuration is read only. */ public Config rollback() { checkWrite(); configMap.rollback(); return this; } /** * Returns the values in this config map as a map of maps. * *

* This is considered a snapshot copy of the config map. * *

* The returned map is modifiable, but modifications to the returned map are not reflected in the config map. * * @return A copy of this config as a map of maps. */ @Override /* Context */ public ObjectMap asMap() { return configMap.asMap(); } //----------------------------------------------------------------------------------------------------------------- // Interface methods //----------------------------------------------------------------------------------------------------------------- /** * Unused. */ @Override /* Context */ public Session createSession(SessionArgs args) { throw new UnsupportedOperationException(); } /** * Unused. */ @Override /* Context */ public SessionArgs createDefaultSessionArgs() { throw new UnsupportedOperationException(); } @Override /* ConfigEventListener */ public void onConfigChange(List events) { for (ConfigEventListener l : listeners) l.onConfigChange(events); } @Override /* Writable */ public MediaType getMediaType() { return MediaType.PLAIN; } //----------------------------------------------------------------------------------------------------------------- // Private methods //----------------------------------------------------------------------------------------------------------------- private String serialize(Object value, Serializer serializer) throws SerializeException { if (value == null) return ""; if (serializer == null) serializer = this.serializer; Class c = value.getClass(); if (value instanceof CharSequence) return nlIfMl((CharSequence)value); if (isSimpleType(c)) return value.toString(); if (value instanceof byte[]) { String s = null; byte[] b = (byte[])value; if (binaryFormat == BinaryFormat.HEX) s = toHex(b); else if (binaryFormat == BinaryFormat.SPACED_HEX) s = toSpacedHex(b); else s = base64Encode(b); int l = binaryLineLength; if (l <= 0 || s.length() <= l) return s; StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i += l) sb.append(binaryLineLength > 0 ? "\n" : "").append(s.substring(i, Math.min(s.length(), i + l))); return sb.toString(); } String r = null; if (multiLineValuesOnSeparateLines) r = "\n" + (String)serializer.serialize(value); else r = (String)serializer.serialize(value); if (r.startsWith("'")) return r.substring(1, r.length()-1); return r; } private String nlIfMl(CharSequence cs) { String s = cs.toString(); if (s.indexOf('\n') != -1 && multiLineValuesOnSeparateLines) return "\n" + s; return s; } @SuppressWarnings({ "unchecked" }) private T parse(String s, Parser parser, Type type, Type...args) throws ParseException { if (isEmpty(s)) return null; if (isSimpleType(type)) return (T)beanSession.convertToType(s, (Class)type); if (type == byte[].class) { if (s.indexOf('\n') != -1) s = s.replaceAll("\n", ""); try { switch (binaryFormat) { case HEX: return (T)fromHex(s); case SPACED_HEX: return (T)fromSpacedHex(s); default: return (T)base64Decode(s); } } catch (Exception e) { throw new ParseException(e, "Value could not be converted to a byte array."); } } if (parser == null) parser = this.parser; if (parser instanceof JsonParser) { char s1 = firstNonWhitespaceChar(s); if (isArray(type) && s1 != '[') s = '[' + s + ']'; else if (s1 != '[' && s1 != '{' && ! "null".equals(s)) s = '\'' + s + '\''; } return parser.parse(s, type, args); } private boolean isSimpleType(Type t) { if (! (t instanceof Class)) return false; Class c = (Class)t; return (c == String.class || c.isPrimitive() || c.isAssignableFrom(Number.class) || c == Boolean.class || c.isEnum()); } private boolean isArray(Type t) { if (! (t instanceof Class)) return false; Class c = (Class)t; return (c.isArray()); } private String sname(String key) { assertFieldNotNull(key, "key"); int i = key.indexOf('/'); if (i == -1) return ""; return key.substring(0, i); } private String skey(String key) { int i = key.indexOf('/'); if (i == -1) return key; return key.substring(i+1); } private String section(String section) { assertFieldNotNull(section, "section"); if (isEmpty(section)) return ""; return section; } private void checkWrite() { if (readOnly) throw new UnsupportedOperationException("Cannot call this method on a read-only configuration."); } //----------------------------------------------------------------------------------------------------------------- // Other methods //----------------------------------------------------------------------------------------------------------------- @Override /* Object */ public String toString() { return configMap.toString(); } @Override /* Object */ protected void finalize() throws Throwable { close(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy