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

org.apache.juneau.ini.ConfigFile 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.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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy