This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 8611b24 Config API refactoring.
8611b24 is described below
commit 8611b246f9a0f5e11f6060197260f0107eef85dd
Author: JamesBognar <[email protected]>
AuthorDate: Tue Feb 20 21:38:07 2018 -0500
Config API refactoring.
---
.../apache/juneau/config/ConfigFileWritable.java | 3 +-
.../juneau/config/event/ChangeEventListener.java | 2 +-
.../org/apache/juneau/config/proto/Config.java | 1339 ++++++++++++++++++++
.../apache/juneau/config/proto/ConfigBuilder.java | 323 +++++
.../org/apache/juneau/config/proto/ConfigMod.java | 103 ++
.../apache/juneau/config/store/ConfigEntry.java | 31 +-
.../org/apache/juneau/config/store/ConfigMap.java | 200 +--
.../juneau/config/proto/ConfigMapListenerTest.java | 62 +-
.../src/main/java/org/apache/juneau/Writable.java | 3 +-
.../org/apache/juneau/internal/StringUtils.java | 2 +
.../org/apache/juneau/utils/StringMessage.java | 4 +-
.../java/org/apache/juneau/utils/StringObject.java | 3 +-
.../org/apache/juneau/rest/ReaderResource.java | 3 +-
13 files changed, 1943 insertions(+), 135 deletions(-)
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigFileWritable.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigFileWritable.java
index f846cb5..f8ee441 100644
---
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigFileWritable.java
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigFileWritable.java
@@ -34,10 +34,11 @@ class ConfigFileWritable implements Writable {
}
@Override /* Writable */
- public void writeTo(Writer out) throws IOException {
+ public Writer writeTo(Writer out) throws IOException {
cf.readLock();
try {
cf.serializeTo(out);
+ return out;
} finally {
cf.readUnlock();
}
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/event/ChangeEventListener.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/event/ChangeEventListener.java
index 546f330..335c66c 100644
---
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/event/ChangeEventListener.java
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/event/ChangeEventListener.java
@@ -24,5 +24,5 @@ public interface ChangeEventListener {
*
* @param events The change events.
*/
- void onEvents(List<ChangeEvent> events);
+ void onChange(List<ChangeEvent> events);
}
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/proto/Config.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/proto/Config.java
new file mode 100644
index 0000000..3b1b30f
--- /dev/null
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/proto/Config.java
@@ -0,0 +1,1339 @@
+//
***************************************************************************************************************************
+// * 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.proto;
+
+import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.internal.ThrowableUtils.*;
+import static org.apache.juneau.config.proto.ConfigMod.*;
+import static java.lang.reflect.Modifier.*;
+
+import java.beans.*;
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.config.encode.*;
+import org.apache.juneau.config.encode.Encoder;
+import org.apache.juneau.config.event.*;
+import org.apache.juneau.config.store.*;
+import org.apache.juneau.config.vars.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.svl.*;
+
+/**
+ * TODO
+ */
+public final class Config extends Context implements ChangeEventListener,
Closeable, Writable {
+
+
//-------------------------------------------------------------------------------------------------------------------
+ // Configurable properties
+
//-------------------------------------------------------------------------------------------------------------------
+
+ private 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> <code>String</code>
+ * <li><b>Default:</b> <js>"Configuration"</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 minus the file
extension, although
+ * the name can be anything identifiable by the {@link Store} 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 Store}
+ * <li><b>Default:</b> {@link FileStore#DEFAULT}
+ * <li><b>Methods:</b>
+ * <ul>
+ * <li class='jm'>{@link
ConfigBuilder#store(Store)}
+ * </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 JsonSerializer#DEFAULT_LAX}
+ * <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 Encoder}
+ * <li><b>Default:</b> {@link XorEncoder#INSTANCE}
+ * <li><b>Methods:</b>
+ * <ul>
+ * <li class='jm'>{@link
ConfigBuilder#encoder(Class)}
+ * <li class='jm'>{@link
ConfigBuilder#encoder(Encoder)}
+ * </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> <code>Integer</code>
+ * <li><b>Default:</b> <code>-1</code>
+ * <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 <code>-1</code> 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> <code>String</code>
+ * <li><b>Default:</b> <js>"BASE64"</js>
+ * <li><b>Methods:</b>
+ * <ul>
+ * <li class='jm'>{@link
ConfigBuilder#binaryFormat(String)}
+ * </ul>
+ * </ul>
+ *
+ * <h5 class='section'>Description:</h5>
+ * <p>
+ * The format to use when persisting byte arrays.
+ *
+ * <p>
+ * Possible values:
+ * <ul>
+ * <li><js>"BASE64"</js> - BASE64-encoded string.
+ * <li><js>"HEX"</js> - Hexadecimal.
+ * <li><js>"SPACED_HEX"</js> - Hexadecimal with spaces between
bytes.
+ * </ul>
+ */
+ public static final String CONFIG_binaryFormat = PREFIX +
"binaryFormat.s";
+
+ /**
+ * Configuration property: Beans on separate lines.
+ *
+ * <h5 class='section'>Property:</h5>
+ * <ul>
+ * <li><b>Name:</b> <js>"Config.beanOnSeparateLines.b"</js>
+ * <li><b>Data type:</b> <code>Boolean</code>
+ * <li><b>Default:</b> <jk>false</jk>
+ * <li><b>Methods:</b>
+ * <ul>
+ * <li class='jm'>{@link
ConfigBuilder#beansOnSeparateLines(boolean)}
+ * </ul>
+ * </ul>
+ *
+ * <h5 class='section'>Description:</h5>
+ * <p>
+ * When enabled, serialized POJOs will be placed on a separate line
from the key.
+ */
+ public static final String CONFIG_beansOnSeparateLines = PREFIX +
"beansOnSeparateLines.b";
+
+
+
//-------------------------------------------------------------------------------------------------------------------
+ // Instance
+
//-------------------------------------------------------------------------------------------------------------------
+
+ private final String name;
+ private final Store store;
+ private final WriterSerializer serializer;
+ private final ReaderParser parser;
+ private final Encoder encoder;
+ private final VarResolverSession varSession;
+ private final int binaryLineLength;
+ private final String binaryFormat;
+ private final boolean beansOnSeparateLines;
+ private final ConfigMap configMap;
+ private final BeanSession beanSession;
+ private volatile boolean closed;
+ private final List<ChangeEventListener> listeners =
Collections.synchronizedList(new LinkedList<ChangeEventListener>());
+
+
+ /**
+ * 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();
+ }
+
+ @Override /* Context */
+ public ConfigBuilder builder() {
+ return new ConfigBuilder(getPropertyStore());
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param ps
+ * The property store containing all the settings for this object.
+ * @throws IOException
+ */
+ public Config(PropertyStore ps) throws IOException {
+ super(ps);
+
+ name = getStringProperty(CONFIG_name, "Configuration");
+ store = getInstanceProperty(CONFIG_store, Store.class,
FileStore.DEFAULT);
+ configMap = store.getMap(name);
+ configMap.register(this);
+ serializer = getInstanceProperty(CONFIG_serializer,
WriterSerializer.class, JsonSerializer.DEFAULT_LAX);
+ parser = getInstanceProperty(CONFIG_parser, ReaderParser.class,
JsonParser.DEFAULT);
+ beanSession = parser.createBeanSession();
+ encoder = getInstanceProperty(CONFIG_encoder, Encoder.class,
XorEncoder.INSTANCE);
+ varSession = getInstanceProperty(CONFIG_varResolver,
VarResolver.class, VarResolver.DEFAULT)
+ .builder()
+ .vars(ConfigFileVar.class)
+ .contextObject(ConfigFileVar.SESSION_config, this)
+ .build()
+ .createSession();
+ binaryLineLength = getIntegerProperty(CONFIG_binaryLineLength,
-1);
+ binaryFormat = getStringProperty(CONFIG_binaryFormat,
"BASE64").toUpperCase();
+ beansOnSeparateLines =
getBooleanProperty(CONFIG_beansOnSeparateLines, false);
+ }
+
+ Config(Config copyFrom, VarResolverSession varSession) {
+ super(null);
+ name = copyFrom.name;
+ store = copyFrom.store;
+ configMap = copyFrom.configMap;
+ configMap.register(this);
+ serializer = copyFrom.serializer;
+ parser = copyFrom.parser;
+ encoder = copyFrom.encoder;
+ this.varSession = varSession;
+ binaryLineLength = copyFrom.binaryLineLength;
+ binaryFormat = copyFrom.binaryFormat;
+ beansOnSeparateLines = copyFrom.beansOnSeparateLines;
+ 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);
+ }
+
+
+
//--------------------------------------------------------------------------------
+ // 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) {
+ val = encoder.decode(key, val);
+ }
+ }
+
+ return val;
+ }
+
+
+
//--------------------------------------------------------------------------------
+ // Workhorse setters
+
//--------------------------------------------------------------------------------
+
+ /**
+ * Sets a value in this config.
+ *
+ * @param key The key.
+ * @param value The value.
+ * @return This object (for method chaining).
+ */
+ public Config set(String key, String value) {
+ assertFieldNotNull(key, "key");
+ String sname = sname(key);
+ String skey = skey(key);
+ ConfigEntry ce = configMap.getEntry(sname, skey);
+
+ String s = asString(value);
+ for (ConfigMod m : ConfigMod.asModifiers(ce.getModifiers())) {
+ if (m == ENCODED) {
+ value = encoder.encode(key, s);
+ }
+ }
+
+ configMap.setValue(sname, skey, s);
+ 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 <code>put(key, value, isEncoded(key))</code>.
+ *
+ * @param key The key. See {@link #getString(String)} for a
description of the key.
+ * @param value The new value POJO.
+ * @return The previous value, or <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 config file 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. See {@link #getString(String)} for a
description of the key.
+ * @param value The new value.
+ * @param serializer
+ * The serializer to use for serializing the object.
+ * If <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 config file 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. See {@link #getString(String)} for a
description of the key.
+ * @param value The new value.
+ * @param serializer
+ * The serializer to use for serializing the object.
+ * If <jk>null</jk>, then uses the predefined serializer on the
config file.
+ * @param modifiers
+ * Optional modifiers to apply to the value.
+ * <br>Can be <jk>null</jk>.
+ * @param comment
+ * Optional same-line comment to add to this value.
+ * <br>Can be <jk>null</jk>.
+ * @param preLines
+ * Optional comment or blank lines to add before this entry.
+ * <br>Can be <jk>null</jk>.
+ * @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 config file is read only.
+ */
+ public Config set(String key, Object value, Serializer serializer,
ConfigMod[] modifiers, String comment, List<String> preLines) throws
SerializeException {
+ assertFieldNotNull(key, "key");
+ String sname = sname(key);
+ String skey = skey(key);
+ ConfigEntry ce = configMap.getEntry(sname, skey);
+
+ String s = serialize(value, serializer);
+ for (ConfigMod m : ConfigMod.asModifiers(ce.getModifiers())) {
+ if (m == ENCODED) {
+ s = encoder.encode(key, s);
+ }
+ }
+
+ configMap.setEntry(sname, skey, s,
ConfigMod.asString(modifiers), comment, preLines);
+ return this;
+ }
+
+ /**
+ * Removes an entry with the specified key.
+ *
+ * @param key The key. See {@link #getString(String)} for a
description of the key.
+ * @return The previous value, or <jk>null</jk> if the section or key
did not previously exist.
+ * @throws UnsupportedOperationException If config file is read only.
+ */
+ public Config remove(String key) {
+ return set(key, null);
+ }
+
+
+
//--------------------------------------------------------------------------------
+ // 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 <code>[section]</code> header).
+ * <li>
+ * <js>"section/key"</js> - A value from the specified
section.
+ * </ul>
+ *
+ * @param key The key. See {@link #getString(String)} for a
description of the key.
+ * @return The value, or <jk>null</jk> if the section or key does not
exist.
+ */
+ public String getString(String key) {
+ return getString(key, null);
+ }
+
+ /**
+ * 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 <code>[section]</code> header).
+ * <li>
+ * <js>"section/key"</js> - A value from the specified
section.
+ * </ul>
+ *
+ * @param key The key. See {@link #getString(String)} for a
description of the key.
+ * @param def The default value.
+ * @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 (s == null)
+ 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. See {@link #getString(String)} for a
description of the key.
+ * @return The value, or an empty list if the section or key does not
exist.
+ */
+ public String[] getStringArray(String key) {
+ return getStringArray(key, new String[0]);
+ }
+
+ /**
+ * Same as {@link #getStringArray(String)} but returns a default value
if the value cannot be found.
+ *
+ * @param key The key. See {@link #getString(String)} for a
description of the key.
+ * @param def The default value if section or key does not exist.
+ * @return The value, or an empty list if the section or key does not
exist.
+ */
+ public String[] getStringArray(String key, String[] def) {
+ String s = getString(key);
+ if (s == null)
+ return def;
+ String[] r = isEmpty(s) ? new String[0] : split(s);
+ return r.length == 0 ? def : r;
+ }
+
+ /**
+ * Convenience method for getting int config values.
+ *
+ * @param key The key. See {@link #getString(String)} for a
description of the key.
+ * @return The value, or <code>0</code> if the section or key does not
exist or cannot be parsed as an integer.
+ */
+ public int getInt(String key) {
+ return getInt(key, 0);
+ }
+
+ /**
+ * 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>
+ *
+ * @param key The key. See {@link #getString(String)} for a
description of the key.
+ * @param def The default value if config file or value does not exist.
+ * @return The value, or the default value if the section or key does
not exist or cannot be parsed as an integer.
+ */
+ public 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. See {@link #getString(String)} for a
description of 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. See {@link #getString(String)} for a
description of the key.
+ * @param def The default value if config file or value does not exist.
+ * @return The value, or the default value if the section or key does
not exist or cannot be parsed as a boolean.
+ */
+ public boolean getBoolean(String key, boolean def) {
+ String s = getString(key);
+ return isEmpty(s) ? def : Boolean.parseBoolean(s);
+ }
+
+ /**
+ * Convenience method for getting long config values.
+ *
+ * @param key The key. See {@link #getString(String)} for a
description of the key.
+ * @return The value, or <code>0</code> if the section or key does not
exist or cannot be parsed as a long.
+ */
+ public long getLong(String key) {
+ return getLong(key, 0);
+ }
+
+ /**
+ * 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>
+ *
+ * @param key The key. See {@link #getString(String)} for a
description of the key.
+ * @param def The default value if config file or value does not exist.
+ * @return The value, or the default value if the section or key does
not exist or cannot be parsed as an integer.
+ */
+ public long getLong(String key, long def) {
+ String s = getString(key);
+ if (isEmpty(s))
+ return def;
+ return parseLongWithSuffix(s);
+ }
+
+ /**
+ * 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 <code>[section]</code> 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'>
+ * ConfigFile cf =
ConfigFile.<jsm>create</jsm>().build(<js>"MyConfig.cfg"</js>);
+ *
+ * <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>
+ * <code>Collection</code> classes are assumed to be followed by zero
or one objects indicating the element type.
+ *
+ * <p>
+ * <code>Map</code> 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.
+ *
+ * <h5 class='section'>Notes:</h5>
+ * <ul class='spaced-list'>
+ * <li>
+ * Use the {@link #getObject(String, Class)} method
instead if you don't need a parameterized map/collection.
+ * </ul>
+ *
+ * @param key The key. See {@link #getString(String)} for a
description of 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. See {@link #getString(String)} for a
description of 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'>
+ * ConfigFile cf =
ConfigFile.<jsm>create</jsm>().build(<js>"MyConfig.cfg"</js>);
+ *
+ * <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. See {@link #getString(String)} for a
description of the key.
+ * @param type The object type to create.
+ * @return The parsed object.
+ * @throws ParseException
+ * If the input contains a syntax error or is malformed, or is not
valid for the specified type.
+ * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for
maps and collections.
+ */
+ public <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. See {@link #getString(String)} for a
description of 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
+ * If the input contains a syntax error or is malformed, or is not
valid for the specified type.
+ * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for
maps and collections.
+ */
+ public <T> 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. See {@link #getString(String)} for a
description of the key.
+ * @param def The default value if section or key does not exist.
+ * @param type The class to convert the value to.
+ * @throws ParseException If parser could not parse the value or if a
parser is not registered with this config file.
+ * @return The value, or <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. See {@link #getString(String)} for a
description of 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 section or key does not exist.
+ * @param type The class to convert the value to.
+ * @throws ParseException If parser could not parse the value or if a
parser is not registered with this config file.
+ * @return The value, or <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. See {@link #getString(String)} for a
description of the key.
+ * @param def The default value if section or key does not exist.
+ * @param type
+ * The object type to create.
+ * <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. See {@link #getString(String)} for a
description of 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 section or key 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);
+ }
+
+ /**
+ * 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.
+ * @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 IllegalArgumentException
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ */
+ public Config writeProperties(String section, Object bean, boolean
ignoreUnknownProperties) throws ParseException, IllegalArgumentException,
IllegalAccessException, InvocationTargetException {
+ assertFieldNotNull(bean, "bean");
+
+ Set<String> keys = configMap.getKeys(section);
+ if (keys == null)
+ throw new IllegalArgumentException("Section not found");
+ keys = new LinkedHashSet<>(keys);
+
+ for (Method m : bean.getClass().getMethods()) {
+ int mod = m.getModifiers();
+ if (isPublic(mod) && (!isStatic(mod)) &&
m.getName().startsWith("set") && m.getParameterTypes().length == 1) {
+ Class<?> pt = m.getParameterTypes()[0];
+ String propName =
Introspector.decapitalize(m.getName().substring(3));
+ Object value = getObject(section + '/' +
propName, pt);
+ if (value != null) {
+ m.invoke(bean, value);
+ keys.remove(propName);
+ }
+ }
+ }
+
+ if (! (ignoreUnknownProperties || keys.isEmpty()))
+ throw new ParseException("Invalid properties found in
config file section ''{0}'': {1}", section, keys);
+
+ return this;
+ }
+
+ /**
+ * Shortcut for calling <code>getSectionAsBean(sectionName, c,
<jk>false</jk>)</code>.
+ *
+ * @param sectionName The section name to write from.
+ * @param c The bean class to create.
+ * @return A new bean instance.
+ * @throws ParseException
+ */
+ public <T> T getSectionAsBean(String sectionName, Class<T>c) throws
ParseException {
+ return getSectionAsBean(sectionName, 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'>
+ * <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'>
+ * <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'>
+ * ConfigFile cf =
ConfigFile.<jsm>create</jsm>().build(<js>"MyConfig.cfg"</js>);
+ * Address myAddress = cf.getSectionAsBean(<js>"MySection"</js>,
Address.<jk>class</jk>);
+ * </p>
+ *
+ * @param section The section name to write from.
+ * @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.
+ * @throws ParseException
+ */
+ public <T> T getSectionAsBean(String section, Class<T> c, boolean
ignoreUnknownProperties) throws ParseException {
+ assertFieldNotNull(c, "c");
+
+ BeanMap<T> bm = beanSession.newBeanMap(c);
+ for (String k : configMap.getKeys(section)) {
+ BeanPropertyMeta bpm = bm.getPropertyMeta(k);
+ if (bpm == null) {
+ if (! ignoreUnknownProperties)
+ throw new ParseException("Unknown
property {0} encountered", k);
+ } else {
+ bm.put(k, getObject(section + '/' + k,
bpm.getClassMeta().getInnerClass()));
+ }
+ }
+ return bm.getBean();
+ }
+
+ /**
+ * 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'>
+ * <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'>
+ * <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'>
+ * ConfigFile cf =
ConfigFile.<jsm>create</jsm>().build(<js>"MyConfig.cfg"</js>);
+ *
+ * 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>
+ *
+ * @param sectionName The section name to retrieve as an interface
proxy.
+ * @param c The proxy interface class.
+ * @return The proxy interface.
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T getSectionAsInterface(final String sectionName, final
Class<T> c) {
+ assertFieldNotNull(c, "c");
+
+ if (! c.isInterface())
+ throw new UnsupportedOperationException("Class passed
to getSectionAsInterface is not an interface.");
+
+ InvocationHandler h = new InvocationHandler() {
+
+ @Override
+ public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
+ BeanInfo bi = Introspector.getBeanInfo(c, null);
+ for (PropertyDescriptor pd :
bi.getPropertyDescriptors()) {
+ Method rm = pd.getReadMethod(), wm =
pd.getWriteMethod();
+ if (method.equals(rm))
+ return
Config.this.getObject(sectionName + '/' + pd.getName(),
rm.getGenericReturnType());
+ if (method.equals(wm))
+ return
Config.this.set(sectionName + '/' + 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. See {@link #getString(String)} for a
description of 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 ! isEmpty(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 <js>"default"</js> for the default section.
+ * @param preLines
+ * Optional comment and blank lines to add immediately before the
section.
+ * <br>Can be <jk>null</jk>.
+ * @return The appended or existing section.
+ * @throws UnsupportedOperationException If config file is read only.
+ */
+ public Config setSection(String name, List<String> preLines) {
+ try {
+ return setSection(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 <js>"default"</js> for the default section.
+ * @param preLines
+ * Optional comment and blank lines to add immediately before the
section.
+ * <br>Can be <jk>null</jk>.
+ * @param contents
+ * Values to set in the new section.
+ * <br>Can be <jk>null</jk>.
+ * @return The appended or existing section.
+ * @throws SerializeException
+ * @throws UnsupportedOperationException If config file is read only.
+ */
+ public Config setSection(String name, List<String> preLines,
Map<String,Object> contents) throws SerializeException {
+ configMap.setSection(name, preLines);
+
+ if (contents != null)
+ for (Map.Entry<String,Object> e : contents.entrySet())
+ set(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).
+ */
+ public Config removeSection(String name) {
+ configMap.removeSection(name);
+ return this;
+ }
+
+ /**
+ * Saves this config to the store.
+ *
+ * @return This object (for method chaining).
+ * @throws IOException
+ */
+ public Config save() throws IOException {
+ configMap.save();
+ 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(ChangeEventListener)}.
+ *
+ * @param listener The new listener to add.
+ * @return This object (for method chaining).
+ * @throws UnsupportedOperationException If config file is read only.
+ */
+ public Config addListener(ChangeEventListener listener) {
+ listeners.add(listener);
+ return this;
+ }
+
+ /**
+ * Removes a listener from this config.
+ *
+ * @param listener The listener to remove.
+ * @return This object (for method chaining).
+ * @throws UnsupportedOperationException If config file is read only.
+ */
+ public Config removeListener(ChangeEventListener listener) {
+ listeners.remove(listener);
+ return this;
+ }
+
+ /**
+ * Unused.
+ */
+ @Override /* Context */
+ public Session createSession(SessionArgs args) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Unused.
+ */
+ @Override /* Context */
+ public SessionArgs createDefaultSessionArgs() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override /* Closeable */
+ public void close() throws IOException {
+ configMap.unregister(this);
+ closed = true;
+ }
+
+ @Override /* Object */
+ protected void finalize() throws Throwable {
+ if (! closed) {
+ System.err.println("Config object not closed.");
+ }
+ }
+
+ @Override /* ChangeEventListener */
+ public void onChange(List<ChangeEvent> events) {
+ for (ChangeEventListener l : listeners)
+ l.onChange(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 (isSimpleType(c))
+ return value.toString();
+
+ if (value instanceof byte[]) {
+ String s = null;
+ byte[] b = (byte[])value;
+ if ("HEX".equals(binaryFormat))
+ s = toHex(b);
+ else if ("SPACED_HEX".equals(binaryFormat))
+ 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('\n').append(s.substring(i,
Math.min(s.length(), i + l)));
+ return sb.toString();
+ }
+
+ String r = null;
+ if (beansOnSeparateLines)
+ r = "\n" + (String)serializer.serialize(value);
+ else
+ r = (String)serializer.serialize(value);
+
+ if (r.startsWith("'"))
+ return r.substring(1, r.length()-1);
+ return r;
+ }
+
+ @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", "");
+ switch (binaryFormat) {
+ case "HEX": return (T)fromHex(s);
+ case "SPACED_HEX": return (T)fromSpacedHex(s);
+ default: return (T)base64Decode(s);
+ }
+ }
+
+ char s1 = firstNonWhitespaceChar(s);
+ if (isArray(type) && s1 != '[')
+ s = '[' + s + ']';
+ else if (s1 != '[' && s1 != '{' && ! "null".equals(s))
+ s = '\'' + s + '\'';
+
+ if (parser == null)
+ parser = this.parser;
+
+ 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 "default";
+ return key.substring(0, i);
+ }
+
+ private String skey(String key) {
+ int i = key.indexOf('/');
+ if (i == -1)
+ return key;
+ return key.substring(i+1);
+ }
+}
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/proto/ConfigBuilder.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/proto/ConfigBuilder.java
new file mode 100644
index 0000000..0e01141
--- /dev/null
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/proto/ConfigBuilder.java
@@ -0,0 +1,323 @@
+//
***************************************************************************************************************************
+// * 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.proto;
+
+import static org.apache.juneau.config.proto.Config.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.config.encode.*;
+import org.apache.juneau.config.store.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.svl.*;
+
+/**
+ * Builder for creating instances of {@link Config Configs}.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bcode'>
+ * Config cf = Config.<jsm>create</jsm>().build(<js>"MyConfig.cfg"</js>);
+ * String setting = cf.get(<js>"MySection/mysetting"</js>);
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5>
+ * <ul class='doctree'>
+ * <li class='link'><a class='doclink'
href='../../../../overview-summary.html#juneau-config'>Overview >
juneau-config</a>
+ * </ul>
+ */
+public class ConfigBuilder extends ContextBuilder {
+
+ /**
+ * Constructor, default settings.
+ */
+ public ConfigBuilder() {
+ super();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param ps The initial configuration settings for this builder.
+ */
+ public ConfigBuilder(PropertyStore ps) {
+ super(ps);
+ }
+
+ @Override /* ContextBuilder */
+ public Config build() {
+ return build(Config.class);
+ }
+
+
+
//--------------------------------------------------------------------------------
+ // Properties
+
//--------------------------------------------------------------------------------
+
+ /**
+ * Configuration property: Configuration name.
+ *
+ * <p>
+ * Specifies the configuration name.
+ * <br>This is typically the configuration file name minus the file
extension, although
+ * the name can be anything identifiable by the {@link Store} used for
retrieving and storing the configuration.
+ *
+ * @param value
+ * The new value for this property.
+ * <br>The default is <js>"Configuration"</js>.
+ * @return This object (for method chaining).
+ */
+ public ConfigBuilder name(String value) {
+ return set(CONFIG_name, value);
+ }
+
+ /**
+ * Configuration property: 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 (for method chaining).
+ */
+ public ConfigBuilder store(Store value) {
+ return set(CONFIG_store, value);
+ }
+
+ /**
+ * Configuration property: POJO serializer.
+ *
+ * <p>
+ * The serializer to use for serializing POJO values.
+ *
+ * @param value
+ * The new value for this property.
+ * <br>The default is {@link JsonSerializer#DEFAULT_LAX}.
+ * @return This object (for method chaining).
+ */
+ public ConfigBuilder serializer(WriterSerializer value) {
+ return set(CONFIG_serializer, value);
+ }
+
+ /**
+ * Configuration property: POJO serializer.
+ *
+ * <p>
+ * The serializer to use for serializing POJO values.
+ *
+ * @param value
+ * The new value for this property.
+ * <br>The default is {@link JsonSerializer#DEFAULT_LAX}.
+ * @return This object (for method chaining).
+ */
+ public ConfigBuilder serializer(Class<? extends WriterSerializer>
value) {
+ return set(CONFIG_serializer, value);
+ }
+
+ /**
+ * Configuration property: 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 (for method chaining).
+ */
+ public ConfigBuilder parser(ReaderParser value) {
+ return set(CONFIG_parser, value);
+ }
+
+ /**
+ * Configuration property: 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 (for method chaining).
+ */
+ public ConfigBuilder parser(Class<? extends ReaderParser> value) {
+ return set(CONFIG_parser, value);
+ }
+
+ /**
+ * Configuration property: Value encoder.
+ *
+ * <p>
+ * The encoder to use for encoding encoded configuration values.
+ *
+ * @param value
+ * The new value for this property.
+ * <br>The default is {@link XorEncoder#INSTANCE}.
+ * @return This object (for method chaining).
+ */
+ public ConfigBuilder encoder(Encoder value) {
+ return set(CONFIG_encoder, value);
+ }
+
+ /**
+ * Configuration property: Value encoder.
+ *
+ * <p>
+ * The encoder to use for encoding encoded configuration values.
+ *
+ * @param value
+ * The new value for this property.
+ * <br>The default is {@link XorEncoder#INSTANCE}.
+ * @return This object (for method chaining).
+ */
+ public ConfigBuilder encoder(Class<? extends Encoder> value) {
+ return set(CONFIG_encoder, value);
+ }
+
+ /**
+ * Configuration property: 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 (for method chaining).
+ */
+ public ConfigBuilder varResolver(VarResolver value) {
+ return set(CONFIG_varResolver, value);
+ }
+
+ /**
+ * Configuration property: 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 (for method chaining).
+ */
+ public ConfigBuilder varResolver(Class<? extends VarResolver> value) {
+ return set(CONFIG_varResolver, value);
+ }
+
+ /**
+ * Configuration property: Binary value line length.
+ *
+ * <p>
+ * When serializing binary values, lines will be split after this many
characters.
+ * <br>Use <code>-1</code> to represent no line splitting.
+ *
+ * @param value
+ * The new value for this property.
+ * <br>The default is <code>-1</code>.
+ * @return This object (for method chaining).
+ */
+ public ConfigBuilder binaryLineLength(int value) {
+ return set(CONFIG_binaryLineLength, value);
+ }
+
+ /**
+ * Configuration property: Binary value format.
+ *
+ * <p>
+ * The format to use when persisting byte arrays.
+ *
+ * <p>
+ * Possible values:
+ * <ul>
+ * <li><js>"BASE64"</js> - BASE64-encoded string.
+ * <li><js>"HEX"</js> - Hexadecimal.
+ * <li><js>"SPACED_HEX"</js> - Hexadecimal with spaces between
bytes.
+ * </ul>
+ *
+ * @param value
+ * The new value for this property.
+ * <br>The default is <js>"BASE64"</js>.
+ * @return This object (for method chaining).
+ */
+ public ConfigBuilder binaryFormat(String value) {
+ return set(CONFIG_binaryFormat, value);
+ }
+
+ /**
+ * Configuration property: Beans on separate lines.
+ *
+ * <p>
+ * When enabled, serialized POJOs will be placed on a separate line
from the key.
+ *
+ * @param value
+ * The new value for this property.
+ * <br>The default is <jk>false</jk>.
+ * @return This object (for method chaining).
+ */
+ public ConfigBuilder beansOnSeparateLines(boolean value) {
+ return set(CONFIG_beansOnSeparateLines, value);
+ }
+
+
+ @Override /* ContextBuilder */
+ public ConfigBuilder set(String name, Object value) {
+ super.set(name, value);
+ return this;
+ }
+
+ @Override /* ContextBuilder */
+ public ConfigBuilder set(boolean append, String name, Object value) {
+ super.set(append, name, value);
+ return this;
+ }
+
+ @Override /* ContextBuilder */
+ public ConfigBuilder set(Map<String,Object> properties) {
+ super.set(properties);
+ return this;
+ }
+
+ @Override /* ContextBuilder */
+ public ConfigBuilder add(Map<String,Object> properties) {
+ super.add(properties);
+ return this;
+ }
+
+ @Override /* ContextBuilder */
+ public ConfigBuilder addTo(String name, Object value) {
+ super.addTo(name, value);
+ return this;
+ }
+
+ @Override /* ContextBuilder */
+ public ConfigBuilder addTo(String name, String key, Object value) {
+ super.addTo(name, key, value);
+ return this;
+ }
+
+ @Override /* ContextBuilder */
+ public ConfigBuilder removeFrom(String name, Object value) {
+ super.removeFrom(name, value);
+ return this;
+ }
+
+ @Override /* ContextBuilder */
+ public ConfigBuilder apply(PropertyStore copyFrom) {
+ super.apply(copyFrom);
+ return this;
+ }
+}
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/proto/ConfigMod.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/proto/ConfigMod.java
new file mode 100644
index 0000000..ff1a46e
--- /dev/null
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/proto/ConfigMod.java
@@ -0,0 +1,103 @@
+//
***************************************************************************************************************************
+// * 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.proto;
+
+import java.util.*;
+
+import org.apache.juneau.internal.*;
+import org.apache.juneau.config.encode.*;
+
+/**
+ * Identifies the supported modification types for config entries.
+ */
+public enum ConfigMod {
+
+ /**
+ * Encoded using the registered {@link Encoder}.
+ */
+ ENCODED("*");
+
+ private final String c;
+
+ private ConfigMod(String c) {
+ this.c = c;
+ }
+
+ /**
+ * Converts an array of modifiers to a modifier string.
+ *
+ * @param mods The modifiers.
+ * @return A modifier string, or an empty string if there are no
modifiers.
+ */
+ public static String asString(ConfigMod...mods) {
+ if (mods.length == 0)
+ return "";
+ if (mods.length == 1)
+ return mods[0].c;
+ StringBuilder sb = new StringBuilder(mods.length);
+ for (ConfigMod m : mods)
+ sb.append(m.c);
+ return sb.toString();
+ }
+
+ private static ConfigMod fromChar(char c) {
+ if (c == '*')
+ return ENCODED;
+ return null;
+ }
+
+ /**
+ * Converts a modifier string (e.g. <js>"^*"</js>) into a list of
{@link ConfigMod Modifiers}
+ * in reverse order of how they appear in the string.
+ *
+ * @param s The modifier string.
+ * @return The list of modifiers, or an empty list if the string is
empty or <jk>null</jk>.
+ */
+ public static List<ConfigMod> asModifiersReverse(String s) {
+ if (StringUtils.isEmpty(s))
+ return Collections.emptyList();
+ if (s.length() == 1) {
+ ConfigMod m = fromChar(s.charAt(0));
+ return m == null ? Collections.<ConfigMod>emptyList() :
Collections.singletonList(m);
+ }
+ List<ConfigMod> l = new ArrayList<>(s.length());
+ for (int i = s.length()-1; i >= 0; i--) {
+ ConfigMod m = fromChar(s.charAt(i));
+ if (m != null)
+ l.add(m);
+ }
+ return l;
+ }
+
+ /**
+ * Converts a modifier string (e.g. <js>"^*"</js>) into a list of
{@link ConfigMod Modifiers}.
+ *
+ * @param s The modifier string.
+ * @return The list of modifiers, or an empty list if the string is
empty or <jk>null</jk>.
+ */
+ public static List<ConfigMod> asModifiers(String s) {
+ if (StringUtils.isEmpty(s))
+ return Collections.emptyList();
+ if (s.length() == 1) {
+ ConfigMod m = fromChar(s.charAt(0));
+ return m == null ? Collections.<ConfigMod>emptyList() :
Collections.singletonList(m);
+ }
+ List<ConfigMod> l = new ArrayList<>(s.length());
+ for (int i = 0; i < s.length(); i++) {
+ ConfigMod m = fromChar(s.charAt(i));
+ if (m != null)
+ l.add(m);
+ }
+ return l;
+ }
+}
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigEntry.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigEntry.java
index 2c4dbe8..7264d64 100644
---
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigEntry.java
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigEntry.java
@@ -109,34 +109,43 @@ public class ConfigEntry {
public boolean hasModifier(char m) {
return modifiers.indexOf(m) != -1;
}
+
+ /**
+ * Returns the modifiers for this entry.
+ *
+ * @return The modifiers for this entry, or an empty string if it has
no modifiers.
+ */
+ public String getModifiers() {
+ return modifiers;
+ }
- Writer writeTo(Writer out) throws IOException {
+ Writer writeTo(Writer w) throws IOException {
if (value == null)
- return out;
+ return w;
for (String pl : preLines)
- out.append(pl).append('\n');
+ w.append(pl).append('\n');
if (rawLine != null) {
String l = rawLine;
if (l.indexOf('\n') != -1)
l = l.replaceAll("(\\r?\\n)", "$1\t");
- out.append(l).append('\n');
+ w.append(l).append('\n');
} else {
- out.append(key);
- out.append(modifiers);
- out.append(" = ");
+ w.append(key);
+ w.append(modifiers);
+ w.append(" = ");
String val = value;
if (val.indexOf('\n') != -1)
val = val.replaceAll("(\\r?\\n)", "$1\t");
if (val.indexOf('#') != -1)
val = val.replaceAll("#", "\\\\#");
- out.append(val);
+ w.append(val);
if (comment != null)
- out.append(" # ").append(comment);
+ w.append(" # ").append(comment);
- out.append('\n');
+ w.append('\n');
}
- return out;
+ return w;
}
}
\ No newline at end of file
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigMap.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigMap.java
index f83388e..4756127 100644
---
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigMap.java
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/store/ConfigMap.java
@@ -21,12 +21,13 @@ import java.util.concurrent.locks.*;
import org.apache.juneau.*;
import org.apache.juneau.config.event.*;
+import org.apache.juneau.http.*;
import org.apache.juneau.internal.*;
/**
* Represents the parsed contents of a configuration.
*/
-public class ConfigMap implements StoreListener {
+public class ConfigMap implements StoreListener, Writable {
private final Store store; // The store that created this
object.
private volatile String contents; // The original contents of
this object.
@@ -210,6 +211,19 @@ public class ConfigMap implements StoreListener {
}
}
+ /**
+ * Returns the keys of the entries in the specified section.
+ *
+ * @param section
+ * The section name.
+ * <br>Must not be <jk>null</jk>.
+ * @return
+ * An unmodifiable set of keys, or <jk>null</jk> if the section
doesn't exist.
+ */
+ public Set<String> getKeys(String section) {
+ ConfigSection cs = entries.get(section);
+ return cs == null ? null :
Collections.unmodifiableSet(cs.entries.keySet());
+ }
//-----------------------------------------------------------------------------------------------------------------
// Setters
@@ -242,7 +256,7 @@ public class ConfigMap implements StoreListener {
public ConfigMap removeSection(String section) {
return applyChange(true, ChangeEvent.removeSection(section));
}
-
+
/**
* Sets the pre-lines on an entry without modifying any other
attributes.
*
@@ -433,7 +447,7 @@ public class ConfigMap implements StoreListener {
* @param listener The new listener.
* @return This object (for method chaining).
*/
- public ConfigMap registerListener(ChangeEventListener listener) {
+ public ConfigMap register(ChangeEventListener listener) {
listeners.add(listener);
return this;
}
@@ -444,7 +458,7 @@ public class ConfigMap implements StoreListener {
* @param listener The listener to remove.
* @return This object (for method chaining).
*/
- public ConfigMap unregisterListener(ChangeEventListener listener) {
+ public ConfigMap unregister(ChangeEventListener listener) {
listeners.remove(listener);
return this;
}
@@ -469,9 +483,86 @@ public class ConfigMap implements StoreListener {
signal(changes);
}
+ @Override /* Object */
+ public String toString() {
+ readLock();
+ try {
+ return asString();
+ } finally {
+ readUnlock();
+ }
+ }
+
+ @Override /* Writable */
+ public Writer writeTo(Writer w) throws IOException {
+ for (ConfigSection cs : entries.values())
+ cs.writeTo(w);
+ return w;
+ }
+
+ @Override /* Writable */
+ public MediaType getMediaType() {
+ return MediaType.PLAIN;
+ }
+
+
+
//--------------------------------------------------------------------------------
+ // Private methods
+
//--------------------------------------------------------------------------------
+
+ private void readLock() {
+ lock.readLock().lock();
+ }
+
+ private void readUnlock() {
+ lock.readLock().unlock();
+ }
+
+ private void writeLock() {
+ lock.writeLock().lock();
+ }
+
+ private void writeUnlock() {
+ lock.writeLock().unlock();
+ }
+
+ private boolean isValidSectionName(String s) {
+ return "default".equals(s) || isValidNewSectionName(s);
+ }
+
+ private boolean isValidKeyName(String s) {
+ if (s == null)
+ return false;
+ s = s.trim();
+ if (s.isEmpty())
+ return false;
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c == '/' || c == '\\' || c == '[' || c == ']' || c
== '=' || c == '#')
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isValidNewSectionName(String s) {
+ if (s == null)
+ return false;
+ s = s.trim();
+ if (s.isEmpty())
+ return false;
+ if ("default".equals(s))
+ return false;
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c == '/' || c == '\\' || c == '[' || c == ']')
+ return false;
+ }
+ return true;
+ }
+
private void signal(List<ChangeEvent> changes) {
for (ChangeEventListener l : listeners)
- l.onEvents(changes);
+ l.onChange(changes);
}
private List<ChangeEvent> findDiffs(String updatedContents) {
@@ -510,6 +601,18 @@ public class ConfigMap implements StoreListener {
return changes;
}
+ // This method should only be called from behind a lock.
+ private String asString() {
+ try {
+ StringWriter sw = new StringWriter();
+ for (ConfigSection cs : entries.values())
+ cs.writeTo(sw);
+ return sw.toString();
+ } catch (IOException e) {
+ throw new RuntimeException(e); // Not possible.
+ }
+ }
+
//---------------------------------------------------------------------------------------------
// ConfigSection
@@ -586,97 +689,22 @@ public class ConfigMap implements StoreListener {
return this;
}
- Writer writeTo(Writer out) throws IOException {
+ Writer writeTo(Writer w) throws IOException {
for (String s : preLines)
- out.append(s).append('\n');
+ w.append(s).append('\n');
if (! name.equals("default"))
- out.append(rawLine).append('\n');
+ w.append(rawLine).append('\n');
else {
// Need separation between default prelines and
first-entry prelines.
if (! preLines.isEmpty())
- out.append('\n');
+ w.append('\n');
}
for (ConfigEntry e : entries.values())
- e.writeTo(out);
+ e.writeTo(w);
- return out;
- }
- }
-
- @Override /* Object */
- public String toString() {
- readLock();
- try {
- return asString();
- } finally {
- readUnlock();
+ return w;
}
}
-
- String asString() {
- try {
- StringWriter sw = new StringWriter();
- for (ConfigSection cs : entries.values())
- cs.writeTo(sw);
- return sw.toString();
- } catch (IOException e) {
- throw new RuntimeException(e); // Not possible.
- }
- }
-
- private boolean isValidNewSectionName(String s) {
- if (s == null)
- return false;
- s = s.trim();
- if (s.isEmpty())
- return false;
- if ("default".equals(s))
- return false;
- for (int i = 0; i < s.length(); i++) {
- char c = s.charAt(i);
- if (c == '/' || c == '\\' || c == '[' || c == ']')
- return false;
- }
- return true;
- }
-
- private boolean isValidSectionName(String s) {
- return "default".equals(s) || isValidNewSectionName(s);
- }
-
- private boolean isValidKeyName(String s) {
- if (s == null)
- return false;
- s = s.trim();
- if (s.isEmpty())
- return false;
- for (int i = 0; i < s.length(); i++) {
- char c = s.charAt(i);
- if (c == '/' || c == '\\' || c == '[' || c == ']' || c
== '=' || c == '#')
- return false;
- }
- return true;
- }
-
-
//--------------------------------------------------------------------------------
- // Private methods
-
//--------------------------------------------------------------------------------
-
- void readLock() {
- lock.readLock().lock();
- }
-
- void readUnlock() {
- lock.readLock().unlock();
- }
-
- void writeLock() {
- lock.writeLock().lock();
- }
-
- void writeUnlock() {
- lock.writeLock().unlock();
- }
}
diff --git
a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/config/proto/ConfigMapListenerTest.java
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/config/proto/ConfigMapListenerTest.java
index bec142e..5e575d3 100644
---
a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/config/proto/ConfigMapListenerTest.java
+++
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/config/proto/ConfigMapListenerTest.java
@@ -45,12 +45,12 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
cm.setValue("default", "foo", "baz");
cm.save();
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("foo = baz|", cm.toString());
}
@@ -72,12 +72,12 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
cm.setValue("S1", "foo", "baz");
cm.save();
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("[S1]|foo = baz|", cm.toString());
}
@@ -101,13 +101,13 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
cm.setValue("default", "k", "vb");
cm.setValue("S1", "k1", "v1b");
cm.save();
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("k = vb|[S1]|k1 = v1b|", cm.toString());
}
@@ -127,13 +127,13 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
cm.setEntry("default", "k", "kb", "^*", "C",
Arrays.asList("#k"));
cm.setEntry("S1", "k1", "k1b", "^*", "C1",
Arrays.asList("#k1"));
cm.save();
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("#k|k^* = kb # C|[S1]|#k1|k1^* = k1b # C1|",
cm.toString());
}
@@ -159,13 +159,13 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
cm.setEntry("default", "k", "kb", "^*", "Cb",
Arrays.asList("#kb"));
cm.setEntry("S1", "k1", "k1b", "^*", "Cb1",
Arrays.asList("#k1b"));
cm.save();
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("#kb|k^* = kb # Cb|#S1|[S1]|#k1b|k1^* = k1b #
Cb1|", cm.toString());
}
@@ -192,13 +192,13 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
cm.setValue("default", "k", null);
cm.setValue("S1", "k1", null);
cm.save();
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("[S1]|", cm.toString());
}
@@ -224,13 +224,13 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
cm.setValue("default", "k", null);
cm.setValue("S1", "k1", null);
cm.save();
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("#S1|[S1]|", cm.toString());
}
@@ -254,7 +254,7 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
cm.setSection("default", Arrays.asList("#D1"));
cm.setSection("S1", Arrays.asList("#S1"));
cm.setSection("S2", null);
@@ -263,7 +263,7 @@ public class ConfigMapListenerTest {
cm.save();
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("#D1||#S1|[S1]|[S2]|[S3]|k3 = v3|",
cm.toString());
}
@@ -289,7 +289,7 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
cm.setSection("default", Arrays.asList("#Db"));
cm.setSection("S1", Arrays.asList("#S1b"));
cm.setSection("S2", null);
@@ -298,7 +298,7 @@ public class ConfigMapListenerTest {
cm.save();
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("#Db||#S1b|[S1]|[S2]|[S3]|k3 = v3|",
cm.toString());
}
@@ -334,7 +334,7 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
cm.removeSection("default");
cm.removeSection("S1");
cm.removeSection("S2");
@@ -342,7 +342,7 @@ public class ConfigMapListenerTest {
cm.save();
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("", cm.toString());
}
@@ -365,7 +365,7 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
s.update("Foo",
"#Da",
"",
@@ -382,7 +382,7 @@ public class ConfigMapListenerTest {
);
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("#Da||k = v # cv||#S1|[S1]|#k1|k1 = v1 #
cv1|[S2]|#k2|k2 = v2 # cv2|[S3]|", cm.toString());
}
@@ -411,7 +411,7 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
cm.setValue("S2", "k2", "v2b");
s.update("Foo",
"[S1]",
@@ -420,7 +420,7 @@ public class ConfigMapListenerTest {
cm.save();
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("[S1]|k1 = v1b|[S2]|k2 = v2b|", cm.toString());
}
@@ -449,7 +449,7 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
cm.setValue("S1", "k1", "v1c");
s.update("Foo",
"[S1]",
@@ -458,7 +458,7 @@ public class ConfigMapListenerTest {
cm.save();
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("[S1]|k1 = v1c|", cm.toString());
}
@@ -495,12 +495,12 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
cm.setValue("S1", "k1", "v1c");
cm.save();
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("[S1]|k1 = v1c|", cm.toString());
@@ -540,7 +540,7 @@ public class ConfigMapListenerTest {
};
ConfigMap cm = s.getMap("Foo");
- cm.registerListener(l);
+ cm.register(l);
cm.setValue("S1", "k1", "v1c");
try {
cm.save();
@@ -550,7 +550,7 @@ public class ConfigMapListenerTest {
}
wait(latch);
assertNull(l.error);
- cm.unregisterListener(l);
+ cm.unregister(l);
assertTextEquals("[S1]|k1 = v1c|", cm.toString());
@@ -575,7 +575,7 @@ public class ConfigMapListenerTest {
}
@Override
- public void onEvents(List<ChangeEvent> events) {
+ public void onChange(List<ChangeEvent> events) {
try {
check(events);
} catch (Exception e) {
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Writable.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Writable.java
index bf507c8..fe926c0 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Writable.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Writable.java
@@ -28,9 +28,10 @@ public interface Writable {
* Serialize this object to the specified writer.
*
* @param w The writer to write to.
+ * @return The same writer passed in.
* @throws IOException
*/
- void writeTo(Writer w) throws IOException;
+ Writer writeTo(Writer w) throws IOException;
/**
* Returns the serialized media type for this resource (e.g.
<js>"text/html"</js>)
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
index b71a6a7..624d9b3 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
@@ -1579,6 +1579,8 @@ public final class StringUtils {
public static List<String> splitEqually(String s, int size) {
if (s == null)
return null;
+ if (size <= 0)
+ return Collections.singletonList(s);
List<String> l = new ArrayList<>((s.length() + size - 1) /
size);
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StringMessage.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StringMessage.java
index 17ed8fa..58ea67d 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StringMessage.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StringMessage.java
@@ -45,9 +45,9 @@ public class StringMessage implements CharSequence, Writable {
}
@Override /* Writable */
- public void writeTo(Writer w) throws IOException {
+ public Writer writeTo(Writer w) throws IOException {
w.write(toString());
-
+ return w;
}
@Override /* Writable */
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StringObject.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StringObject.java
index 2982db6..18546ab 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StringObject.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StringObject.java
@@ -83,9 +83,10 @@ public class StringObject implements CharSequence, Writable {
}
@Override /* Writable */
- public void writeTo(Writer w) throws IOException {
+ public Writer writeTo(Writer w) throws IOException {
try {
s.serialize(o, w);
+ return w;
} catch (SerializeException e) {
throw new IOException(e);
}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ReaderResource.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ReaderResource.java
index 22275ad..c235cbd 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ReaderResource.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ReaderResource.java
@@ -114,13 +114,14 @@ public class ReaderResource implements Writable {
}
@Override /* Writeable */
- public void writeTo(Writer w) throws IOException {
+ public Writer writeTo(Writer w) throws IOException {
for (String s : contents) {
if (varSession != null)
varSession.resolveTo(s, w);
else
w.write(s);
}
+ return w;
}
@Override /* Writeable */
--
To stop receiving notification emails like this one, please contact
[email protected].