| // *************************************************************************************************************************** |
| // * 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.annotation.*; |
| 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. |
| * |
| * <ul class='seealso'> |
| * <li class='link'>{@doc juneau-config} |
| * <li class='extlink'>{@source} |
| * </ul> |
| */ |
| @ConfigurableContext |
| public final class Config extends Context implements ConfigEventListener, Writable { |
| |
| private static boolean DISABLE_AUTO_SYSTEM_PROPS = Boolean.getBoolean("juneau.disableAutoSystemProps"); |
| private static volatile Config SYSTEM_DEFAULT = findSystemDefault(); |
| |
| /** |
| * Sets a system default configuration. |
| * |
| * @param systemDefault The new system default configuration. |
| */ |
| public synchronized static void setSystemDefault(Config systemDefault) { |
| SYSTEM_DEFAULT = systemDefault; |
| } |
| |
| /** |
| * Returns the system default configuration. |
| * |
| * @return The system default configuration, or <jk>null</jk> if it doesn't exist. |
| */ |
| public synchronized static Config getSystemDefault() { |
| return SYSTEM_DEFAULT; |
| } |
| |
| private synchronized static Config findSystemDefault() { |
| |
| for (String n : getCandidateSystemDefaultConfigNames()) { |
| Config config = find(n); |
| if (config != null) { |
| if (! DISABLE_AUTO_SYSTEM_PROPS) |
| config.setSystemProperties(); |
| return config; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns the list of candidate system default configuration file names. |
| * |
| * <p> |
| * If the <js>"juneau.configFile"</js> system property is set, returns a singleton of that value. |
| * <br>Otherwise, returns a list consisting of the following values: |
| * <ol> |
| * <li>File with same name as jar file but with <js>".cfg"</js> extension. (e.g. <js>"myjar.cfg"</js>) |
| * <li>Any file ending in <js>".cfg"</js> in the home directory (names ordered alphabetically). |
| * <li><js>"juneau.cfg"</js> |
| * <li><js>"default.cfg"</js> |
| * <li><js>"application.cfg"</js> |
| * <li><js>"app.cfg"</js> |
| * <li><js>"settings.cfg"</js> |
| * <li><js>"application.properties"</js> |
| * </ol> |
| * <p> |
| * |
| * @return |
| * A list of candidate file names. |
| * <br>The returned list is modifiable. |
| * <br>Each call constructs a new list. |
| */ |
| public synchronized static List<String> getCandidateSystemDefaultConfigNames() { |
| List<String> l = new ArrayList<>(); |
| |
| String s = System.getProperty("juneau.configFile"); |
| if (s != null) { |
| l.add(s); |
| return l; |
| } |
| |
| String cmd = System.getProperty("sun.java.command", "not_found").split("\\s+")[0]; |
| if (cmd.endsWith(".jar") && ! cmd.contains("surefirebooter")) { |
| cmd = cmd.replaceAll(".*?([^\\\\\\/]+)\\.jar$", "$1"); |
| l.add(cmd + ".cfg"); |
| cmd = cmd.replaceAll("[\\.\\_].*$", ""); // Try also without version in jar name. |
| l.add(cmd + ".cfg"); |
| } |
| |
| Set<File> files = new TreeSet<>(Arrays.asList(new File(".").listFiles())); |
| for (File f : files) |
| if (f.getName().endsWith(".cfg")) |
| l.add(f.getName()); |
| |
| l.add("juneau.cfg"); |
| l.add("default.cfg"); |
| l.add("application.cfg"); |
| l.add("app.cfg"); |
| l.add("settings.cfg"); |
| l.add("application.properties"); |
| |
| return l; |
| } |
| |
| private synchronized static Config find(String name) { |
| if (name == null) |
| return null; |
| if (ConfigFileStore.DEFAULT.exists(name)) |
| return Config.create(name).store(ConfigFileStore.DEFAULT).build(); |
| if (ConfigClasspathStore.DEFAULT.exists(name)) |
| return Config.create(name).store(ConfigClasspathStore.DEFAULT).build(); |
| return null; |
| } |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // Configurable properties |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| static final String PREFIX = "Config"; |
| |
| /** |
| * Configuration property: Configuration name. |
| * |
| * <h5 class='section'>Property:</h5> |
| * <ul> |
| * <li><b>Name:</b> <js>"Config.name.s"</js> |
| * <li><b>Data type:</b> <c>String</c> |
| * <li><b>Default:</b> <js>"Configuration.cfg"</js> |
| * <li><b>Methods:</b> |
| * <ul> |
| * <li class='jm'>{@link ConfigBuilder#name(String)} |
| * </ul> |
| * </ul> |
| * |
| * <h5 class='section'>Description:</h5> |
| * <p> |
| * Specifies the configuration name. |
| * <br>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. |
| * |
| * <h5 class='section'>Property:</h5> |
| * <ul> |
| * <li><b>Name:</b> <js>"Config.store.o"</js> |
| * <li><b>Data type:</b> {@link ConfigStore} |
| * <li><b>Default:</b> {@link ConfigFileStore#DEFAULT} |
| * <li><b>Methods:</b> |
| * <ul> |
| * <li class='jm'>{@link ConfigBuilder#store(ConfigStore)} |
| * </ul> |
| * </ul> |
| * |
| * <h5 class='section'>Description:</h5> |
| * <p> |
| * The configuration store used for retrieving and storing configurations. |
| */ |
| public static final String CONFIG_store = PREFIX + ".store.o"; |
| |
| /** |
| * Configuration property: POJO serializer. |
| * |
| * <h5 class='section'>Property:</h5> |
| * <ul> |
| * <li><b>Name:</b> <js>"Config.serializer.o"</js> |
| * <li><b>Data type:</b> {@link WriterSerializer} |
| * <li><b>Default:</b> {@link SimpleJsonSerializer#DEFAULT} |
| * <li><b>Methods:</b> |
| * <ul> |
| * <li class='jm'>{@link ConfigBuilder#serializer(Class)} |
| * <li class='jm'>{@link ConfigBuilder#serializer(WriterSerializer)} |
| * </ul> |
| * </ul> |
| * |
| * <h5 class='section'>Description:</h5> |
| * <p> |
| * The serializer to use for serializing POJO values. |
| */ |
| public static final String CONFIG_serializer = PREFIX + ".serializer.o"; |
| |
| /** |
| * Configuration property: POJO parser. |
| * |
| * <h5 class='section'>Property:</h5> |
| * <ul> |
| * <li><b>Name:</b> <js>"Config.parser.o"</js> |
| * <li><b>Data type:</b> {@link ReaderParser} |
| * <li><b>Default:</b> {@link JsonParser#DEFAULT} |
| * <li><b>Methods:</b> |
| * <ul> |
| * <li class='jm'>{@link ConfigBuilder#parser(Class)} |
| * <li class='jm'>{@link ConfigBuilder#parser(ReaderParser)} |
| * </ul> |
| * </ul> |
| * |
| * <h5 class='section'>Description:</h5> |
| * <p> |
| * The parser to use for parsing values to POJOs. |
| */ |
| public static final String CONFIG_parser = PREFIX + ".parser.o"; |
| |
| /** |
| * Configuration property: Value encoder. |
| * |
| * <h5 class='section'>Property:</h5> |
| * <ul> |
| * <li><b>Name:</b> <js>"Config.encoder.o"</js> |
| * <li><b>Data type:</b> {@link ConfigEncoder} |
| * <li><b>Default:</b> {@link ConfigXorEncoder#INSTANCE} |
| * <li><b>Methods:</b> |
| * <ul> |
| * <li class='jm'>{@link ConfigBuilder#encoder(Class)} |
| * <li class='jm'>{@link ConfigBuilder#encoder(ConfigEncoder)} |
| * </ul> |
| * </ul> |
| * |
| * <h5 class='section'>Description:</h5> |
| * <p> |
| * The encoder to use for encoding encoded configuration values. |
| */ |
| public static final String CONFIG_encoder = PREFIX + ".encoder.o"; |
| |
| /** |
| * Configuration property: SVL variable resolver. |
| * |
| * <h5 class='section'>Property:</h5> |
| * <ul> |
| * <li><b>Name:</b> <js>"Config.varResolver.o"</js> |
| * <li><b>Data type:</b> {@link VarResolver} |
| * <li><b>Default:</b> {@link VarResolver#DEFAULT} |
| * <li><b>Methods:</b> |
| * <ul> |
| * <li class='jm'>{@link ConfigBuilder#varResolver(Class)} |
| * <li class='jm'>{@link ConfigBuilder#varResolver(VarResolver)} |
| * </ul> |
| * </ul> |
| * |
| * <h5 class='section'>Description:</h5> |
| * <p> |
| * The resolver to use for resolving SVL variables. |
| */ |
| public static final String CONFIG_varResolver = PREFIX + ".varResolver.o"; |
| |
| /** |
| * Configuration property: Binary value line length. |
| * |
| * <h5 class='section'>Property:</h5> |
| * <ul> |
| * <li><b>Name:</b> <js>"Config.binaryLineLength.i"</js> |
| * <li><b>Data type:</b> <c>Integer</c> |
| * <li><b>Default:</b> <c>-1</c> |
| * <li><b>Methods:</b> |
| * <ul> |
| * <li class='jm'>{@link ConfigBuilder#binaryLineLength(int)} |
| * </ul> |
| * </ul> |
| * |
| * <h5 class='section'>Description:</h5> |
| * <p> |
| * When serializing binary values, lines will be split after this many characters. |
| * <br>Use <c>-1</c> to represent no line splitting. |
| */ |
| public static final String CONFIG_binaryLineLength = PREFIX + ".binaryLineLength.i"; |
| |
| /** |
| * Configuration property: Binary value format. |
| * |
| * <h5 class='section'>Property:</h5> |
| * <ul> |
| * <li><b>Name:</b> <js>"Config.binaryFormat.s"</js> |
| * <li><b>Data type:</b> {@link BinaryFormat} |
| * <li><b>Default:</b> {@link BinaryFormat#BASE64} |
| * <li><b>Methods:</b> |
| * <ul> |
| * <li class='jm'>{@link ConfigBuilder#binaryFormat(BinaryFormat)} |
| * </ul> |
| * </ul> |
| * |
| * <h5 class='section'>Description:</h5> |
| * <p> |
| * The format to use when persisting byte arrays. |
| * |
| * <p> |
| * Possible values: |
| * <ul> |
| * <li>{@link BinaryFormat#BASE64} - BASE64-encoded string. |
| * <li>{@link BinaryFormat#HEX} - Hexadecimal. |
| * <li>{@link BinaryFormat#SPACED_HEX} - Hexadecimal with spaces between bytes. |
| * </ul> |
| */ |
| public static final String CONFIG_binaryFormat = PREFIX + ".binaryFormat.s"; |
| |
| /** |
| * Configuration property: Multi-line values should always be on separate lines. |
| * |
| * <h5 class='section'>Property:</h5> |
| * <ul> |
| * <li><b>Name:</b> <js>"Config.multiLineValuesOnSeparateLines.b"</js> |
| * <li><b>Data type:</b> <c>Boolean</c> |
| * <li><b>Default:</b> <jk>false</jk> |
| * <li><b>Methods:</b> |
| * <ul> |
| * <li class='jm'>{@link ConfigBuilder#multiLineValuesOnSeparateLines()} |
| * </ul> |
| * </ul> |
| * |
| * <h5 class='section'>Description:</h5> |
| * <p> |
| * 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. |
| * |
| * <h5 class='section'>Property:</h5> |
| * <ul> |
| * <li><b>Name:</b> <js>"Config.readOnly.b"</js> |
| * <li><b>Data type:</b> <c>Boolean</c> |
| * <li><b>Default:</b> <jk>false</jk> |
| * <li><b>Methods:</b> |
| * <ul> |
| * <li class='jm'>{@link ConfigBuilder#readOnly()} |
| * </ul> |
| * </ul> |
| * |
| * <h5 class='section'>Description:</h5> |
| * <p> |
| * 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<ConfigEventListener> listeners = Collections.synchronizedList(new LinkedList<ConfigEventListener>()); |
| |
| |
| /** |
| * Instantiates a new clean-slate {@link ConfigBuilder} object. |
| * |
| * <p> |
| * This is equivalent to simply calling <code><jk>new</jk> ConfigBuilder()</code>. |
| * |
| * @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. |
| * |
| * <p> |
| * This is equivalent to simply calling <code><jk>new</jk> ConfigBuilder().name(name)</code>. |
| * |
| * @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 Thrown by underlying stream. |
| */ |
| public Config(PropertyStore ps) throws IOException { |
| super(ps, true); |
| |
| 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, true); |
| 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. |
| * |
| * <p> |
| * 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); |
| } |
| |
| /** |
| * Returns the name associated with this config (usually a file name). |
| * |
| * @return The name associated with this config, or <jk>null</jk> if it has no name. |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| //----------------------------------------------------------------------------------------------------------------- |
| // Workhorse getters |
| //----------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Returns the specified value as a string from the config file. |
| * |
| * <p> |
| * Unlike {@link #getString(String)}, this method doesn't replace SVL variables. |
| * |
| * @param key The key. |
| * @return The value, or <jk>null</jk> 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; |
| } |
| |
| //----------------------------------------------------------------------------------------------------------------- |
| // Utility methods |
| //----------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Takes the settings defined in this configuration and sets them as system properties. |
| * |
| * @return This object (for method chaining). |
| */ |
| public Config setSystemProperties() { |
| for (String section : getSections()) { |
| for (String key : getKeys(section)) { |
| String k = (section.isEmpty() ? key : section + '/' + key); |
| System.setProperty(k, get(k)); |
| } |
| } |
| return this; |
| } |
| |
| //----------------------------------------------------------------------------------------------------------------- |
| // 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 = stringify(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. |
| * |
| * <p> |
| * Equivalent to calling <c>put(key, value, isEncoded(key))</c>. |
| * |
| * @param key The key. |
| * @param value The new value POJO. |
| * @return The previous value, or <jk>null</jk> 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 <jk>null</jk>, then uses the predefined serializer on the config file. |
| * @return The previous value, or <jk>null</jk> 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 <jk>null</jk>, then uses the predefined serializer on the config file. |
| * @param modifier |
| * Optional modifier to apply to the value. |
| * <br>If <jk>null</jk>, then previous value will not be replaced. |
| * @param comment |
| * Optional same-line comment to add to this value. |
| * <br>If <jk>null</jk>, then previous value will not be replaced. |
| * @param preLines |
| * Optional comment or blank lines to add before this entry. |
| * <br>If <jk>null</jk>, then previous value will not be replaced. |
| * @return The previous value, or <jk>null</jk> 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<String> 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 <jk>null</jk>, then uses the predefined serializer on the config file. |
| * @param modifiers |
| * Optional modifiers to apply to the value. |
| * <br>If <jk>null</jk>, then previous value will not be replaced. |
| * @param comment |
| * Optional same-line comment to add to this value. |
| * <br>If <jk>null</jk>, then previous value will not be replaced. |
| * @param preLines |
| * Optional comment or blank lines to add before this entry. |
| * <br>If <jk>null</jk>, then previous value will not be replaced. |
| * @return The previous value, or <jk>null</jk> 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<String> 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 <jk>null</jk> 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. |
| * |
| * <p> |
| * 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. |
| * |
| * <p> |
| * The key can be in one of the following formats... |
| * <ul class='spaced-list'> |
| * <li> |
| * <js>"key"</js> - A value in the default section (i.e. defined above any <c>[section]</c> header). |
| * <li> |
| * <js>"section/key"</js> - A value from the specified section. |
| * </ul> |
| * |
| * @param key The key. |
| * @return The value, or <jk>null</jk> 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. |
| * |
| * <p> |
| * The key can be in one of the following formats... |
| * <ul class='spaced-list'> |
| * <li> |
| * <js>"key"</js> - A value in the default section (i.e. defined above any <c>[section]</c> header). |
| * <li> |
| * <js>"section/key"</js> - A value from the specified section. |
| * </ul> |
| * |
| * @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. |
| * |
| * <p> |
| * <js>"K"</js>, <js>"M"</js>, and <js>"G"</js> can be used to identify kilo, mega, and giga. |
| * |
| * <h5 class='section'>Example:</h5> |
| * <ul class='spaced-list'> |
| * <li> |
| * <code><js>"100K"</js> => 1024000</code> |
| * <li> |
| * <code><js>"100M"</js> => 104857600</code> |
| * </ul> |
| * |
| * <p> |
| * Uses {@link Integer#decode(String)} underneath, so any of the following integer formats are supported: |
| * <ul> |
| * <li><js>"0x..."</js> |
| * <li><js>"0X..."</js> |
| * <li><js>"#..."</js> |
| * <li><js>"0..."</js> |
| * </ul> |
| * |
| * @param key The key. |
| * @return The value, or <c>0</c> 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 <jk>false</jk> 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. |
| * |
| * <p> |
| * <js>"K"</js>, <js>"M"</js>, and <js>"G"</js> can be used to identify kilo, mega, and giga. |
| * |
| * <h5 class='section'>Example:</h5> |
| * <ul class='spaced-list'> |
| * <li> |
| * <code><js>"100K"</js> => 1024000</code> |
| * <li> |
| * <code><js>"100M"</js> => 104857600</code> |
| * </ul> |
| * |
| * <p> |
| * Uses {@link Long#decode(String)} underneath, so any of the following number formats are supported: |
| * <ul> |
| * <li><js>"0x..."</js> |
| * <li><js>"0X..."</js> |
| * <li><js>"#..."</js> |
| * <li><js>"0..."</js> |
| * </ul> |
| * |
| * @param key The key. |
| * @return The value, or <c>0</c> 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. |
| * |
| * <p> |
| * Uses {@link Double#valueOf(String)} underneath, so any of the following number formats are supported: |
| * <ul> |
| * <li><js>"0x..."</js> |
| * <li><js>"0X..."</js> |
| * <li><js>"#..."</js> |
| * <li><js>"0..."</js> |
| * </ul> |
| * |
| * @param key The key. |
| * @return The value, or <c>0</c> 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. |
| * |
| * <p> |
| * Uses {@link Float#valueOf(String)} underneath, so any of the following number formats are supported: |
| * <ul> |
| * <li><js>"0x..."</js> |
| * <li><js>"0X..."</js> |
| * <li><js>"#..."</js> |
| * <li><js>"0..."</js> |
| * </ul> |
| * |
| * @param key The key. |
| * @return The value, or <c>0</c> 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. |
| * |
| * <p> |
| * This is equivalent to calling the following: |
| * <p class='bcode w800'> |
| * <jk>byte</jk>[] b = config.getObject(key, <jk>byte</jk>[].<jk>class</jk>); |
| * </p> |
| * |
| * 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 <jk>null</jk> 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. |
| * |
| * <p> |
| * The key can be in one of the following formats... |
| * <ul class='spaced-list'> |
| * <li> |
| * <js>"key"</js> - A value in the default section (i.e. defined above any <c>[section]</c> header). |
| * <li> |
| * <js>"section/key"</js> - A value from the specified section. |
| * </ul> |
| * |
| * <p> |
| * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). |
| * |
| * <h5 class='section'>Examples:</h5> |
| * <p class='bcode w800'> |
| * Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build(); |
| * |
| * <jc>// Parse into a linked-list of strings.</jc> |
| * List l = cf.getObject(<js>"MySection/myListOfStrings"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); |
| * |
| * <jc>// Parse into a linked-list of beans.</jc> |
| * List l = cf.getObject(<js>"MySection/myListOfBeans"</js>, LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); |
| * |
| * <jc>// Parse into a linked-list of linked-lists of strings.</jc> |
| * List l = cf.getObject(<js>"MySection/my2dListOfStrings"</js>, LinkedList.<jk>class</jk>, |
| * LinkedList.<jk>class</jk>, String.<jk>class</jk>); |
| * |
| * <jc>// Parse into a map of string keys/values.</jc> |
| * Map m = cf.getObject(<js>"MySection/myMap"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, |
| * String.<jk>class</jk>); |
| * |
| * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> |
| * Map m = cf.getObject(<js>"MySection/myMapOfListsOfBeans"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, |
| * List.<jk>class</jk>, MyBean.<jk>class</jk>); |
| * </p> |
| * |
| * <p> |
| * <c>Collection</c> classes are assumed to be followed by zero or one objects indicating the element type. |
| * |
| * <p> |
| * <c>Map</c> classes are assumed to be followed by zero or two meta objects indicating the key and value |
| * types. |
| * |
| * <p> |
| * The array can be arbitrarily long to indicate arbitrarily complex data structures. |
| * |
| * <ul class='notes'> |
| * <li> |
| * Use the {@link #getObject(String, Class)} method instead if you don't need a parameterized map/collection. |
| * </ul> |
| * |
| * @param key The key. |
| * @param type |
| * The object type to create. |
| * <br>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. |
| * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} |
| * <br>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 <jk>null</jk> if the section or key does not exist. |
| */ |
| public <T> 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 <jk>null</jk>, then uses the predefined parser on the config file. |
| * @param type |
| * The object type to create. |
| * <br>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. |
| * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} |
| * <br>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 <jk>null</jk> if the section or key does not exist. |
| */ |
| public <T> 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. |
| * |
| * <p> |
| * This is the preferred parse method for simple types since you don't need to cast the results. |
| * |
| * <h5 class='section'>Examples:</h5> |
| * <p class='bcode w800'> |
| * Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build(); |
| * |
| * <jc>// Parse into a string.</jc> |
| * String s = cf.getObject(<js>"MySection/mySimpleString"</js>, String.<jk>class</jk>); |
| * |
| * <jc>// Parse into a bean.</jc> |
| * MyBean b = cf.getObject(<js>"MySection/myBean"</js>, MyBean.<jk>class</jk>); |
| * |
| * <jc>// Parse into a bean array.</jc> |
| * MyBean[] b = cf.getObject(<js>"MySection/myBeanArray"</js>, MyBean[].<jk>class</jk>); |
| * |
| * <jc>// Parse into a linked-list of objects.</jc> |
| * List l = cf.getObject(<js>"MySection/myList"</js>, LinkedList.<jk>class</jk>); |
| * |
| * <jc>// Parse into a map of object keys/values.</jc> |
| * Map m = cf.getObject(<js>"MySection/myMap"</js>, TreeMap.<jk>class</jk>); |
| * </p> |
| * |
| * @param <T> 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 Malformed input encountered. |
| * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections. |
| */ |
| public <T> T getObject(String key, Class<T> 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 <T> The class type of the object being created. |
| * @param key The key. |
| * @param parser |
| * The parser to use for parsing the object. |
| * If <jk>null</jk>, then uses the predefined parser on the config file. |
| * @param type The object type to create. |
| * @return The parsed object. |
| * @throws ParseException Malformed input encountered. |
| * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections. |
| */ |
| public <T> T getObject(String key, Parser parser, Class<T> 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. |
| * |
| * <p> |
| * 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 <jk>null</jk> if the section or key does not exist. |
| */ |
| public <T> T getObjectWithDefault(String key, T def, Class<T> 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 <jk>null</jk>, 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 <jk>null</jk> if the section or key does not exist. |
| */ |
| public <T> T getObjectWithDefault(String key, Parser parser, T def, Class<T> 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. |
| * |
| * <p> |
| * 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. |
| * <br>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. |
| * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} |
| * <br>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 <jk>null</jk> if the section or key does not exist. |
| */ |
| public <T> 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 <jk>null</jk>, 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. |
| * <br>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. |
| * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} |
| * <br>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 <jk>null</jk> if the section or key does not exist. |
| */ |
| public <T> 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); |
| } |
| |
| /** |
| * Convenience method for returning a config entry as an {@link ObjectMap}. |
| * |
| * @param key The key. |
| * @return The value, or <jk>null</jk> if the section or key does not exist. |
| * @throws ParseException Malformed input encountered. |
| */ |
| public ObjectMap getObjectMap(String key) throws ParseException { |
| return getObject(key, ObjectMap.class); |
| } |
| |
| /** |
| * Convenience method for returning a config entry as an {@link ObjectMap}. |
| * |
| * @param key The key. |
| * @param def The default value. |
| * @return The value, or the default value if the section or key does not exist. |
| * @throws ParseException Malformed input encountered. |
| */ |
| public ObjectMap getObjectMap(String key, ObjectMap def) throws ParseException { |
| return getObjectWithDefault(key, def, ObjectMap.class); |
| } |
| |
| /** |
| * Convenience method for returning a config entry as an {@link ObjectList}. |
| * |
| * @param key The key. |
| * @return The value, or <jk>null</jk> if the section or key does not exist. |
| * @throws ParseException Malformed input encountered. |
| */ |
| public ObjectList getObjectList(String key) throws ParseException { |
| return getObject(key, ObjectList.class); |
| } |
| |
| /** |
| * Convenience method for returning a config entry as an {@link ObjectList}. |
| * |
| * @param key The key. |
| * @param def The default value. |
| * @return The value, or the default value if the section or key does not exist. |
| * @throws ParseException Malformed input encountered. |
| */ |
| public ObjectList getObjectList(String key, ObjectList def) throws ParseException { |
| return getObjectWithDefault(key, def, ObjectList.class); |
| } |
| |
| /** |
| * Returns the keys of the entries in the specified section. |
| * |
| * @param section |
| * The section name to write from. |
| * <br>If empty, refers to the default section. |
| * <br>Must not be <jk>null</jk>. |
| * @return |
| * An unmodifiable set of keys, or an empty set if the section doesn't exist. |
| */ |
| public Set<String> 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. |
| * <br>If empty, refers to the default section. |
| * <br>Must not be <jk>null</jk>. |
| * @param bean The bean to set the properties on. |
| * @param ignoreUnknownProperties |
| * If <jk>true</jk>, 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 UnsupportedOperationException If configuration is read only. |
| */ |
| public Config writeProperties(String section, Object bean, boolean ignoreUnknownProperties) throws ParseException { |
| checkWrite(); |
| assertFieldNotNull(bean, "bean"); |
| section = section(section); |
| |
| Set<String> 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 <code>getSectionAsBean(sectionName, c, <jk>false</jk>)</code>. |
| * |
| * @param section |
| * The section name to write from. |
| * <br>If empty, refers to the default section. |
| * <br>Must not be <jk>null</jk>. |
| * @param c The bean class to create. |
| * @return A new bean instance. |
| * @throws ParseException Malformed input encountered. |
| */ |
| public <T> T getSectionAsBean(String section, Class<T> c) throws ParseException { |
| return getSectionAsBean(section, c, false); |
| } |
| |
| /** |
| * Converts this config file section to the specified bean instance. |
| * |
| * <p> |
| * Key/value pairs in the config file section get copied as bean property values to the specified bean class. |
| * |
| * <h5 class='figure'>Example config file</h5> |
| * <p class='bcode w800'> |
| * <cs>[MyAddress]</cs> |
| * <ck>name</ck> = <cv>John Smith</cv> |
| * <ck>street</ck> = <cv>123 Main Street</cv> |
| * <ck>city</ck> = <cv>Anywhere</cv> |
| * <ck>state</ck> = <cv>NY</cv> |
| * <ck>zip</ck> = <cv>12345</cv> |
| * </p> |
| * |
| * <h5 class='figure'>Example bean</h5> |
| * <p class='bcode w800'> |
| * <jk>public class</jk> Address { |
| * public String name, street, city; |
| * public StateEnum state; |
| * public int zip; |
| * } |
| * </p> |
| * |
| * <h5 class='figure'>Example usage</h5> |
| * <p class='bcode w800'> |
| * Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build(); |
| * Address myAddress = cf.getSectionAsBean(<js>"MySection"</js>, Address.<jk>class</jk>); |
| * </p> |
| * |
| * @param section |
| * The section name to write from. |
| * <br>If empty, refers to the default section. |
| * <br>Must not be <jk>null</jk>. |
| * @param c The bean class to create. |
| * @param ignoreUnknownProperties |
| * If <jk>false</jk>, throws a {@link ParseException} if the section contains an entry that isn't a bean property |
| * name. |
| * @return A new bean instance, or <jk>null</jk> if the section doesn't exist. |
| * @throws ParseException Unknown property was encountered in section. |
| */ |
| public <T> T getSectionAsBean(String section, Class<T> c, boolean ignoreUnknownProperties) throws ParseException { |
| assertFieldNotNull(c, "c"); |
| section = section(section); |
| |
| if (! configMap.hasSection(section)) |
| return null; |
| |
| Set<String> keys = configMap.getKeys(section); |
| |
| BeanMap<T> 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. |
| * <br>If empty, refers to the default section. |
| * <br>Must not be <jk>null</jk>. |
| * @return A new {@link ObjectMap}, or <jk>null</jk> if the section doesn't exist. |
| * @throws ParseException Malformed input encountered. |
| */ |
| public ObjectMap getSectionAsMap(String section) throws ParseException { |
| section = section(section); |
| |
| if (! configMap.hasSection(section)) |
| return null; |
| |
| Set<String> keys = configMap.getKeys(section); |
| |
| ObjectMap om = new ObjectMap(); |
| for (String k : keys) |
| om.put(k, getObject(section + '/' + k, Object.class)); |
| return om; |
| } |
| |
| /** |
| * Returns the section names defined in this config. |
| * |
| * @return The section names defined in this config. |
| */ |
| public Set<String> getSections() { |
| return Collections.unmodifiableSet(configMap.getSections()); |
| } |
| |
| /** |
| * Wraps a config file section inside a Java interface so that values in the section can be read and |
| * write using getters and setters. |
| * |
| * <h5 class='figure'>Example config file</h5> |
| * <p class='bcode w800'> |
| * <cs>[MySection]</cs> |
| * <ck>string</ck> = <cv>foo</cv> |
| * <ck>int</ck> = <cv>123</cv> |
| * <ck>enum</ck> = <cv>ONE</cv> |
| * <ck>bean</ck> = <cv>{foo:'bar',baz:123}</cv> |
| * <ck>int3dArray</ck> = <cv>[[[123,null],null],null]</cv> |
| * <ck>bean1d3dListMap</ck> = <cv>{key:[[[[{foo:'bar',baz:123}]]]]}</cv> |
| * </p> |
| * |
| * <h5 class='figure'>Example interface</h5> |
| * <p class='bcode w800'> |
| * <jk>public interface</jk> MyConfigInterface { |
| * |
| * String getString(); |
| * <jk>void</jk> setString(String x); |
| * |
| * <jk>int</jk> getInt(); |
| * <jk>void</jk> setInt(<jk>int</jk> x); |
| * |
| * MyEnum getEnum(); |
| * <jk>void</jk> setEnum(MyEnum x); |
| * |
| * MyBean getBean(); |
| * <jk>void</jk> setBean(MyBean x); |
| * |
| * <jk>int</jk>[][][] getInt3dArray(); |
| * <jk>void</jk> setInt3dArray(<jk>int</jk>[][][] x); |
| * |
| * Map<String,List<MyBean[][][]>> getBean1d3dListMap(); |
| * <jk>void</jk> setBean1d3dListMap(Map<String,List<MyBean[][][]>> x); |
| * } |
| * </p> |
| * |
| * <h5 class='figure'>Example usage</h5> |
| * <p class='bcode w800'> |
| * Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build(); |
| * |
| * MyConfigInterface ci = cf.getSectionAsInterface(<js>"MySection"</js>, MyConfigInterface.<jk>class</jk>); |
| * |
| * <jk>int</jk> myInt = ci.getInt(); |
| * |
| * ci.setBean(<jk>new</jk> MyBean()); |
| * |
| * cf.save(); |
| * </p> |
| * |
| * <ul class='notes'> |
| * <li>Calls to setters when the configuration is read-only will cause {@link UnsupportedOperationException} to be thrown. |
| * </ul> |
| * |
| * @param section |
| * The section name to write from. |
| * <br>If empty, refers to the default section. |
| * <br>Must not be <jk>null</jk>. |
| * @param c The proxy interface class. |
| * @return The proxy interface. |
| */ |
| @SuppressWarnings("unchecked") |
| public <T> T getSectionAsInterface(String section, final Class<T> 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 <jk>true</jk> if this section contains the specified key and the key has a non-blank value. |
| * |
| * @param key The key. |
| * @return <jk>true</jk> 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. |
| * |
| * <p> |
| * Returns the existing section if it already exists. |
| * |
| * @param name |
| * The section name. |
| * <br>Must not be <jk>null</jk>. |
| * <br>Use blank for the default section. |
| * @param preLines |
| * Optional comment and blank lines to add immediately before the section. |
| * <br>If <jk>null</jk>, 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<String> 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. |
| * <br>Must not be <jk>null</jk>. |
| * <br>Use blank for the default section. |
| * @param preLines |
| * Optional comment and blank lines to add immediately before the section. |
| * <br>If <jk>null</jk>, previous pre-lines will not be replaced. |
| * @param contents |
| * Values to set in the new section. |
| * <br>Can be <jk>null</jk>. |
| * @return The appended or existing section. |
| * @throws SerializeException Contents could not be serialized. |
| * @throws UnsupportedOperationException If configuration is read only. |
| */ |
| public Config setSection(String name, List<String> preLines, Map<String,Object> contents) throws SerializeException { |
| checkWrite(); |
| configMap.setSection(section(name), preLines); |
| |
| if (contents != null) |
| for (Map.Entry<String,Object> 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; |
| } |
| |
| /** |
| * Creates the specified import statement if it doesn't exist. |
| * |
| * @param sectionName |
| * The section name where to place the import statement. |
| * <br>Must not be <jk>null</jk>. |
| * <br>Use blank for the default section. |
| * @param importName |
| * The import name. |
| * <br>Must not be <jk>null</jk>. |
| * @param preLines |
| * Optional comment and blank lines to add immediately before the import statement. |
| * <br>If <jk>null</jk>, previous pre-lines will not be replaced. |
| * @return The appended or existing import statement. |
| * @throws UnsupportedOperationException If configuration is read only. |
| */ |
| public Config setImport(String sectionName, String importName, List<String> preLines) { |
| checkWrite(); |
| configMap.setImport(section(name), importName, preLines); |
| return this; |
| } |
| |
| /** |
| * Removes the import statement with the specified name from the specified section. |
| * |
| * @param sectionName |
| * The section name where to place the import statement. |
| * <br>Must not be <jk>null</jk>. |
| * <br>Use blank for the default section. |
| * @param importName |
| * The import name. |
| * <br>Must not be <jk>null</jk>. |
| * @return This object (for method chaining). |
| * @throws UnsupportedOperationException If configuration is read only. |
| */ |
| public Config removeImport(String sectionName, String importName) { |
| checkWrite(); |
| configMap.removeImport(sectionName, importName); |
| 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 Value could not be serialized. |
| */ |
| public Config load(Map<String,Map<String,Object>> m) throws SerializeException { |
| if (m != null) |
| for (Map.Entry<String,Map<String,Object>> 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 Thrown by underlying stream. |
| * @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. |
| * |
| * <p> |
| * 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. |
| * |
| * <p> |
| * Listeners should be removed using {@link #removeListener(ConfigEventListener)}. |
| * |
| * @param listener The new listener to add. |
| * @return This object (for method chaining). |
| */ |
| public synchronized 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 synchronized Config removeListener(ConfigEventListener listener) { |
| listeners.remove(listener); |
| return this; |
| } |
| |
| /** |
| * Closes this configuration object by unregistering it from the underlying config map. |
| * |
| * @throws IOException Thrown by underlying stream. |
| */ |
| 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 Thrown by underlying stream. |
| * @throws InterruptedException Thread was interrupted. |
| * @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 Thrown by underlying stream. |
| * @throws InterruptedException Thread was interrupted. |
| * @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. |
| * |
| * <p> |
| * This is considered a snapshot copy of the config map. |
| * |
| * <p> |
| * 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 toMap() { |
| return configMap.asMap(); |
| } |
| |
| |
| //----------------------------------------------------------------------------------------------------------------- |
| // Test methods |
| //----------------------------------------------------------------------------------------------------------------- |
| |
| ConfigMap getConfigMap() { |
| return configMap; |
| } |
| |
| List<ConfigEventListener> getListeners() { |
| return Collections.unmodifiableList(listeners); |
| } |
| |
| |
| //----------------------------------------------------------------------------------------------------------------- |
| // 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(ConfigEvents 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> 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(); |
| } |
| } |