| // *************************************************************************************************************************** |
| // * 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.common.internal.ArgUtils.*; |
| import static org.apache.juneau.common.internal.IOUtils.*; |
| import static org.apache.juneau.common.internal.StringUtils.*; |
| import static org.apache.juneau.common.internal.ThrowableUtils.*; |
| import static org.apache.juneau.internal.CollectionUtils.*; |
| |
| import java.io.*; |
| import java.lang.annotation.*; |
| import java.lang.reflect.*; |
| import java.util.*; |
| |
| import org.apache.juneau.*; |
| import org.apache.juneau.collections.*; |
| import org.apache.juneau.config.event.*; |
| import org.apache.juneau.config.internal.*; |
| import org.apache.juneau.config.mod.*; |
| import org.apache.juneau.config.store.*; |
| import org.apache.juneau.config.vars.*; |
| 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.*; |
| import org.apache.juneau.utils.*; |
| |
| /** |
| * Main configuration API class. |
| * |
| * <h5 class='section'>Notes:</h5><ul> |
| * <li class='note'>This class is thread safe and reusable. |
| * </ul> |
| * |
| * <h5 class='section'>See Also:</h5><ul> |
| * <li class='link'><a class="doclink" href="../../../../index.html#juneau-config">Overview > juneau-config</a> |
| * </ul> |
| */ |
| public final class Config extends Context implements ConfigEventListener { |
| |
| //----------------------------------------------------------------------------------------------------------------- |
| // Static |
| //----------------------------------------------------------------------------------------------------------------- |
| |
| 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 = list(); |
| |
| 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 = sortedSet(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 (FileStore.DEFAULT.exists(name)) |
| return Config.create(name).store(FileStore.DEFAULT).build(); |
| if (ClasspathStore.DEFAULT.exists(name)) |
| return Config.create(name).store(ClasspathStore.DEFAULT).build(); |
| return null; |
| } |
| |
| /** |
| * Creates a new builder for this object. |
| * |
| * @return A new builder. |
| */ |
| public static Builder create() { |
| return new Builder(); |
| } |
| |
| /** |
| * Same as {@link #create()} but initializes the builder with the specified config name. |
| * |
| * @param name The configuration name. |
| * @return A new builder. |
| */ |
| public static Builder create(String name) { |
| return new Builder().name(name); |
| } |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // Builder |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Builder class. |
| */ |
| @FluentSetters |
| public static class Builder extends Context.Builder { |
| |
| String name; |
| ConfigStore store; |
| WriterSerializer serializer; |
| ReaderParser parser; |
| Map<Character,Mod> mods; |
| VarResolver varResolver; |
| int binaryLineLength; |
| BinaryFormat binaryFormat; |
| boolean multiLineValuesOnSeparateLines, readOnly; |
| |
| /** |
| * Constructor, default settings. |
| */ |
| protected Builder() { |
| super(); |
| name = env("Config.name", "Configuration.cfg"); |
| store = FileStore.DEFAULT; |
| serializer = Json5Serializer.DEFAULT; |
| parser = JsonParser.DEFAULT; |
| mods = map(); |
| mods(XorEncodeMod.INSTANCE); |
| varResolver = VarResolver.DEFAULT; |
| binaryLineLength = env("Config.binaryLineLength", -1); |
| binaryFormat = env("Config.binaryFormat", BinaryFormat.BASE64); |
| multiLineValuesOnSeparateLines = env("Config.multiLineValuesOnSeparateLines", false); |
| readOnly = env("Config.readOnly", false); |
| } |
| |
| /** |
| * Copy constructor. |
| * |
| * @param copyFrom The bean to copy from. |
| */ |
| protected Builder(Config copyFrom) { |
| super(copyFrom); |
| name = copyFrom.name; |
| store = copyFrom.store; |
| serializer = copyFrom.serializer; |
| parser = copyFrom.parser; |
| mods = copyOf(copyFrom.mods); |
| varResolver = copyFrom.varResolver; |
| binaryLineLength = copyFrom.binaryLineLength; |
| binaryFormat = copyFrom.binaryFormat; |
| multiLineValuesOnSeparateLines = copyFrom.multiLineValuesOnSeparateLines; |
| readOnly = copyFrom.readOnly; |
| } |
| |
| /** |
| * Copy constructor. |
| * |
| * @param copyFrom The builder to copy from. |
| */ |
| protected Builder(Builder copyFrom) { |
| super(copyFrom); |
| name = copyFrom.name; |
| store = copyFrom.store; |
| serializer = copyFrom.serializer; |
| parser = copyFrom.parser; |
| mods = copyOf(copyFrom.mods); |
| varResolver = copyFrom.varResolver; |
| binaryLineLength = copyFrom.binaryLineLength; |
| binaryFormat = copyFrom.binaryFormat; |
| multiLineValuesOnSeparateLines = copyFrom.multiLineValuesOnSeparateLines; |
| readOnly = copyFrom.readOnly; |
| } |
| |
| @Override /* Context.Builder */ |
| public Builder copy() { |
| return new Builder(this); |
| } |
| |
| @Override /* Context.Builder */ |
| public Config build() { |
| return build(Config.class); |
| } |
| |
| //----------------------------------------------------------------------------------------------------------------- |
| // Properties |
| //----------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Configuration name. |
| * |
| * <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. |
| * |
| * @param value |
| * The new value for this property. |
| * <br>The default is the first value found: |
| * <ul> |
| * <li>System property <js>"Config.name" |
| * <li>Environment variable <js>"CONFIG_NAME" |
| * <li><js>"Configuration.cfg"</js> |
| * </ul> |
| * @return This object. |
| */ |
| public Builder name(String value) { |
| name = value; |
| return this; |
| } |
| |
| /** |
| * Configuration store. |
| * |
| * <p> |
| * The configuration store used for retrieving and storing configurations. |
| * |
| * @param value |
| * The new value for this property. |
| * <br>The default is {@link FileStore#DEFAULT}. |
| * @return This object. |
| */ |
| public Builder store(ConfigStore value) { |
| store = value; |
| return this; |
| } |
| |
| /** |
| * Configuration store. |
| * |
| * <p> |
| * Convenience method for calling <code>store(ConfigMemoryStore.<jsf>DEFAULT</jsf>)</code>. |
| * |
| * @return This object. |
| */ |
| public Builder memStore() { |
| store = MemoryStore.DEFAULT; |
| return this; |
| } |
| |
| /** |
| * POJO serializer. |
| * |
| * <p> |
| * The serializer to use for serializing POJO values. |
| * |
| * @param value |
| * The new value for this property. |
| * <br>The default is {@link Json5Serializer#DEFAULT} |
| * @return This object. |
| */ |
| public Builder serializer(WriterSerializer value) { |
| serializer = value; |
| return this; |
| } |
| |
| /** |
| * POJO parser. |
| * |
| * <p> |
| * The parser to use for parsing values to POJOs. |
| * |
| * @param value |
| * The new value for this property. |
| * <br>The default is {@link JsonParser#DEFAULT}. |
| * @return This object. |
| */ |
| public Builder parser(ReaderParser value) { |
| parser = value; |
| return this; |
| } |
| |
| /** |
| * Adds a value modifier. |
| * |
| * <p> |
| * Modifiers are used to modify entry value before being persisted. |
| * |
| * @param values |
| * The mods to apply to this config. |
| * @return This object. |
| */ |
| public Builder mods(Mod...values) { |
| for (Mod value : values) |
| mods.put(value.getId(), value); |
| return this; |
| } |
| |
| /** |
| * SVL variable resolver. |
| * |
| * <p> |
| * The resolver to use for resolving SVL variables. |
| * |
| * @param value |
| * The new value for this property. |
| * <br>The default is {@link VarResolver#DEFAULT}. |
| * @return This object. |
| */ |
| public Builder varResolver(VarResolver value) { |
| varResolver = value; |
| return this; |
| } |
| |
| /** |
| * Binary value line length. |
| * |
| * <p> |
| * When serializing binary values, lines will be split after this many characters. |
| * <br>Use <c>-1</c> to represent no line splitting. |
| * |
| * @param value |
| * The new value for this property. |
| * <br>The default is the first value found: |
| * <ul> |
| * <li>System property <js>"Config.binaryLineLength" |
| * <li>Environment variable <js>"CONFIG_BINARYLINELENGTH" |
| * <li><c>-1</c> |
| * </ul> |
| * @return This object. |
| */ |
| public Builder binaryLineLength(int value) { |
| binaryLineLength = value; |
| return this; |
| } |
| |
| /** |
| * Binary value format. |
| * |
| * <p> |
| * The format to use when persisting byte arrays. |
| * |
| * <ul class='values'> |
| * <li>{@link BinaryFormat#BASE64} - BASE64-encoded string. |
| * <li>{@link BinaryFormat#HEX} - Hexadecimal. |
| * <li>{@link BinaryFormat#SPACED_HEX} - Hexadecimal with spaces between bytes. |
| * </ul> |
| * |
| * @param value |
| * The new value for this property. |
| * <br>The default is the first value found: |
| * <ul> |
| * <li>System property <js>"Config.binaryFormat" |
| * <li>Environment variable <js>"CONFIG_BINARYFORMAT" |
| * <li>{@link BinaryFormat#BASE64} |
| * </ul> |
| * @return This object. |
| */ |
| public Builder binaryFormat(BinaryFormat value) { |
| binaryFormat = value; |
| return this; |
| } |
| |
| /** |
| * Multi-line values on separate lines. |
| * |
| * <p> |
| * When enabled, multi-line values will always be placed on a separate line from the key. |
| * |
| * <p> |
| * The default is the first value found: |
| * <ul> |
| * <li>System property <js>"Config.multiLineValuesOnSeparateLine" |
| * <li>Environment variable <js>"CONFIG_MULTILINEVALUESONSEPARATELINE" |
| * <li><jk>false</jk> |
| * </ul> |
| * |
| * @return This object. |
| */ |
| public Builder multiLineValuesOnSeparateLines() { |
| multiLineValuesOnSeparateLines = true; |
| return this; |
| } |
| |
| /** |
| * Read-only mode. |
| * |
| * <p> |
| * When enabled, attempts to call any setters on this object will throw an {@link UnsupportedOperationException}. |
| * |
| * <p> |
| * The default is the first value found: |
| * <ul> |
| * <li>System property <js>"Config.readOnly" |
| * <li>Environment variable <js>"CONFIG_READONLY" |
| * <li><jk>false</jk> |
| * </ul> |
| * |
| * @return This object. |
| */ |
| public Builder readOnly() { |
| readOnly = true; |
| return this; |
| } |
| |
| // <FluentSetters> |
| |
| @Override /* GENERATED - org.apache.juneau.Context.Builder */ |
| public Builder annotations(Annotation...values) { |
| super.annotations(values); |
| return this; |
| } |
| |
| @Override /* GENERATED - org.apache.juneau.Context.Builder */ |
| public Builder apply(AnnotationWorkList work) { |
| super.apply(work); |
| return this; |
| } |
| |
| @Override /* GENERATED - org.apache.juneau.Context.Builder */ |
| public Builder applyAnnotations(java.lang.Class<?>...fromClasses) { |
| super.applyAnnotations(fromClasses); |
| return this; |
| } |
| |
| @Override /* GENERATED - org.apache.juneau.Context.Builder */ |
| public Builder applyAnnotations(Method...fromMethods) { |
| super.applyAnnotations(fromMethods); |
| return this; |
| } |
| |
| @Override /* GENERATED - org.apache.juneau.Context.Builder */ |
| public Builder cache(Cache<HashKey,? extends org.apache.juneau.Context> value) { |
| super.cache(value); |
| return this; |
| } |
| |
| @Override /* GENERATED - org.apache.juneau.Context.Builder */ |
| public Builder debug() { |
| super.debug(); |
| return this; |
| } |
| |
| @Override /* GENERATED - org.apache.juneau.Context.Builder */ |
| public Builder debug(boolean value) { |
| super.debug(value); |
| return this; |
| } |
| |
| @Override /* GENERATED - org.apache.juneau.Context.Builder */ |
| public Builder impl(Context value) { |
| super.impl(value); |
| return this; |
| } |
| |
| @Override /* GENERATED - org.apache.juneau.Context.Builder */ |
| public Builder type(Class<? extends org.apache.juneau.Context> value) { |
| super.type(value); |
| return this; |
| } |
| |
| // </FluentSetters> |
| } |
| |
| //------------------------------------------------------------------------------------------------------------------- |
| // Instance |
| //------------------------------------------------------------------------------------------------------------------- |
| |
| final String name; |
| final ConfigStore store; |
| final WriterSerializer serializer; |
| final ReaderParser parser; |
| final Map<Character,Mod> mods; |
| final VarResolver varResolver; |
| final int binaryLineLength; |
| final BinaryFormat binaryFormat; |
| final boolean multiLineValuesOnSeparateLines, readOnly; |
| final BeanSession beanSession; |
| final VarResolverSession varSession; |
| |
| private final ConfigMap configMap; |
| private final List<ConfigEventListener> listeners = synced(linkedList()); |
| |
| |
| @Override /* Context */ |
| public Builder copy() { |
| return new Builder(this); |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param builder The builder for this object. |
| * @throws IOException Thrown by underlying stream. |
| */ |
| public Config(Builder builder) throws IOException { |
| super(builder); |
| |
| name = builder.name; |
| store = builder.store; |
| configMap = store.getMap(name); |
| configMap.register(this); |
| serializer = builder.serializer; |
| parser = builder.parser; |
| beanSession = parser.getBeanContext().getSession(); |
| mods = unmodifiable(copyOf(builder.mods)); |
| varResolver = builder.varResolver; |
| varSession = varResolver |
| .copy() |
| .vars(ConfigVar.class) |
| .bean(Config.class, this) |
| .build() |
| .createSession(); |
| binaryLineLength = builder.binaryLineLength; |
| binaryFormat = builder.binaryFormat; |
| multiLineValuesOnSeparateLines = builder.multiLineValuesOnSeparateLines; |
| readOnly = builder.readOnly; |
| } |
| |
| Config(Config copyFrom, VarResolverSession varSession) { |
| super(copyFrom); |
| name = copyFrom.name; |
| store = copyFrom.store; |
| configMap = copyFrom.configMap; |
| configMap.register(this); |
| serializer = copyFrom.serializer; |
| parser = copyFrom.parser; |
| mods = copyFrom.mods; |
| varResolver = copyFrom.varResolver; |
| 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. |
| */ |
| private String getRaw(String key) { |
| |
| String sname = sname(key); |
| String skey = skey(key); |
| |
| ConfigMapEntry ce = configMap.getEntry(sname, skey); |
| |
| if (ce == null) |
| return null; |
| |
| return removeMods(ce.getModifiers(), ce.getValue()); |
| } |
| |
| String applyMods(String mods, String x) { |
| if (mods != null && x != null) |
| for (int i = 0; i < mods.length(); i++) |
| x = getMod(mods.charAt(i)).doApply(x); |
| return x; |
| } |
| |
| String removeMods(String mods, String x) { |
| if (mods != null && x != null) |
| for (int i = mods.length()-1; i > -1; i--) |
| x = getMod(mods.charAt(i)).doRemove(x); |
| return x; |
| } |
| |
| Mod getMod(char id) { |
| Mod x = mods.get(id); |
| return x == null ? Mod.NO_OP : x; |
| } |
| |
| //----------------------------------------------------------------------------------------------------------------- |
| // Utility methods |
| //----------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Takes the settings defined in this configuration and sets them as system properties. |
| * |
| * @return This object. |
| */ |
| public Config setSystemProperties() { |
| for (String section : getSectionNames()) { |
| for (String key : getKeys(section)) { |
| String k = (section.isEmpty() ? key : section + '/' + key); |
| System.setProperty(k, getRaw(k)); |
| } |
| } |
| return this; |
| } |
| |
| //----------------------------------------------------------------------------------------------------------------- |
| // Workhorse setters |
| //----------------------------------------------------------------------------------------------------------------- |
| |
| /** |
| * Sets a value in this config. |
| * |
| * @param key The key. |
| * @param value The value. |
| * @return This object. |
| * @throws UnsupportedOperationException If configuration is read only. |
| */ |
| public Config set(String key, String value) { |
| checkWrite(); |
| assertArgNotNull("key", key); |
| String sname = sname(key); |
| String skey = skey(key); |
| |
| ConfigMapEntry ce = configMap.getEntry(sname, skey); |
| if (ce == null && value == null) |
| return this; |
| |
| String s = applyMods(ce == null ? null : ce.getModifiers(), stringify(value)); |
| |
| 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 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, String modifiers, String comment, List<String> preLines) throws SerializeException { |
| checkWrite(); |
| assertArgNotNull("key", key); |
| String sname = sname(key); |
| String skey = skey(key); |
| modifiers = nullIfEmpty(modifiers); |
| |
| String s = applyMods(modifiers, serialize(value, serializer)); |
| |
| configMap.setEntry(sname, skey, s, 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. |
| * @throws UnsupportedOperationException If configuration is read only. |
| */ |
| public Config applyMods() { |
| checkWrite(); |
| for (String section : configMap.getSections()) { |
| for (String key : configMap.getKeys(section)) { |
| ConfigMapEntry ce = configMap.getEntry(section, key); |
| if (ce.getModifiers() != null) { |
| String mods = ce.getModifiers(); |
| String value = ce.getValue(); |
| for (int i = 0; i < mods.length(); i++) { |
| Mod mod = getMod(mods.charAt(i)); |
| if (! mod.isApplied(value)) { |
| configMap.setEntry(section, key, mod.apply(value), 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> |
| * |
| * <p> |
| * If entry does not exist, returns an empty {@link Entry} object. |
| * |
| * @param key The key. |
| * @return The entry bean, never <jk>null</jk>. |
| */ |
| public Entry get(String key) { |
| return new Entry(this, configMap, sname(key), skey(key)); |
| } |
| |
| /** |
| * 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> |
| * |
| * <p> |
| * If entry does not exist, returns <jk>null</jk>. |
| * |
| * <h5 class='section'>Notes:</h5><ul> |
| * <li class='note'>This method is equivalent to calling <c>get(<jv>key</jv>).orElse(<jk>null</jk>);</c>. |
| * </ul> |
| * |
| * @param key The key. |
| * @return The entry value, or <jk>null</jk> if it doesn't exist. |
| */ |
| public String getString(String key) { |
| return new Entry(this, configMap, sname(key), skey(key)).orElse(null); |
| } |
| |
| /** |
| * Gets the section with the specified name. |
| * |
| * <p> |
| * If section does not exist, returns an empty {@link Section} object. |
| * |
| * @param name The section name. <jk>null</jk> and blank refer to the default section. |
| * @return The section bean, never <jk>null</jk>. |
| */ |
| public Section getSection(String name) { |
| return new Section(this, configMap, emptyIfNull(name)); |
| } |
| |
| /** |
| * 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)); |
| } |
| |
| /** |
| * Returns the section names defined in this config. |
| * |
| * @return The section names defined in this config. |
| */ |
| public Set<String> getSectionNames() { |
| return unmodifiable(configMap.getSections()); |
| } |
| |
| /** |
| * 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(get(key).as(String.class).orElse(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 asRuntimeException(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. |
| * @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. |
| * @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. |
| * @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. |
| * @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. |
| * @throws IOException If a problem occurred trying to send contents to the writer. |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| 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. |
| * @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(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. |
| * @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. |
| * @throws UnsupportedOperationException If configuration is read only. |
| */ |
| public Config rollback() { |
| checkWrite(); |
| configMap.rollback(); |
| return this; |
| } |
| |
| /** |
| * Returns the contents of this config as a simple map. |
| * |
| * @return The contents of this config as a simple map. |
| */ |
| public JsonMap toMap() { |
| return configMap.asMap(); |
| } |
| |
| //----------------------------------------------------------------------------------------------------------------- |
| // Test methods |
| //----------------------------------------------------------------------------------------------------------------- |
| |
| ConfigMap getConfigMap() { |
| return configMap; |
| } |
| |
| List<ConfigEventListener> getListeners() { |
| return unmodifiable(listeners); |
| } |
| |
| |
| //----------------------------------------------------------------------------------------------------------------- |
| // Interface methods |
| //----------------------------------------------------------------------------------------------------------------- |
| |
| @Override /* ConfigEventListener */ |
| public synchronized void onConfigChange(ConfigEvents events) { |
| for (ConfigEventListener l : listeners) |
| l.onConfigChange(events); |
| } |
| |
| //----------------------------------------------------------------------------------------------------------------- |
| // 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; |
| } |
| |
| 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 String sname(String key) { |
| assertArgNotNull("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) { |
| assertArgNotNull("section", section); |
| if (isEmpty(section)) |
| return ""; |
| return section; |
| } |
| |
| 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(); |
| } |
| } |