org.apache.juneau.ini.ConfigFile Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * 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.ini;
import static java.lang.reflect.Modifier.*;
import static org.apache.juneau.ini.ConfigFileFormat.*;
import static org.apache.juneau.ini.ConfigUtils.*;
import static org.apache.juneau.internal.ThrowableUtils.*;
import static org.apache.juneau.internal.StringUtils.*;
import static org.apache.juneau.internal.ArrayUtils.*;
import java.beans.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.svl.*;
/**
* Implements the API for accessing the contents of a config file.
*
*
* Refer to org.apache.juneau.ini for usage information.
*/
public abstract class ConfigFile implements Map {
//--------------------------------------------------------------------------------
// Abstract methods
//--------------------------------------------------------------------------------
/**
* Retrieves an entry value from this config file.
*
* @param sectionName The section name. Must not be null .
* @param sectionKey The section key. Must not be null .
* @return The value, or the default value if the section or value doesn't exist.
*/
public abstract String get(String sectionName, String sectionKey);
/**
* Sets an entry value in this config file.
*
* @param sectionName The section name. Must not be null .
* @param sectionKey The section key. Must not be null .
* @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 encoded If true , then encode the value using the encoder associated with this config file.
* @param newline If true , then put serialized output on a separate line from the key.
* @return The previous value, or null if the section or key did not previously exist.
* @throws SerializeException If the object value could not be converted to a JSON string for some reason.
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract String put(String sectionName, String sectionKey, Object value, Serializer serializer,
boolean encoded, boolean newline) throws SerializeException;
/**
* Identical to {@link #put(String, String, Object, Serializer, boolean, boolean)} except used when the value is a
* simple string to avoid having to catch a {@link SerializeException}.
*
* @param sectionName The section name. Must not be null .
* @param sectionKey The section key. Must not be null .
* @param value The new value.
* @param encoded
* @return The previous value, or null if the section or key did not previously exist.
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract String put(String sectionName, String sectionKey, String value, boolean encoded);
/**
* Removes an entry from this config file.
*
* @param sectionName The section name. Must not be null .
* @param sectionKey The section key. Must not be null .
* @return The previous value, or null if the section or key did not previously exist.
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract String remove(String sectionName, String sectionKey);
/**
* Returns the current set of keys in the specified section.
*
* @param sectionName The section name. Must not be null .
* @return The list of keys in the specified section, or null if section does not exist.
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract Set getSectionKeys(String sectionName);
/**
* Reloads this config file object from the persisted file contents if the modified timestamp on the file has changed.
*
* @return This object (for method chaining).
* @throws IOException If file could not be read, or file is not associated with this object.
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract ConfigFile loadIfModified() throws IOException;
/**
* Loads this config file object from the persisted file contents.
*
* @return This object (for method chaining).
* @throws IOException If file could not be read, or file is not associated with this object.
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract ConfigFile load() throws IOException;
/**
* Loads this config file object from the specified reader.
*
* @param r The reader to read from.
* @return This object (for method chaining).
* @throws IOException If file could not be read, or file is not associated with this object.
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract ConfigFile load(Reader r) throws IOException;
/**
* Adds arbitrary lines to the specified config file section.
*
*
* The lines can be any of the following....
*
* -
*
"# comment" - A comment line.
* -
*
"key=val" - A key/value pair (equivalent to calling {@link #put(String,Object)}.
* -
*
" foobar " - Anything else (interpreted as a comment).
*
*
*
* If the section does not exist, it will automatically be created.
*
* @param section The name of the section to add lines to, or null to add to the beginning unnamed section.
* @param lines The lines to add to the section.
* @return This object (for method chaining).
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract ConfigFile addLines(String section, String...lines);
/**
* Adds header comments to the specified section.
*
*
* Header comments are defined as lines that start with "#" immediately preceding a section header
* "[section]" .
* These are handled as part of the section itself instead of being interpreted as comments in the previous section.
*
*
* Header comments can be of the following formats...
*
* -
*
"# comment" - A comment line.
* -
*
"comment" - Anything else (will automatically be prefixed with "# " ).
*
*
*
* If the section does not exist, it will automatically be created.
*
* @param section The name of the section to add lines to, or null to add to the default section.
* @param headerComments The comment lines to add to the section.
* @return This object (for method chaining).
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract ConfigFile addHeaderComments(String section, String...headerComments);
/**
* Removes any header comments from the specified section.
*
* @param section The name of the section to remove header comments from.
* @return This object (for method chaining).
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract ConfigFile clearHeaderComments(String section);
/**
* Returns the reusable bean session associated with this config file.
*
*
* Used for performing simple datatype conversions.
*
* @return The reusable bean session associated with this config file.
*/
protected abstract BeanSession getBeanSession();
/**
* Converts the specified object to a string.
*
*
* The serialized output is identical to LAX JSON (JSON with unquoted attributes) except for the following
* exceptions:
*
* - Top level strings are not quoted.
*
*
* @param o The object to serialize.
* @param serializer
* The serializer to use for serializing the object.
* If null , then uses the predefined serializer on the config file.
* @param newline If true , add a newline at the beginning of the value.
* @return The serialized object.
* @throws SerializeException
*/
protected abstract String serialize(Object o, Serializer serializer, boolean newline) throws SerializeException;
/**
* Converts the specified string to an object of the specified type.
*
* @param s The string to parse.
* @param parser
* The parser to use for parsing the object.
* If null , then uses the predefined parser on the config file.
* @param type The data type to create.
* @param args The generic type arguments if the type is a {@link Collection} or {@link Map}
* @return The parsed object.
* @throws ParseException
*/
protected abstract T parse(String s, Parser parser, Type type, Type...args) throws ParseException;
/**
* Places a read lock on this config file.
*/
protected abstract void readLock();
/**
* Removes the read lock on this config file.
*/
protected abstract void readUnlock();
//--------------------------------------------------------------------------------
// API methods
//--------------------------------------------------------------------------------
/**
* Returns the specified value as a string from the config file.
*
* @param key The key. See {@link #getString(String)} for a description of the key.
* @param def The default value if the section or value does not exist.
* @return The value, or the default value if the section or value doesn't exist.
*/
public final String getString(String key, String def) {
assertFieldNotNull(key, "key");
String s = get(getSectionName(key), getSectionKey(key));
return (StringUtils.isEmpty(s) && def != null ? def : s);
}
/**
* Removes an entry with the specified key.
*
* @param key The key. See {@link #getString(String)} for a description of the key.
* @return The previous value, or null if the section or key did not previously exist.
* @throws UnsupportedOperationException If config file is read only.
*/
public final String removeString(String key) {
assertFieldNotNull(key, "key");
return remove(getSectionName(key), getSectionKey(key));
}
/**
* 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:
*
* ConfigFile cf = new ConfigFileBuilder().build("MyConfig.cfg" );
*
* // 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. See {@link #getString(String)} for a description of 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 final 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. See {@link #getString(String)} for a description of 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 final T getObject(String key, Parser parser, Type type, Type...args) throws ParseException {
assertFieldNotNull(key, "key");
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:
*
* ConfigFile cf = new ConfigFileBuilder().build("MyConfig.cfg" );
*
* // 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. See {@link #getString(String)} for a description of 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 final 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. See {@link #getString(String)} for a description of 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 final T getObject(String key, Parser parser, Class type) throws ParseException {
assertFieldNotNull(key, "key");
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. See {@link #getString(String)} for a description of the key.
* @param def The default value if section or key 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 final 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. See {@link #getString(String)} for a description of 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 section or key 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 final T getObjectWithDefault(String key, Parser parser, T def, Class type) throws ParseException {
assertFieldNotNull(key, "key");
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. See {@link #getString(String)} for a description of the key.
* @param def The default value if section or key 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 final 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. See {@link #getString(String)} for a description of 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 section or key 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 final T getObjectWithDefault(String key, Parser parser, T def, Type type, Type...args) throws ParseException {
assertFieldNotNull(key, "key");
assertFieldNotNull(type, "type");
T t = parse(getString(key), parser, type, args);
return (t == null ? def : t);
}
/**
* Gets the entry with the specified key and converts it to the specified value.
*
*
* Same as {@link #getObject(String, Class)}, but used when key is already broken into section/key.
*
* @param sectionName The section name. Must not be null .
* @param sectionKey The section key. Must not be null .
* @param c 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 the default value if the section or value doesn't exist.
*/
public final T getObject(String sectionName, String sectionKey, Class c) throws ParseException {
return getObject(sectionName, sectionKey, null, c);
}
/**
* Same as {@link #getObject(String, String, Class)} but allows you to specify the parser to use to parse the value.
*
* @param sectionName The section name. Must not be null .
* @param sectionKey The section key. Must not be null .
* @param parser
* The parser to use for parsing the object.
* If null , then uses the predefined parser on the config file.
* @param c 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 the default value if the section or value doesn't exist.
*/
public final T getObject(String sectionName, String sectionKey, Parser parser, Class c) throws ParseException {
assertFieldNotNull(sectionName, "sectionName");
assertFieldNotNull(sectionKey, "sectionKey");
return parse(get(sectionName, sectionKey), parser, c);
}
/**
* Gets the entry with the specified key and converts it to the specified value.
*
*
* Same as {@link #getObject(String, Type, Type...)}, but used when key is already broken into section/key.
*
* @param sectionName The section name. Must not be null .
* @param sectionKey The section key. Must not be null .
* @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 final T getObject(String sectionName, String sectionKey, Type type, Type...args) throws ParseException {
return getObject(sectionName, sectionKey, null, type, args);
}
/**
* Same as {@link #getObject(String, String, Type, Type...)} but allows you to specify the parser to use to parse
* the value.
*
* @param sectionName The section name. Must not be null .
* @param sectionKey The section key. Must not be null .
* @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 final T getObject(String sectionName, String sectionKey, Parser parser, Type type, Type...args)
throws ParseException {
assertFieldNotNull(sectionName, "sectionName");
assertFieldNotNull(sectionKey, "sectionKey");
return parse(get(sectionName, sectionKey), parser, type, args);
}
/**
* 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. See {@link #getString(String)} for a description of the key.
* @return The value, or null if the section or key does not exist.
*/
public final String getString(String key) {
return getString(key, null);
}
/**
* Gets the entry with the specified key, splits the value on commas, and returns the values as trimmed strings.
*
* @param key The key. See {@link #getString(String)} for a description of the key.
* @return The value, or an empty list if the section or key does not exist.
*/
public final 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. See {@link #getString(String)} for a description of the key.
* @param def The default value if section or key does not exist.
* @return The value, or an empty list if the section or key does not exist.
*/
public final String[] getStringArray(String key, String[] def) {
String s = getString(key);
if (s == null)
return def;
String[] r = StringUtils.isEmpty(s) ? new String[0] : split(s);
return r.length == 0 ? def : r;
}
/**
* Convenience method for getting int config values.
*
* @param key The key. See {@link #getString(String)} for a description of the key.
* @return The value, or 0
if the section or key does not exist or cannot be parsed as an integer.
*/
public final int getInt(String key) {
return getInt(key, 0);
}
/**
* Convenience method for getting int config values.
*
*
* "M" and "K" can be used to identify millions and thousands.
*
*
Example:
*
* -
*
"100K" => 1024000
* -
*
"100M" => 104857600
*
*
* @param key The key. See {@link #getString(String)} for a description of the key.
* @param def The default value if config file or value does not exist.
* @return The value, or the default value if the section or key does not exist or cannot be parsed as an integer.
*/
public final int getInt(String key, int def) {
String s = getString(key);
if (StringUtils.isEmpty(s))
return def;
return parseIntWithSuffix(s);
}
/**
* Convenience method for getting boolean config values.
*
* @param key The key. See {@link #getString(String)} for a description of the key.
* @return The value, or false if the section or key does not exist or cannot be parsed as a boolean.
*/
public final boolean getBoolean(String key) {
return getBoolean(key, false);
}
/**
* Convenience method for getting boolean config values.
*
* @param key The key. See {@link #getString(String)} for a description of the key.
* @param def The default value if config file or 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 final boolean getBoolean(String key, boolean def) {
String s = getString(key);
return StringUtils.isEmpty(s) ? def : Boolean.parseBoolean(s);
}
/**
* 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. See {@link #getString(String)} for a description of 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 config file is read only.
*/
public final String put(String key, Object value) throws SerializeException {
return put(key, value, null, isEncoded(key), false);
}
/**
* Same as {@link #put(String, Object)} but allows you to specify the serializer to use to serialize the value.
*
* @param key The key. See {@link #getString(String)} for a description of the key.
* @param value The new value POJO.
* @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 config file is read only.
*/
public final String put(String key, Object value, Serializer serializer) throws SerializeException {
return put(key, value, serializer, isEncoded(key), false);
}
/**
* Adds or replaces an entry with the specified key with the specified value.
*
*
* The format of the entry depends on the data type of the value.
*
* -
* Simple types (
String
, Number
, Boolean
, primitives)
* are serialized as plain strings.
* -
* Arrays and collections of simple types are serialized as comma-delimited lists of plain strings.
*
-
* Other types (e.g. beans) are serialized using the serializer registered with this config file.
*
- Arrays and collections of other types are serialized as comma-delimited lists of serialized strings of
* each entry.
*
*
* @param key The key. See {@link #getString(String)} for a description of the key.
* @param value The new value.
* @param encoded
* If true , value is encoded by the registered encoder when the config file is persisted to disk.
* @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 config file is read only.
*/
public final String put(String key, Object value, boolean encoded) throws SerializeException {
return put(key, value, null, encoded, false);
}
/**
* Same as {@link #put(String, Object, boolean)} but allows you to specify the serializer to use to serialize the
* value.
*
* @param key The key. See {@link #getString(String)} for a description of 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 encoded
* If true , value is encoded by the registered encoder when the config file is persisted to disk.
* @param newline If true , a newline is added to the beginning of the input.
* @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 config file is read only.
*/
public final String put(String key, Object value, Serializer serializer, boolean encoded, boolean newline)
throws SerializeException {
assertFieldNotNull(key, "key");
return put(getSectionName(key), getSectionKey(key), serialize(value, serializer, newline), encoded);
}
/**
* Returns the specified section as a map of key/value pairs.
*
* @param sectionName The section name to retrieve.
* @return A map of the section, or null if the section was not found.
*/
public final ObjectMap getSectionMap(String sectionName) {
readLock();
try {
Set keys = getSectionKeys(sectionName);
if (keys == null)
return null;
ObjectMap m = new ObjectMap();
for (String key : keys)
m.put(key, get(sectionName, key));
return m;
} finally {
readUnlock();
}
}
/**
* Copies the entries in a section to the specified bean by calling the public setters on that bean.
*
* @param sectionName The section name to write from.
* @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.
* @param permittedPropertyTypes
* If specified, only look for setters whose property types are those listed.
* If not specified, use all setters.
* @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
*/
public final ObjectMap writeProperties(String sectionName, Object bean, boolean ignoreUnknownProperties,
Class>...permittedPropertyTypes) throws ParseException, IllegalArgumentException, IllegalAccessException,
InvocationTargetException {
assertFieldNotNull(bean, "bean");
ObjectMap om = new ObjectMap();
readLock();
try {
Set keys = getSectionKeys(sectionName);
if (keys == null)
throw new IllegalArgumentException("Section not found");
keys = new LinkedHashSet(keys);
for (Method m : bean.getClass().getMethods()) {
int mod = m.getModifiers();
if (isPublic(mod) && (!isStatic(mod)) && m.getName().startsWith("set") && m.getParameterTypes().length == 1) {
Class> pt = m.getParameterTypes()[0];
if (permittedPropertyTypes == null || permittedPropertyTypes.length == 0 || contains(pt, permittedPropertyTypes)) {
String propName = Introspector.decapitalize(m.getName().substring(3));
Object value = getObject(sectionName, propName, pt);
if (value != null) {
m.invoke(bean, value);
om.put(propName, value);
keys.remove(propName);
}
}
}
}
if (! (ignoreUnknownProperties || keys.isEmpty()))
throw new ParseException("Invalid properties found in config file section ''{0}'': {1}", sectionName, keys);
return om;
} finally {
readUnlock();
}
}
/**
* Shortcut for calling getSectionAsBean(sectionName, c, false )
.
*
* @param sectionName The section name to write from.
* @param c The bean class to create.
* @return A new bean instance.
* @throws ParseException
*/
public final T getSectionAsBean(String sectionName, Classc) throws ParseException {
return getSectionAsBean(sectionName, 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
*
* ConfigFile cf = new ConfigFileBuilder().build("MyConfig.cfg" );
* Address myAddress = cf.getSectionAsBean("MySection" , Address.class );
*
*
* @param sectionName The section name to write from.
* @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.
* @throws ParseException
*/
public final T getSectionAsBean(String sectionName, Class c, boolean ignoreUnknownProperties)
throws ParseException {
assertFieldNotNull(c, "c");
readLock();
try {
BeanMap bm = getBeanSession().newBeanMap(c);
for (String k : getSectionKeys(sectionName)) {
BeanPropertyMeta bpm = bm.getPropertyMeta(k);
if (bpm == null) {
if (! ignoreUnknownProperties)
throw new ParseException("Unknown property {0} encountered", k);
} else {
bm.put(k, getObject(sectionName + '/' + k, bpm.getClassMeta().getInnerClass()));
}
}
return bm.getBean();
} finally {
readUnlock();
}
}
/**
* 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
*
* ConfigFile cf = new ConfigFileBuilder().build("MyConfig.cfg" );
*
* MyConfigInterface ci = cf.getSectionAsInterface("MySection" , MyConfigInterface.class );
*
* int myInt = ci.getInt();
*
* ci.setBean(new MyBean());
*
* cf.save();
*
*
* @param sectionName The section name to retrieve as an interface proxy.
* @param c The proxy interface class.
* @return The proxy interface.
*/
@SuppressWarnings("unchecked")
public final T getSectionAsInterface(final String sectionName, final Class c) {
assertFieldNotNull(c, "c");
if (! c.isInterface())
throw new UnsupportedOperationException("Class 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 ConfigFile.this.getObject(sectionName, pd.getName(), rm.getGenericReturnType());
if (method.equals(wm))
return ConfigFile.this.put(sectionName, pd.getName(), args[0], null, false, false);
}
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. See {@link #getString(String)} for a description of the key.
* @return true if this section contains the specified key and the key has a non-blank value.
*/
public final boolean containsNonEmptyValue(String key) {
return ! StringUtils.isEmpty(getString(key, null));
}
/**
* Gets the section with the specified name.
*
* @param name The section name.
* @return The section, or null if section does not exist.
*/
protected abstract Section getSection(String name);
/**
* Gets the section with the specified name and optionally creates it if it's not there.
*
* @param name The section name.
* @param create Create the section if it's not there.
* @return The section, or null if section does not exist.
* @throws UnsupportedOperationException
* If config file is read only and section doesn't exist and create
is true .
*/
protected abstract Section getSection(String name, boolean create);
/**
* Appends a section to this config file if it does not already exist.
*
*
* Returns the existing section if it already exists.
*
* @param name The section name, or null for the default section.
* @return The appended or existing section.
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract ConfigFile addSection(String name);
/**
* Creates or overwrites the specified section.
*
* @param name The section name, or null for the default section.
* @param contents The contents of the new section.
* @return The appended or existing section.
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract ConfigFile setSection(String name, Map contents);
/**
* Removes the section with the specified name.
*
* @param name The name of the section to remove, or null for the default section.
* @return The removed section, or null if named section does not exist.
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract ConfigFile removeSection(String name);
/**
* Returns true if the encoding flag is set on the specified entry.
*
* @param key The key. See {@link #getString(String)} for a description of the key.
* @return true if the encoding flag is set on the specified entry.
*/
public abstract boolean isEncoded(String key);
/**
* Saves this config file to disk.
*
* @return This object (for method chaining).
* @throws IOException If a problem occurred trying to save file to disk, or file is not associated with this object.
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract ConfigFile save() throws IOException;
/**
* Saves this config file to the specified writer as an INI file.
*
*
* The writer will automatically be closed.
*
* @param out 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.
*/
public final ConfigFile serializeTo(Writer out) throws IOException {
return serializeTo(out, INI);
}
/**
* Same as {@link #serializeTo(Writer)}, except allows you to explicitly specify a format.
*
* @param out The writer to send the output to.
* @param format The {@link ConfigFileFormat} of the output.
* @return This object (for method chaining).
* @throws IOException If a problem occurred trying to send contents to the writer.
*/
public abstract ConfigFile serializeTo(Writer out, ConfigFileFormat format) throws IOException;
/**
* Add a listener to this config file to react to modification events.
*
* @param listener The new listener to add.
* @return This object (for method chaining).
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract ConfigFile addListener(ConfigFileListener listener);
/**
* Merges the contents of the specified config file into this config file.
*
*
* Pretty much identical to just replacing this config file, but causes the
* {@link ConfigFileListener#onChange(ConfigFile, Set)} method to be invoked on differences between the file.
*
* @param cf The config file whose values should be copied into this config file.
* @return This object (for method chaining).
* @throws UnsupportedOperationException If config file is read only.
*/
public abstract ConfigFile merge(ConfigFile cf);
/**
* Returns the config file contents as a string.
*
*
* The contents of the string are the same as the contents that would be serialized to disk.
*/
@Override /* Object */
public abstract String toString();
/**
* Returns a wrapped instance of this config file where calls to getters have their values first resolved by the
* specified {@link VarResolver}.
*
* @param vr The {@link VarResolver} for resolving variables in values.
* @return This config file wrapped in an instance of {@link ConfigFileWrapped}.
*/
public abstract ConfigFile getResolving(VarResolver vr);
/**
* Returns a wrapped instance of this config file where calls to getters have their values first resolved by the
* specified {@link VarResolverSession}.
*
* @param vs The {@link VarResolverSession} for resolving variables in values.
* @return This config file wrapped in an instance of {@link ConfigFileWrapped}.
*/
public abstract ConfigFile getResolving(VarResolverSession vs);
/**
* Returns a wrapped instance of this config file where calls to getters have their values first resolved by a
* default {@link VarResolver}.
*
* The default {@link VarResolver} is registered with the following {@link Var StringVars}:
*
* -
*
$S{key}
,$S{key,default}
- System properties.
* -
*
$E{key}
,$E{key,default}
- Environment variables.
* -
*
$C{key}
,$C{key,default}
- Values in this configuration file.
*
*
* @return A new config file that resolves string variables.
*/
public abstract ConfigFile getResolving();
/**
* Wraps this config file in a {@link Writable} interface that renders it as plain text.
*
* @return This config file wrapped in a {@link Writable}.
*/
public abstract Writable toWritable();
/**
* @return The string var resolver associated with this config file.
*/
protected VarResolver getVarResolver() {
// Only ConfigFileWrapped returns a value.
return null;
}
private static int parseIntWithSuffix(String s) {
assertFieldNotNull(s, "s");
int m = 1;
if (s.endsWith("M")) {
m = 1024*1024;
s = s.substring(0, s.length()-1).trim();
} else if (s.endsWith("K")) {
m = 1024;
s = s.substring(0, s.length()-1).trim();
}
return Integer.parseInt(s) * m;
}
}