Merge pull request #156 from jboner/wip-1504-config-comments-patriknw

Rewrite config comments
This commit is contained in:
patriknw 2011-12-14 08:47:19 -08:00
commit d9e9efe2d7
46 changed files with 883 additions and 455 deletions

View file

@ -21,7 +21,7 @@ import java.util.Set;
* is a key in a JSON object; it's just a string that's the key in a map. A * is a key in a JSON object; it's just a string that's the key in a map. A
* "path" is a parseable expression with a syntax and it refers to a series of * "path" is a parseable expression with a syntax and it refers to a series of
* keys. Path expressions are described in the <a * keys. Path expressions are described in the <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">spec for * href="https://github.com/typesafehub/config/blob/master/HOCON.md">spec for
* Human-Optimized Config Object Notation</a>. In brief, a path is * Human-Optimized Config Object Notation</a>. In brief, a path is
* period-separated so "a.b.c" looks for key c in object b in object a in the * period-separated so "a.b.c" looks for key c in object b in object a in the
* root object. Sometimes double quotes are needed around special characters in * root object. Sometimes double quotes are needed around special characters in
@ -97,7 +97,7 @@ public interface Config extends ConfigMergeable {
/** /**
* Returns a replacement config with all substitutions (the * Returns a replacement config with all substitutions (the
* <code>${foo.bar}</code> syntax, see <a * <code>${foo.bar}</code> syntax, see <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">the * href="https://github.com/typesafehub/config/blob/master/HOCON.md">the
* spec</a>) resolved. Substitutions are looked up using this * spec</a>) resolved. Substitutions are looked up using this
* <code>Config</code> as the root object, that is, a substitution * <code>Config</code> as the root object, that is, a substitution
* <code>${foo.bar}</code> will be replaced with the result of * <code>${foo.bar}</code> will be replaced with the result of
@ -395,7 +395,8 @@ public interface Config extends ConfigMergeable {
* Gets a value as a size in bytes (parses special strings like "128M"). If * Gets a value as a size in bytes (parses special strings like "128M"). If
* the value is already a number, then it's left alone; if it's a string, * the value is already a number, then it's left alone; if it's a string,
* it's parsed understanding unit suffixes such as "128K", as documented in * it's parsed understanding unit suffixes such as "128K", as documented in
* the <a href="https://github.com/havocp/config/blob/master/HOCON.md">the * the <a
* href="https://github.com/typesafehub/config/blob/master/HOCON.md">the
* spec</a>. * spec</a>.
* *
* @param path * @param path
@ -414,9 +415,9 @@ public interface Config extends ConfigMergeable {
* Get value as a duration in milliseconds. If the value is already a * Get value as a duration in milliseconds. If the value is already a
* number, then it's left alone; if it's a string, it's parsed understanding * number, then it's left alone; if it's a string, it's parsed understanding
* units suffixes like "10m" or "5ns" as documented in the <a * units suffixes like "10m" or "5ns" as documented in the <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">the * href="https://github.com/typesafehub/config/blob/master/HOCON.md">the
* spec</a>. * spec</a>.
* *
* @param path * @param path
* path expression * path expression
* @return the duration value at the requested path, in milliseconds * @return the duration value at the requested path, in milliseconds

View file

@ -5,7 +5,8 @@ package com.typesafe.config;
/** /**
* All exceptions thrown by the library are subclasses of ConfigException. * All exceptions thrown by the library are subclasses of
* <code>ConfigException</code>.
*/ */
public abstract class ConfigException extends RuntimeException { public abstract class ConfigException extends RuntimeException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -338,6 +339,9 @@ public abstract class ConfigException extends RuntimeException {
sb.append(p.problem()); sb.append(p.problem());
sb.append(", "); sb.append(", ");
} }
if (sb.length() == 0)
throw new ConfigException.BugOrBroken(
"ValidationFailed must have a non-empty list of problems");
sb.setLength(sb.length() - 2); // chop comma and space sb.setLength(sb.length() - 2); // chop comma and space
return sb.toString(); return sb.toString();

View file

@ -295,18 +295,18 @@ public final class ConfigFactory {
/** /**
* Converts a Java {@link java.util.Properties} object to a * Converts a Java {@link java.util.Properties} object to a
* {@link ConfigObject} using the rules documented in the <a * {@link ConfigObject} using the rules documented in the <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON * href="https://github.com/typesafehub/config/blob/master/HOCON.md">HOCON
* spec</a>. The keys in the <code>Properties</code> object are split on the * spec</a>. The keys in the <code>Properties</code> object are split on the
* period character '.' and treated as paths. The values will all end up as * period character '.' and treated as paths. The values will all end up as
* string values. If you have both "a=foo" and "a.b=bar" in your properties * string values. If you have both "a=foo" and "a.b=bar" in your properties
* file, so "a" is both the object containing "b" and the string "foo", then * file, so "a" is both the object containing "b" and the string "foo", then
* the string value is dropped. * the string value is dropped.
* *
* <p> * <p>
* If you want to have <code>System.getProperties()</code> as a * If you want to have <code>System.getProperties()</code> as a
* ConfigObject, it's better to use the {@link #systemProperties()} method * ConfigObject, it's better to use the {@link #systemProperties()} method
* which returns a cached global singleton. * which returns a cached global singleton.
* *
* @param properties * @param properties
* a Java Properties object * a Java Properties object
* @param options * @param options

View file

@ -24,19 +24,19 @@ public interface ConfigMergeable {
* method (they need to merge the fallback keys into themselves). All other * method (they need to merge the fallback keys into themselves). All other
* values just return the original value, since they automatically override * values just return the original value, since they automatically override
* any fallback. * any fallback.
* *
* <p> * <p>
* The semantics of merging are described in the <a * The semantics of merging are described in the <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">spec for * href="https://github.com/typesafehub/config/blob/master/HOCON.md">spec
* HOCON</a>. * for HOCON</a>.
* *
* <p> * <p>
* Note that objects do not merge "across" non-objects; if you write * Note that objects do not merge "across" non-objects; if you write
* <code>object.withFallback(nonObject).withFallback(otherObject)</code>, * <code>object.withFallback(nonObject).withFallback(otherObject)</code>,
* then <code>otherObject</code> will simply be ignored. This is an * then <code>otherObject</code> will simply be ignored. This is an
* intentional part of how merging works. Both non-objects, and any object * intentional part of how merging works. Both non-objects, and any object
* which has fallen back to a non-object, block subsequent fallbacks. * which has fallen back to a non-object, block subsequent fallbacks.
* *
* @param other * @param other
* an object whose keys should be used if the keys are not * an object whose keys should be used if the keys are not
* present in this one * present in this one

View file

@ -8,38 +8,38 @@ import java.util.Map;
/** /**
* Subtype of {@link ConfigValue} representing an object (dictionary, map) * Subtype of {@link ConfigValue} representing an object (dictionary, map)
* value, as in JSON's <code>{ "a" : 42 }</code> syntax. * value, as in JSON's <code>{ "a" : 42 }</code> syntax.
* *
* <p> * <p>
* {@code ConfigObject} implements {@code java.util.Map<String, ConfigValue>} so * {@code ConfigObject} implements {@code java.util.Map<String, ConfigValue>} so
* you can use it like a regular Java map. Or call {@link #unwrapped()} to * you can use it like a regular Java map. Or call {@link #unwrapped()} to
* unwrap the map to a map with plain Java values rather than * unwrap the map to a map with plain Java values rather than
* {@code ConfigValue}. * {@code ConfigValue}.
* *
* <p> * <p>
* Like all {@link ConfigValue} subtypes, {@code ConfigObject} is immutable. * Like all {@link ConfigValue} subtypes, {@code ConfigObject} is immutable.
* This makes it threadsafe and you never have to create "defensive copies." The * This makes it threadsafe and you never have to create "defensive copies." The
* mutator methods from {@link java.util.Map} all throw * mutator methods from {@link java.util.Map} all throw
* {@link java.lang.UnsupportedOperationException}. * {@link java.lang.UnsupportedOperationException}.
* *
* <p> * <p>
* The {@link ConfigValue#valueType} method on an object returns * The {@link ConfigValue#valueType} method on an object returns
* {@link ConfigValueType#OBJECT}. * {@link ConfigValueType#OBJECT}.
* *
* <p> * <p>
* In most cases you want to use the {@link Config} interface rather than this * In most cases you want to use the {@link Config} interface rather than this
* one. Call {@link #toConfig()} to convert a {@code ConfigObject} to a * one. Call {@link #toConfig()} to convert a {@code ConfigObject} to a
* {@code Config}. * {@code Config}.
* *
* <p> * <p>
* The API for a {@code ConfigObject} is in terms of keys, while the API for a * The API for a {@code ConfigObject} is in terms of keys, while the API for a
* {@link Config} is in terms of path expressions. Conceptually, * {@link Config} is in terms of path expressions. Conceptually,
* {@code ConfigObject} is a tree of maps from keys to values, while a * {@code ConfigObject} is a tree of maps from keys to values, while a
* {@code ConfigObject} is a one-level map from paths to values. * {@code Config} is a one-level map from paths to values.
* *
* <p> * <p>
* Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert * Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert
* between path expressions and individual path elements (keys). * between path expressions and individual path elements (keys).
* *
* <p> * <p>
* A {@code ConfigObject} may contain null values, which will have * A {@code ConfigObject} may contain null values, which will have
* {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If * {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If
@ -47,7 +47,7 @@ import java.util.Map;
* file (or wherever this value tree came from). If {@code get()} returns a * file (or wherever this value tree came from). If {@code get()} returns a
* {@link ConfigValue} with type {@code ConfigValueType#NULL} then the key was * {@link ConfigValue} with type {@code ConfigValueType#NULL} then the key was
* set to null explicitly in the config file. * set to null explicitly in the config file.
* *
* <p> * <p>
* <em>Do not implement {@code ConfigObject}</em>; it should only be implemented * <em>Do not implement {@code ConfigObject}</em>; it should only be implemented
* by the config library. Arbitrary implementations will not work because the * by the config library. Arbitrary implementations will not work because the

View file

@ -4,6 +4,7 @@
package com.typesafe.config; package com.typesafe.config;
import java.net.URL; import java.net.URL;
import java.util.List;
/** /**
@ -12,13 +13,13 @@ import java.net.URL;
* with {@link ConfigValue#origin}. Exceptions may have an origin, see * with {@link ConfigValue#origin}. Exceptions may have an origin, see
* {@link ConfigException#origin}, but be careful because * {@link ConfigException#origin}, but be careful because
* <code>ConfigException.origin()</code> may return null. * <code>ConfigException.origin()</code> may return null.
* *
* <p> * <p>
* It's best to use this interface only for debugging; its accuracy is * It's best to use this interface only for debugging; its accuracy is
* "best effort" rather than guaranteed, and a potentially-noticeable amount of * "best effort" rather than guaranteed, and a potentially-noticeable amount of
* memory could probably be saved if origins were not kept around, so in the * memory could probably be saved if origins were not kept around, so in the
* future there might be some option to discard origins. * future there might be some option to discard origins.
* *
* <p> * <p>
* <em>Do not implement this interface</em>; it should only be implemented by * <em>Do not implement this interface</em>; it should only be implemented by
* the config library. Arbitrary implementations will not work because the * the config library. Arbitrary implementations will not work because the
@ -66,4 +67,16 @@ public interface ConfigOrigin {
* @return line number or -1 if none is available * @return line number or -1 if none is available
*/ */
public int lineNumber(); public int lineNumber();
/**
* Returns any comments that appeared to "go with" this place in the file.
* Often an empty list, but never null. The details of this are subject to
* change, but at the moment comments that are immediately before an array
* element or object field, with no blank line after the comment, "go with"
* that element or field.
*
* @return any comments that seemed to "go with" this origin, empty list if
* none
*/
public List<String> comments();
} }

View file

@ -6,11 +6,13 @@ package com.typesafe.config;
/** /**
* A set of options related to resolving substitutions. Substitutions use the * A set of options related to resolving substitutions. Substitutions use the
* <code>${foo.bar}</code> syntax and are documented in the <a * <code>${foo.bar}</code> syntax and are documented in the <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON</a> spec. * href="https://github.com/typesafehub/config/blob/master/HOCON.md">HOCON</a>
* spec.
* <p> * <p>
* This object is immutable, so the "setters" return a new object. * This object is immutable, so the "setters" return a new object.
* <p> * <p>
* Here is an example of creating a custom {@code ConfigResolveOptions}: * Here is an example of creating a custom {@code ConfigResolveOptions}:
*
* <pre> * <pre>
* ConfigResolveOptions options = ConfigResolveOptions.defaults() * ConfigResolveOptions options = ConfigResolveOptions.defaults()
* .setUseSystemEnvironment(false) * .setUseSystemEnvironment(false)

View file

@ -5,8 +5,8 @@ package com.typesafe.config;
/** /**
* The syntax of a character stream, <a href="http://json.org">JSON</a>, <a * The syntax of a character stream, <a href="http://json.org">JSON</a>, <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON</a> aka * href="https://github.com/typesafehub/config/blob/master/HOCON.md">HOCON</a>
* ".conf", or <a href= * aka ".conf", or <a href=
* "http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29" * "http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29"
* >Java properties</a>. * >Java properties</a>.
* *
@ -19,8 +19,8 @@ public enum ConfigSyntax {
JSON, JSON,
/** /**
* The JSON-superset <a * The JSON-superset <a
* href="https://github.com/havocp/config/blob/master/HOCON.md">HOCON</a> * href="https://github.com/typesafehub/config/blob/master/HOCON.md"
* format. * >HOCON</a> format.
*/ */
CONF, CONF,
/** /**

View file

@ -4,6 +4,10 @@ import java.util.List;
import com.typesafe.config.impl.ConfigImplUtil; import com.typesafe.config.impl.ConfigImplUtil;
/**
* Contains static utility methods.
*
*/
public final class ConfigUtil { public final class ConfigUtil {
private ConfigUtil() { private ConfigUtil() {
@ -41,7 +45,7 @@ public final class ConfigUtil {
* elements as needed and then joining them separated by a period. A path * elements as needed and then joining them separated by a period. A path
* expression is usable with a {@link Config}, while individual path * expression is usable with a {@link Config}, while individual path
* elements are usable with a {@link ConfigObject}. * elements are usable with a {@link ConfigObject}.
* *
* @param elements * @param elements
* the keys in the path * the keys in the path
* @return a path expression * @return a path expression
@ -57,7 +61,7 @@ public final class ConfigUtil {
* and unquoting the individual path elements. A path expression is usable * and unquoting the individual path elements. A path expression is usable
* with a {@link Config}, while individual path elements are usable with a * with a {@link Config}, while individual path elements are usable with a
* {@link ConfigObject}. * {@link ConfigObject}.
* *
* @param path * @param path
* a path expression * a path expression
* @return the individual keys in the path * @return the individual keys in the path

View file

@ -8,9 +8,9 @@ import java.util.Map;
import com.typesafe.config.impl.ConfigImpl; import com.typesafe.config.impl.ConfigImpl;
/** /**
* This class holds some static factory methods for building ConfigValue. See * This class holds some static factory methods for building {@link ConfigValue}
* also ConfigFactory which has methods for parsing files and certain in-memory * instances. See also {@link ConfigFactory} which has methods for parsing files
* data structures. * and certain in-memory data structures.
*/ */
public final class ConfigValueFactory { public final class ConfigValueFactory {
private ConfigValueFactory() { private ConfigValueFactory() {

View file

@ -111,12 +111,12 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
return ConfigValueType.OBJECT; return ConfigValueType.OBJECT;
} }
protected abstract AbstractConfigObject newCopy(ResolveStatus status, protected abstract AbstractConfigObject newCopy(ResolveStatus status, boolean ignoresFallbacks,
boolean ignoresFallbacks); ConfigOrigin origin);
@Override @Override
protected AbstractConfigObject newCopy(boolean ignoresFallbacks) { protected AbstractConfigObject newCopy(boolean ignoresFallbacks, ConfigOrigin origin) {
return newCopy(resolveStatus(), ignoresFallbacks); return newCopy(resolveStatus(), ignoresFallbacks, origin);
} }
@Override @Override
@ -173,7 +173,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
return new SimpleConfigObject(mergeOrigins(this, fallback), merged, newResolveStatus, return new SimpleConfigObject(mergeOrigins(this, fallback), merged, newResolveStatus,
newIgnoresFallbacks); newIgnoresFallbacks);
else if (newResolveStatus != resolveStatus() || newIgnoresFallbacks != ignoresFallbacks()) else if (newResolveStatus != resolveStatus() || newIgnoresFallbacks != ignoresFallbacks())
return newCopy(newResolveStatus, newIgnoresFallbacks); return newCopy(newResolveStatus, newIgnoresFallbacks, origin());
else else
return this; return this;
} }
@ -234,7 +234,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
} }
} }
if (changes == null) { if (changes == null) {
return newCopy(newResolveStatus, ignoresFallbacks()); return newCopy(newResolveStatus, ignoresFallbacks(), origin());
} else { } else {
Map<String, AbstractConfigValue> modified = new HashMap<String, AbstractConfigValue>(); Map<String, AbstractConfigValue> modified = new HashMap<String, AbstractConfigValue>();
for (String k : keySet()) { for (String k : keySet()) {
@ -306,6 +306,12 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
sb.append("# "); sb.append("# ");
sb.append(v.origin().description()); sb.append(v.origin().description());
sb.append("\n"); sb.append("\n");
for (String comment : v.origin().comments()) {
indent(sb, indent + 1);
sb.append("# ");
sb.append(comment);
sb.append("\n");
}
indent(sb, indent + 1); indent(sb, indent + 1);
} }
v.render(sb, indent + 1, k, formatted); v.render(sb, indent + 1, k, formatted);

View file

@ -18,14 +18,14 @@ import com.typesafe.config.ConfigValue;
*/ */
abstract class AbstractConfigValue implements ConfigValue, MergeableValue { abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
final private ConfigOrigin origin; final private SimpleConfigOrigin origin;
AbstractConfigValue(ConfigOrigin origin) { AbstractConfigValue(ConfigOrigin origin) {
this.origin = origin; this.origin = (SimpleConfigOrigin) origin;
} }
@Override @Override
public ConfigOrigin origin() { public SimpleConfigOrigin origin() {
return this.origin; return this.origin;
} }
@ -76,9 +76,7 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
return this; return this;
} }
protected AbstractConfigValue newCopy(boolean ignoresFallbacks) { protected abstract AbstractConfigValue newCopy(boolean ignoresFallbacks, ConfigOrigin origin);
return this;
}
// this is virtualized rather than a field because only some subclasses // this is virtualized rather than a field because only some subclasses
// really need to store the boolean, and they may be able to pack it // really need to store the boolean, and they may be able to pack it
@ -105,6 +103,13 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
throw badMergeException(); throw badMergeException();
} }
public AbstractConfigValue withOrigin(ConfigOrigin origin) {
if (this.origin == origin)
return this;
else
return newCopy(ignoresFallbacks(), origin);
}
@Override @Override
public AbstractConfigValue withFallback(ConfigMergeable mergeable) { public AbstractConfigValue withFallback(ConfigMergeable mergeable) {
if (ignoresFallbacks()) { if (ignoresFallbacks()) {
@ -118,7 +123,7 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
AbstractConfigObject fallback = (AbstractConfigObject) other; AbstractConfigObject fallback = (AbstractConfigObject) other;
if (fallback.resolveStatus() == ResolveStatus.RESOLVED && fallback.isEmpty()) { if (fallback.resolveStatus() == ResolveStatus.RESOLVED && fallback.isEmpty()) {
if (fallback.ignoresFallbacks()) if (fallback.ignoresFallbacks())
return newCopy(true /* ignoresFallbacks */); return newCopy(true /* ignoresFallbacks */, origin);
else else
return this; return this;
} else { } else {
@ -128,7 +133,7 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
// falling back to a non-object doesn't merge anything, and also // falling back to a non-object doesn't merge anything, and also
// prohibits merging any objects that we fall back to later. // prohibits merging any objects that we fall back to later.
// so we have to switch to ignoresFallbacks mode. // so we have to switch to ignoresFallbacks mode.
return newCopy(true /* ignoresFallbacks */); return newCopy(true /* ignoresFallbacks */, origin);
} }
} }
} }

View file

@ -29,4 +29,9 @@ final class ConfigBoolean extends AbstractConfigValue {
String transformToString() { String transformToString() {
return value ? "true" : "false"; return value ? "true" : "false";
} }
@Override
protected ConfigBoolean newCopy(boolean ignoresFallbacks, ConfigOrigin origin) {
return new ConfigBoolean(origin, value);
}
} }

View file

@ -107,6 +107,11 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
return ignoresFallbacks; return ignoresFallbacks;
} }
@Override
protected AbstractConfigValue newCopy(boolean newIgnoresFallbacks, ConfigOrigin newOrigin) {
return new ConfigDelayedMerge(newOrigin, stack, newIgnoresFallbacks);
}
@Override @Override
protected final ConfigDelayedMerge mergedWithTheUnmergeable(Unmergeable fallback) { protected final ConfigDelayedMerge mergedWithTheUnmergeable(Unmergeable fallback) {
if (ignoresFallbacks) if (ignoresFallbacks)
@ -196,6 +201,12 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
i += 1; i += 1;
sb.append(v.origin().description()); sb.append(v.origin().description());
sb.append("\n"); sb.append("\n");
for (String comment : v.origin().comments()) {
indent(sb, indent);
sb.append("# ");
sb.append(comment);
sb.append("\n");
}
indent(sb, indent); indent(sb, indent);
} }

View file

@ -49,12 +49,12 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
} }
@Override @Override
protected ConfigDelayedMergeObject newCopy(ResolveStatus status, protected ConfigDelayedMergeObject newCopy(ResolveStatus status, boolean ignoresFallbacks,
boolean ignoresFallbacks) { ConfigOrigin origin) {
if (status != resolveStatus()) if (status != resolveStatus())
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken(
"attempt to create resolved ConfigDelayedMergeObject"); "attempt to create resolved ConfigDelayedMergeObject");
return new ConfigDelayedMergeObject(origin(), stack, ignoresFallbacks); return new ConfigDelayedMergeObject(origin, stack, ignoresFallbacks);
} }
@Override @Override

View file

@ -43,4 +43,9 @@ final class ConfigDouble extends ConfigNumber {
protected double doubleValue() { protected double doubleValue() {
return value; return value;
} }
@Override
protected ConfigDouble newCopy(boolean ignoresFallbacks, ConfigOrigin origin) {
return new ConfigDouble(origin, value, originalText);
}
} }

View file

@ -43,4 +43,9 @@ final class ConfigInt extends ConfigNumber {
protected double doubleValue() { protected double doubleValue() {
return value; return value;
} }
@Override
protected ConfigInt newCopy(boolean ignoresFallbacks, ConfigOrigin origin) {
return new ConfigInt(origin, value, originalText);
}
} }

View file

@ -43,4 +43,9 @@ final class ConfigLong extends ConfigNumber {
protected double doubleValue() { protected double doubleValue() {
return value; return value;
} }
@Override
protected ConfigLong newCopy(boolean ignoresFallbacks, ConfigOrigin origin) {
return new ConfigLong(origin, value, originalText);
}
} }

View file

@ -39,4 +39,9 @@ final class ConfigNull extends AbstractConfigValue {
protected void render(StringBuilder sb, int indent, boolean formatted) { protected void render(StringBuilder sb, int indent, boolean formatted) {
sb.append("null"); sb.append("null");
} }
@Override
protected ConfigNull newCopy(boolean ignoresFallbacks, ConfigOrigin origin) {
return new ConfigNull(origin);
}
} }

View file

@ -11,7 +11,7 @@ abstract class ConfigNumber extends AbstractConfigValue {
// a sentence) we always have it exactly as the person typed it into the // a sentence) we always have it exactly as the person typed it into the
// config file. It's purely cosmetic; equals/hashCode don't consider this // config file. It's purely cosmetic; equals/hashCode don't consider this
// for example. // for example.
final private String originalText; final protected String originalText;
protected ConfigNumber(ConfigOrigin origin, String originalText) { protected ConfigNumber(ConfigOrigin origin, String originalText) {
super(origin); super(origin);

View file

@ -34,4 +34,9 @@ final class ConfigString extends AbstractConfigValue {
protected void render(StringBuilder sb, int indent, boolean formatted) { protected void render(StringBuilder sb, int indent, boolean formatted) {
sb.append(ConfigImplUtil.renderJsonString(value)); sb.append(ConfigImplUtil.renderJsonString(value));
} }
@Override
protected ConfigString newCopy(boolean ignoresFallbacks, ConfigOrigin origin) {
return new ConfigString(origin, value);
}
} }

View file

@ -61,8 +61,8 @@ final class ConfigSubstitution extends AbstractConfigValue implements
} }
@Override @Override
protected ConfigSubstitution newCopy(boolean ignoresFallbacks) { protected ConfigSubstitution newCopy(boolean ignoresFallbacks, ConfigOrigin newOrigin) {
return new ConfigSubstitution(origin(), pieces, prefixLength, ignoresFallbacks); return new ConfigSubstitution(newOrigin, pieces, prefixLength, ignoresFallbacks);
} }
@Override @Override

View file

@ -32,9 +32,53 @@ final class Parser {
return context.parse(); return context.parse();
} }
static private final class TokenWithComments {
final Token token;
final List<Token> comments;
TokenWithComments(Token token, List<Token> comments) {
this.token = token;
this.comments = comments;
}
TokenWithComments(Token token) {
this(token, Collections.<Token> emptyList());
}
TokenWithComments prepend(List<Token> earlier) {
if (this.comments.isEmpty()) {
return new TokenWithComments(token, earlier);
} else {
List<Token> merged = new ArrayList<Token>();
merged.addAll(earlier);
merged.addAll(comments);
return new TokenWithComments(token, merged);
}
}
SimpleConfigOrigin setComments(SimpleConfigOrigin origin) {
if (comments.isEmpty()) {
return origin;
} else {
List<String> newComments = new ArrayList<String>();
for (Token c : comments) {
newComments.add(Tokens.getCommentText(c));
}
return origin.setComments(newComments);
}
}
@Override
public String toString() {
// this ends up in user-visible error messages, so we don't want the
// comments
return token.toString();
}
}
static private final class ParseContext { static private final class ParseContext {
private int lineNumber; private int lineNumber;
final private Stack<Token> buffer; final private Stack<TokenWithComments> buffer;
final private Iterator<Token> tokens; final private Iterator<Token> tokens;
final private ConfigIncluder includer; final private ConfigIncluder includer;
final private ConfigIncludeContext includeContext; final private ConfigIncludeContext includeContext;
@ -50,7 +94,7 @@ final class Parser {
Iterator<Token> tokens, ConfigIncluder includer, Iterator<Token> tokens, ConfigIncluder includer,
ConfigIncludeContext includeContext) { ConfigIncludeContext includeContext) {
lineNumber = 1; lineNumber = 1;
buffer = new Stack<Token>(); buffer = new Stack<TokenWithComments>();
this.tokens = tokens; this.tokens = tokens;
this.flavor = flavor; this.flavor = flavor;
this.baseOrigin = origin; this.baseOrigin = origin;
@ -60,14 +104,67 @@ final class Parser {
this.equalsCount = 0; this.equalsCount = 0;
} }
private Token nextToken() { private void consolidateCommentBlock(Token commentToken) {
Token t = null; // a comment block "goes with" the following token
if (buffer.isEmpty()) { // unless it's separated from it by a blank line.
t = tokens.next(); // we want to build a list of newline tokens followed
} else { // by a non-newline non-comment token; with all comments
t = buffer.pop(); // associated with that final non-newline non-comment token.
List<Token> newlines = new ArrayList<Token>();
List<Token> comments = new ArrayList<Token>();
Token previous = null;
Token next = commentToken;
while (true) {
if (Tokens.isNewline(next)) {
if (previous != null && Tokens.isNewline(previous)) {
// blank line; drop all comments to this point and
// start a new comment block
comments.clear();
}
newlines.add(next);
} else if (Tokens.isComment(next)) {
comments.add(next);
} else {
// a non-newline non-comment token
break;
}
previous = next;
next = tokens.next();
} }
// put our concluding token in the queue with all the comments
// attached
buffer.push(new TokenWithComments(next, comments));
// now put all the newlines back in front of it
ListIterator<Token> li = newlines.listIterator(newlines.size());
while (li.hasPrevious()) {
buffer.push(new TokenWithComments(li.previous()));
}
}
private TokenWithComments popToken() {
if (buffer.isEmpty()) {
Token t = tokens.next();
if (Tokens.isComment(t)) {
consolidateCommentBlock(t);
return buffer.pop();
} else {
return new TokenWithComments(t);
}
} else {
return buffer.pop();
}
}
private TokenWithComments nextToken() {
TokenWithComments withComments = null;
withComments = popToken();
Token t = withComments.token;
if (Tokens.isProblem(t)) { if (Tokens.isProblem(t)) {
ConfigOrigin origin = t.origin(); ConfigOrigin origin = t.origin();
String message = Tokens.getProblemMessage(t); String message = Tokens.getProblemMessage(t);
@ -79,32 +176,35 @@ final class Parser {
message = addKeyName(message); message = addKeyName(message);
} }
throw new ConfigException.Parse(origin, message, cause); throw new ConfigException.Parse(origin, message, cause);
} } else {
if (flavor == ConfigSyntax.JSON) {
if (flavor == ConfigSyntax.JSON) { if (Tokens.isUnquotedText(t)) {
if (Tokens.isUnquotedText(t)) { throw parseError(addKeyName("Token not allowed in valid JSON: '"
throw parseError(addKeyName("Token not allowed in valid JSON: '" + Tokens.getUnquotedText(t) + "'"));
+ Tokens.getUnquotedText(t) + "'")); } else if (Tokens.isSubstitution(t)) {
} else if (Tokens.isSubstitution(t)) { throw parseError(addKeyName("Substitutions (${} syntax) not allowed in JSON"));
throw parseError(addKeyName("Substitutions (${} syntax) not allowed in JSON")); }
} }
}
return t; return withComments;
}
} }
private void putBack(Token token) { private void putBack(TokenWithComments token) {
buffer.push(token); buffer.push(token);
} }
private Token nextTokenIgnoringNewline() { private TokenWithComments nextTokenIgnoringNewline() {
Token t = nextToken(); TokenWithComments t = nextToken();
while (Tokens.isNewline(t)) {
while (Tokens.isNewline(t.token)) {
// line number tokens have the line that was _ended_ by the // line number tokens have the line that was _ended_ by the
// newline, so we have to add one. // newline, so we have to add one.
lineNumber = t.lineNumber() + 1; lineNumber = t.token.lineNumber() + 1;
t = nextToken(); t = nextToken();
} }
return t; return t;
} }
@ -116,8 +216,8 @@ final class Parser {
// is left just after the comma or the newline. // is left just after the comma or the newline.
private boolean checkElementSeparator() { private boolean checkElementSeparator() {
if (flavor == ConfigSyntax.JSON) { if (flavor == ConfigSyntax.JSON) {
Token t = nextTokenIgnoringNewline(); TokenWithComments t = nextTokenIgnoringNewline();
if (t == Tokens.COMMA) { if (t.token == Tokens.COMMA) {
return true; return true;
} else { } else {
putBack(t); putBack(t);
@ -125,15 +225,16 @@ final class Parser {
} }
} else { } else {
boolean sawSeparatorOrNewline = false; boolean sawSeparatorOrNewline = false;
Token t = nextToken(); TokenWithComments t = nextToken();
while (true) { while (true) {
if (Tokens.isNewline(t)) { if (Tokens.isNewline(t.token)) {
// newline number is the line just ended, so add one // newline number is the line just ended, so add one
lineNumber = t.lineNumber() + 1; lineNumber = t.token.lineNumber() + 1;
sawSeparatorOrNewline = true; sawSeparatorOrNewline = true;
// we want to continue to also eat // we want to continue to also eat
// a comma if there is one. // a comma if there is one.
} else if (t == Tokens.COMMA) { } else if (t.token == Tokens.COMMA) {
return true; return true;
} else { } else {
// non-newline-or-comma // non-newline-or-comma
@ -154,12 +255,17 @@ final class Parser {
return; return;
List<Token> values = null; // create only if we have value tokens List<Token> values = null; // create only if we have value tokens
Token t = nextTokenIgnoringNewline(); // ignore a newline up front TokenWithComments firstValueWithComments = null;
while (Tokens.isValue(t) || Tokens.isUnquotedText(t) TokenWithComments t = nextTokenIgnoringNewline(); // ignore a
|| Tokens.isSubstitution(t)) { // newline up
if (values == null) // front
while (Tokens.isValue(t.token) || Tokens.isUnquotedText(t.token)
|| Tokens.isSubstitution(t.token)) {
if (values == null) {
values = new ArrayList<Token>(); values = new ArrayList<Token>();
values.add(t); firstValueWithComments = t;
}
values.add(t.token);
t = nextToken(); // but don't consolidate across a newline t = nextToken(); // but don't consolidate across a newline
} }
// the last one wasn't a value token // the last one wasn't a value token
@ -168,9 +274,9 @@ final class Parser {
if (values == null) if (values == null)
return; return;
if (values.size() == 1 && Tokens.isValue(values.get(0))) { if (values.size() == 1 && Tokens.isValue(firstValueWithComments.token)) {
// a single value token requires no consolidation // a single value token requires no consolidation
putBack(values.get(0)); putBack(firstValueWithComments);
return; return;
} }
@ -235,7 +341,7 @@ final class Parser {
firstOrigin, minimized)); firstOrigin, minimized));
} }
putBack(consolidated); putBack(new TokenWithComments(consolidated, firstValueWithComments.comments));
} }
private ConfigOrigin lineOrigin() { private ConfigOrigin lineOrigin() {
@ -309,17 +415,23 @@ final class Parser {
return part + ")"; return part + ")";
} }
private AbstractConfigValue parseValue(Token token) { private AbstractConfigValue parseValue(TokenWithComments t) {
if (Tokens.isValue(token)) { AbstractConfigValue v;
return Tokens.getValue(token);
} else if (token == Tokens.OPEN_CURLY) { if (Tokens.isValue(t.token)) {
return parseObject(true); v = Tokens.getValue(t.token);
} else if (token == Tokens.OPEN_SQUARE) { } else if (t.token == Tokens.OPEN_CURLY) {
return parseArray(); v = parseObject(true);
} else if (t.token == Tokens.OPEN_SQUARE) {
v = parseArray();
} else { } else {
throw parseError(addQuoteSuggestion(token.toString(), throw parseError(addQuoteSuggestion(t.token.toString(),
"Expecting a value but got wrong token: " + token)); "Expecting a value but got wrong token: " + t.token));
} }
v = v.withOrigin(t.setComments(v.origin()));
return v;
} }
private static AbstractConfigObject createValueUnderPath(Path path, private static AbstractConfigObject createValueUnderPath(Path path,
@ -339,24 +451,29 @@ final class Parser {
remaining = remaining.remainder(); remaining = remaining.remainder();
} }
} }
// the setComments(null) is to ensure comments are only
// on the exact leaf node they apply to.
// a comment before "foo.bar" applies to the full setting
// "foo.bar" not also to "foo"
ListIterator<String> i = keys.listIterator(keys.size()); ListIterator<String> i = keys.listIterator(keys.size());
String deepest = i.previous(); String deepest = i.previous();
AbstractConfigObject o = new SimpleConfigObject(value.origin(), AbstractConfigObject o = new SimpleConfigObject(value.origin().setComments(null),
Collections.<String, AbstractConfigValue> singletonMap( Collections.<String, AbstractConfigValue> singletonMap(
deepest, value)); deepest, value));
while (i.hasPrevious()) { while (i.hasPrevious()) {
Map<String, AbstractConfigValue> m = Collections.<String, AbstractConfigValue> singletonMap( Map<String, AbstractConfigValue> m = Collections.<String, AbstractConfigValue> singletonMap(
i.previous(), o); i.previous(), o);
o = new SimpleConfigObject(value.origin(), m); o = new SimpleConfigObject(value.origin().setComments(null), m);
} }
return o; return o;
} }
private Path parseKey(Token token) { private Path parseKey(TokenWithComments token) {
if (flavor == ConfigSyntax.JSON) { if (flavor == ConfigSyntax.JSON) {
if (Tokens.isValueWithType(token, ConfigValueType.STRING)) { if (Tokens.isValueWithType(token.token, ConfigValueType.STRING)) {
String key = (String) Tokens.getValue(token).unwrapped(); String key = (String) Tokens.getValue(token.token).unwrapped();
return Path.newKey(key); return Path.newKey(key);
} else { } else {
throw parseError(addKeyName("Expecting close brace } or a field name here, got " throw parseError(addKeyName("Expecting close brace } or a field name here, got "
@ -364,9 +481,9 @@ final class Parser {
} }
} else { } else {
List<Token> expression = new ArrayList<Token>(); List<Token> expression = new ArrayList<Token>();
Token t = token; TokenWithComments t = token;
while (Tokens.isValue(t) || Tokens.isUnquotedText(t)) { while (Tokens.isValue(t.token) || Tokens.isUnquotedText(t.token)) {
expression.add(t); expression.add(t.token);
t = nextToken(); // note: don't cross a newline t = nextToken(); // note: don't cross a newline
} }
@ -400,13 +517,13 @@ final class Parser {
} }
private void parseInclude(Map<String, AbstractConfigValue> values) { private void parseInclude(Map<String, AbstractConfigValue> values) {
Token t = nextTokenIgnoringNewline(); TokenWithComments t = nextTokenIgnoringNewline();
while (isUnquotedWhitespace(t)) { while (isUnquotedWhitespace(t.token)) {
t = nextTokenIgnoringNewline(); t = nextTokenIgnoringNewline();
} }
if (Tokens.isValueWithType(t, ConfigValueType.STRING)) { if (Tokens.isValueWithType(t.token, ConfigValueType.STRING)) {
String name = (String) Tokens.getValue(t).unwrapped(); String name = (String) Tokens.getValue(t.token).unwrapped();
AbstractConfigObject obj = (AbstractConfigObject) includer AbstractConfigObject obj = (AbstractConfigObject) includer
.include(includeContext, name); .include(includeContext, name);
@ -448,8 +565,8 @@ final class Parser {
boolean lastInsideEquals = false; boolean lastInsideEquals = false;
while (true) { while (true) {
Token t = nextTokenIgnoringNewline(); TokenWithComments t = nextTokenIgnoringNewline();
if (t == Tokens.CLOSE_CURLY) { if (t.token == Tokens.CLOSE_CURLY) {
if (flavor == ConfigSyntax.JSON && afterComma) { if (flavor == ConfigSyntax.JSON && afterComma) {
throw parseError(addQuoteSuggestion(t.toString(), throw parseError(addQuoteSuggestion(t.toString(),
"expecting a field name after a comma, got a close brace } instead")); "expecting a field name after a comma, got a close brace } instead"));
@ -458,45 +575,45 @@ final class Parser {
"unbalanced close brace '}' with no open brace")); "unbalanced close brace '}' with no open brace"));
} }
break; break;
} else if (t == Tokens.END && !hadOpenCurly) { } else if (t.token == Tokens.END && !hadOpenCurly) {
putBack(t); putBack(t);
break; break;
} else if (flavor != ConfigSyntax.JSON && isIncludeKeyword(t)) { } else if (flavor != ConfigSyntax.JSON && isIncludeKeyword(t.token)) {
parseInclude(values); parseInclude(values);
afterComma = false; afterComma = false;
} else { } else {
Path path = parseKey(t); TokenWithComments keyToken = t;
Token afterKey = nextTokenIgnoringNewline(); Path path = parseKey(keyToken);
TokenWithComments afterKey = nextTokenIgnoringNewline();
boolean insideEquals = false; boolean insideEquals = false;
// path must be on-stack while we parse the value // path must be on-stack while we parse the value
pathStack.push(path); pathStack.push(path);
Token valueToken; TokenWithComments valueToken;
AbstractConfigValue newValue; AbstractConfigValue newValue;
if (flavor == ConfigSyntax.CONF if (flavor == ConfigSyntax.CONF && afterKey.token == Tokens.OPEN_CURLY) {
&& afterKey == Tokens.OPEN_CURLY) {
// can omit the ':' or '=' before an object value // can omit the ':' or '=' before an object value
valueToken = afterKey; valueToken = afterKey;
newValue = parseObject(true);
} else { } else {
if (!isKeyValueSeparatorToken(afterKey)) { if (!isKeyValueSeparatorToken(afterKey.token)) {
throw parseError(addQuoteSuggestion(afterKey.toString(), throw parseError(addQuoteSuggestion(afterKey.toString(),
"Key '" + path.render() + "' may not be followed by token: " "Key '" + path.render() + "' may not be followed by token: "
+ afterKey)); + afterKey));
} }
if (afterKey == Tokens.EQUALS) { if (afterKey.token == Tokens.EQUALS) {
insideEquals = true; insideEquals = true;
equalsCount += 1; equalsCount += 1;
} }
consolidateValueTokens(); consolidateValueTokens();
valueToken = nextTokenIgnoringNewline(); valueToken = nextTokenIgnoringNewline();
newValue = parseValue(valueToken);
} }
newValue = parseValue(valueToken.prepend(keyToken.comments));
lastPath = pathStack.pop(); lastPath = pathStack.pop();
if (insideEquals) { if (insideEquals) {
equalsCount -= 1; equalsCount -= 1;
@ -547,7 +664,7 @@ final class Parser {
afterComma = true; afterComma = true;
} else { } else {
t = nextTokenIgnoringNewline(); t = nextTokenIgnoringNewline();
if (t == Tokens.CLOSE_CURLY) { if (t.token == Tokens.CLOSE_CURLY) {
if (!hadOpenCurly) { if (!hadOpenCurly) {
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals, throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
t.toString(), "unbalanced close brace '}' with no open brace")); t.toString(), "unbalanced close brace '}' with no open brace"));
@ -557,7 +674,7 @@ final class Parser {
throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals, throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals,
t.toString(), "Expecting close brace } or a comma, got " + t)); t.toString(), "Expecting close brace } or a comma, got " + t));
} else { } else {
if (t == Tokens.END) { if (t.token == Tokens.END) {
putBack(t); putBack(t);
break; break;
} else { } else {
@ -567,6 +684,7 @@ final class Parser {
} }
} }
} }
return new SimpleConfigObject(objectOrigin, values); return new SimpleConfigObject(objectOrigin, values);
} }
@ -577,18 +695,15 @@ final class Parser {
consolidateValueTokens(); consolidateValueTokens();
Token t = nextTokenIgnoringNewline(); TokenWithComments t = nextTokenIgnoringNewline();
// special-case the first element // special-case the first element
if (t == Tokens.CLOSE_SQUARE) { if (t.token == Tokens.CLOSE_SQUARE) {
return new SimpleConfigList(arrayOrigin, return new SimpleConfigList(arrayOrigin,
Collections.<AbstractConfigValue> emptyList()); Collections.<AbstractConfigValue> emptyList());
} else if (Tokens.isValue(t)) { } else if (Tokens.isValue(t.token) || t.token == Tokens.OPEN_CURLY
|| t.token == Tokens.OPEN_SQUARE) {
values.add(parseValue(t)); values.add(parseValue(t));
} else if (t == Tokens.OPEN_CURLY) {
values.add(parseObject(true));
} else if (t == Tokens.OPEN_SQUARE) {
values.add(parseArray());
} else { } else {
throw parseError(addKeyName("List should have ] or a first element after the open [, instead had token: " throw parseError(addKeyName("List should have ] or a first element after the open [, instead had token: "
+ t + t
@ -604,7 +719,7 @@ final class Parser {
// comma (or newline equivalent) consumed // comma (or newline equivalent) consumed
} else { } else {
t = nextTokenIgnoringNewline(); t = nextTokenIgnoringNewline();
if (t == Tokens.CLOSE_SQUARE) { if (t.token == Tokens.CLOSE_SQUARE) {
return new SimpleConfigList(arrayOrigin, values); return new SimpleConfigList(arrayOrigin, values);
} else { } else {
throw parseError(addKeyName("List should have ended with ] or had a comma, instead had token: " throw parseError(addKeyName("List should have ended with ] or had a comma, instead had token: "
@ -619,14 +734,10 @@ final class Parser {
consolidateValueTokens(); consolidateValueTokens();
t = nextTokenIgnoringNewline(); t = nextTokenIgnoringNewline();
if (Tokens.isValue(t)) { if (Tokens.isValue(t.token) || t.token == Tokens.OPEN_CURLY
|| t.token == Tokens.OPEN_SQUARE) {
values.add(parseValue(t)); values.add(parseValue(t));
} else if (t == Tokens.OPEN_CURLY) { } else if (flavor != ConfigSyntax.JSON && t.token == Tokens.CLOSE_SQUARE) {
values.add(parseObject(true));
} else if (t == Tokens.OPEN_SQUARE) {
values.add(parseArray());
} else if (flavor != ConfigSyntax.JSON
&& t == Tokens.CLOSE_SQUARE) {
// we allow one trailing comma // we allow one trailing comma
putBack(t); putBack(t);
} else { } else {
@ -640,8 +751,8 @@ final class Parser {
} }
AbstractConfigValue parse() { AbstractConfigValue parse() {
Token t = nextTokenIgnoringNewline(); TokenWithComments t = nextTokenIgnoringNewline();
if (t == Tokens.START) { if (t.token == Tokens.START) {
// OK // OK
} else { } else {
throw new ConfigException.BugOrBroken( throw new ConfigException.BugOrBroken(
@ -650,13 +761,11 @@ final class Parser {
t = nextTokenIgnoringNewline(); t = nextTokenIgnoringNewline();
AbstractConfigValue result = null; AbstractConfigValue result = null;
if (t == Tokens.OPEN_CURLY) { if (t.token == Tokens.OPEN_CURLY || t.token == Tokens.OPEN_SQUARE) {
result = parseObject(true); result = parseValue(t);
} else if (t == Tokens.OPEN_SQUARE) {
result = parseArray();
} else { } else {
if (flavor == ConfigSyntax.JSON) { if (flavor == ConfigSyntax.JSON) {
if (t == Tokens.END) { if (t.token == Tokens.END) {
throw parseError("Empty document"); throw parseError("Empty document");
} else { } else {
throw parseError("Document must have an object or array at root, unexpected token: " throw parseError("Document must have an object or array at root, unexpected token: "
@ -668,11 +777,14 @@ final class Parser {
// of it, so put it back. // of it, so put it back.
putBack(t); putBack(t);
result = parseObject(false); result = parseObject(false);
// in this case we don't try to use commentsStack comments
// since they would all presumably apply to fields not the
// root object
} }
} }
t = nextTokenIgnoringNewline(); t = nextTokenIgnoringNewline();
if (t == Tokens.END) { if (t.token == Tokens.END) {
return result; return result;
} else { } else {
throw parseError("Document has trailing tokens after first object or array: " throw parseError("Document has trailing tokens after first object or array: "

View file

@ -145,6 +145,14 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
sb.append("# "); sb.append("# ");
sb.append(v.origin().description()); sb.append(v.origin().description());
sb.append("\n"); sb.append("\n");
for (String comment : v.origin().comments()) {
indent(sb, indent + 1);
sb.append("# ");
sb.append(comment);
sb.append("\n");
}
indent(sb, indent + 1); indent(sb, indent + 1);
} }
v.render(sb, indent + 1, formatted); v.render(sb, indent + 1, formatted);
@ -353,4 +361,9 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
public ConfigValue set(int index, ConfigValue element) { public ConfigValue set(int index, ConfigValue element) {
throw weAreImmutable("set"); throw weAreImmutable("set");
} }
@Override
protected SimpleConfigList newCopy(boolean ignoresFallbacks, ConfigOrigin newOrigin) {
return new SimpleConfigList(newOrigin, value);
}
} }

View file

@ -45,8 +45,9 @@ final class SimpleConfigObject extends AbstractConfigObject {
} }
@Override @Override
protected SimpleConfigObject newCopy(ResolveStatus newStatus, boolean newIgnoresFallbacks) { protected SimpleConfigObject newCopy(ResolveStatus newStatus, boolean newIgnoresFallbacks,
return new SimpleConfigObject(origin(), value, newStatus, newIgnoresFallbacks); ConfigOrigin newOrigin) {
return new SimpleConfigObject(newOrigin, value, newStatus, newIgnoresFallbacks);
} }
@Override @Override

View file

@ -8,6 +8,7 @@ import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -22,19 +23,21 @@ final class SimpleConfigOrigin implements ConfigOrigin {
final private int endLineNumber; final private int endLineNumber;
final private OriginType originType; final private OriginType originType;
final private String urlOrNull; final private String urlOrNull;
final private List<String> commentsOrNull;
protected SimpleConfigOrigin(String description, int lineNumber, int endLineNumber, protected SimpleConfigOrigin(String description, int lineNumber, int endLineNumber,
OriginType originType, OriginType originType,
String urlOrNull) { String urlOrNull, List<String> commentsOrNull) {
this.description = description; this.description = description;
this.lineNumber = lineNumber; this.lineNumber = lineNumber;
this.endLineNumber = endLineNumber; this.endLineNumber = endLineNumber;
this.originType = originType; this.originType = originType;
this.urlOrNull = urlOrNull; this.urlOrNull = urlOrNull;
this.commentsOrNull = commentsOrNull;
} }
static SimpleConfigOrigin newSimple(String description) { static SimpleConfigOrigin newSimple(String description) {
return new SimpleConfigOrigin(description, -1, -1, OriginType.GENERIC, null); return new SimpleConfigOrigin(description, -1, -1, OriginType.GENERIC, null, null);
} }
static SimpleConfigOrigin newFile(String filename) { static SimpleConfigOrigin newFile(String filename) {
@ -44,17 +47,17 @@ final class SimpleConfigOrigin implements ConfigOrigin {
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
url = null; url = null;
} }
return new SimpleConfigOrigin(filename, -1, -1, OriginType.FILE, url); return new SimpleConfigOrigin(filename, -1, -1, OriginType.FILE, url, null);
} }
static SimpleConfigOrigin newURL(URL url) { static SimpleConfigOrigin newURL(URL url) {
String u = url.toExternalForm(); String u = url.toExternalForm();
return new SimpleConfigOrigin(u, -1, -1, OriginType.URL, u); return new SimpleConfigOrigin(u, -1, -1, OriginType.URL, u, null);
} }
static SimpleConfigOrigin newResource(String resource, URL url) { static SimpleConfigOrigin newResource(String resource, URL url) {
return new SimpleConfigOrigin(resource, -1, -1, OriginType.RESOURCE, return new SimpleConfigOrigin(resource, -1, -1, OriginType.RESOURCE,
url != null ? url.toExternalForm() : null); url != null ? url.toExternalForm() : null, null);
} }
static SimpleConfigOrigin newResource(String resource) { static SimpleConfigOrigin newResource(String resource) {
@ -66,13 +69,22 @@ final class SimpleConfigOrigin implements ConfigOrigin {
return this; return this;
} else { } else {
return new SimpleConfigOrigin(this.description, lineNumber, lineNumber, return new SimpleConfigOrigin(this.description, lineNumber, lineNumber,
this.originType, this.urlOrNull); this.originType, this.urlOrNull, this.commentsOrNull);
} }
} }
SimpleConfigOrigin addURL(URL url) { SimpleConfigOrigin addURL(URL url) {
return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber, this.originType, return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber,
url != null ? url.toExternalForm() : null); this.originType, url != null ? url.toExternalForm() : null, this.commentsOrNull);
}
SimpleConfigOrigin setComments(List<String> comments) {
if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull)) {
return this;
} else {
return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber,
this.originType, this.urlOrNull, comments);
}
} }
@Override @Override
@ -172,12 +184,22 @@ final class SimpleConfigOrigin implements ConfigOrigin {
return lineNumber; return lineNumber;
} }
@Override
public List<String> comments() {
if (commentsOrNull != null) {
return commentsOrNull;
} else {
return Collections.emptyList();
}
}
static final String MERGE_OF_PREFIX = "merge of "; static final String MERGE_OF_PREFIX = "merge of ";
private static SimpleConfigOrigin mergeTwo(SimpleConfigOrigin a, SimpleConfigOrigin b) { private static SimpleConfigOrigin mergeTwo(SimpleConfigOrigin a, SimpleConfigOrigin b) {
String mergedDesc; String mergedDesc;
int mergedStartLine; int mergedStartLine;
int mergedEndLine; int mergedEndLine;
List<String> mergedComments;
OriginType mergedType; OriginType mergedType;
if (a.originType == b.originType) { if (a.originType == b.originType) {
@ -233,8 +255,18 @@ final class SimpleConfigOrigin implements ConfigOrigin {
mergedURL = null; mergedURL = null;
} }
if (ConfigImplUtil.equalsHandlingNull(a.commentsOrNull, b.commentsOrNull)) {
mergedComments = a.commentsOrNull;
} else {
mergedComments = new ArrayList<String>();
if (a.commentsOrNull != null)
mergedComments.addAll(a.commentsOrNull);
if (b.commentsOrNull != null)
mergedComments.addAll(b.commentsOrNull);
}
return new SimpleConfigOrigin(mergedDesc, mergedStartLine, mergedEndLine, mergedType, return new SimpleConfigOrigin(mergedDesc, mergedStartLine, mergedEndLine, mergedType,
mergedURL); mergedURL, mergedComments);
} }
private static int similarity(SimpleConfigOrigin a, SimpleConfigOrigin b) { private static int similarity(SimpleConfigOrigin a, SimpleConfigOrigin b) {

View file

@ -17,5 +17,6 @@ enum TokenType {
NEWLINE, NEWLINE,
UNQUOTED_TEXT, UNQUOTED_TEXT,
SUBSTITUTION, SUBSTITUTION,
PROBLEM; PROBLEM,
COMMENT;
} }

View file

@ -168,40 +168,27 @@ final class Tokenizer {
return c != '\n' && ConfigImplUtil.isWhitespace(c); return c != '\n' && ConfigImplUtil.isWhitespace(c);
} }
private int slurpComment() { private boolean startOfComment(int c) {
for (;;) { if (c == -1) {
int c = nextCharRaw(); return false;
if (c == -1 || c == '\n') { } else {
return c; if (allowComments) {
} if (c == '#') {
} return true;
} } else if (c == '/') {
int maybeSecondSlash = nextCharRaw();
// get next char, skipping comments // we want to predictably NOT consume any chars
private int nextCharSkippingComments() { putBack(maybeSecondSlash);
for (;;) { if (maybeSecondSlash == '/') {
int c = nextCharRaw(); return true;
if (c == -1) {
return -1;
} else {
if (allowComments) {
if (c == '#') {
return slurpComment();
} else if (c == '/') {
int maybeSecondSlash = nextCharRaw();
if (maybeSecondSlash == '/') {
return slurpComment();
} else {
putBack(maybeSecondSlash);
return c;
}
} else { } else {
return c; return false;
} }
} else { } else {
return c; return false;
} }
} else {
return false;
} }
} }
} }
@ -209,7 +196,7 @@ final class Tokenizer {
// get next char, skipping non-newline whitespace // get next char, skipping non-newline whitespace
private int nextCharAfterWhitespace(WhitespaceSaver saver) { private int nextCharAfterWhitespace(WhitespaceSaver saver) {
for (;;) { for (;;) {
int c = nextCharSkippingComments(); int c = nextCharRaw();
if (c == -1) { if (c == -1) {
return -1; return -1;
@ -269,6 +256,27 @@ final class Tokenizer {
return ((SimpleConfigOrigin) baseOrigin).setLineNumber(lineNumber); return ((SimpleConfigOrigin) baseOrigin).setLineNumber(lineNumber);
} }
// ONE char has always been consumed, either the # or the first /, but
// not both slashes
private Token pullComment(int firstChar) {
if (firstChar == '/') {
int discard = nextCharRaw();
if (discard != '/')
throw new ConfigException.BugOrBroken("called pullComment but // not seen");
}
StringBuilder sb = new StringBuilder();
for (;;) {
int c = nextCharRaw();
if (c == -1 || c == '\n') {
putBack(c);
return Tokens.newComment(lineOrigin, sb.toString());
} else {
sb.appendCodePoint(c);
}
}
}
// chars JSON allows a number to start with // chars JSON allows a number to start with
static final String firstNumberChars = "0123456789-"; static final String firstNumberChars = "0123456789-";
// chars JSON allows to be part of a number // chars JSON allows to be part of a number
@ -283,7 +291,7 @@ final class Tokenizer {
private Token pullUnquotedText() { private Token pullUnquotedText() {
ConfigOrigin origin = lineOrigin; ConfigOrigin origin = lineOrigin;
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
int c = nextCharSkippingComments(); int c = nextCharRaw();
while (true) { while (true) {
if (c == -1) { if (c == -1) {
break; break;
@ -291,6 +299,8 @@ final class Tokenizer {
break; break;
} else if (isWhitespace(c)) { } else if (isWhitespace(c)) {
break; break;
} else if (startOfComment(c)) {
break;
} else { } else {
sb.appendCodePoint(c); sb.appendCodePoint(c);
} }
@ -310,7 +320,7 @@ final class Tokenizer {
return Tokens.newBoolean(origin, false); return Tokens.newBoolean(origin, false);
} }
c = nextCharSkippingComments(); c = nextCharRaw();
} }
// put back the char that ended the unquoted text // put back the char that ended the unquoted text
@ -324,12 +334,12 @@ final class Tokenizer {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.appendCodePoint(firstChar); sb.appendCodePoint(firstChar);
boolean containedDecimalOrE = false; boolean containedDecimalOrE = false;
int c = nextCharSkippingComments(); int c = nextCharRaw();
while (c != -1 && numberChars.indexOf(c) >= 0) { while (c != -1 && numberChars.indexOf(c) >= 0) {
if (c == '.' || c == 'e' || c == 'E') if (c == '.' || c == 'e' || c == 'E')
containedDecimalOrE = true; containedDecimalOrE = true;
sb.appendCodePoint(c); sb.appendCodePoint(c);
c = nextCharSkippingComments(); c = nextCharRaw();
} }
// the last character we looked at wasn't part of the number, put it // the last character we looked at wasn't part of the number, put it
// back // back
@ -382,7 +392,7 @@ final class Tokenizer {
// kind of absurdly slow, but screw it for now // kind of absurdly slow, but screw it for now
char[] a = new char[4]; char[] a = new char[4];
for (int i = 0; i < 4; ++i) { for (int i = 0; i < 4; ++i) {
int c = nextCharSkippingComments(); int c = nextCharRaw();
if (c == -1) if (c == -1)
throw problem("End of input but expecting 4 hex digits for \\uXXXX escape"); throw problem("End of input but expecting 4 hex digits for \\uXXXX escape");
a[i] = (char) c; a[i] = (char) c;
@ -431,14 +441,14 @@ final class Tokenizer {
private Token pullSubstitution() throws ProblemException { private Token pullSubstitution() throws ProblemException {
// the initial '$' has already been consumed // the initial '$' has already been consumed
ConfigOrigin origin = lineOrigin; ConfigOrigin origin = lineOrigin;
int c = nextCharSkippingComments(); int c = nextCharRaw();
if (c != '{') { if (c != '{') {
throw problem(asString(c), "'$' not followed by {, '" + asString(c) throw problem(asString(c), "'$' not followed by {, '" + asString(c)
+ "' not allowed after '$'", true /* suggestQuotes */); + "' not allowed after '$'", true /* suggestQuotes */);
} }
boolean optional = false; boolean optional = false;
c = nextCharSkippingComments(); c = nextCharRaw();
if (c == '?') { if (c == '?') {
optional = true; optional = true;
} else { } else {
@ -484,45 +494,49 @@ final class Tokenizer {
return line; return line;
} else { } else {
Token t = null; Token t = null;
switch (c) { if (startOfComment(c)) {
case '"': t = pullComment(c);
t = pullQuotedString(); } else {
break; switch (c) {
case '$': case '"':
t = pullSubstitution(); t = pullQuotedString();
break; break;
case ':': case '$':
t = Tokens.COLON; t = pullSubstitution();
break; break;
case ',': case ':':
t = Tokens.COMMA; t = Tokens.COLON;
break; break;
case '=': case ',':
t = Tokens.EQUALS; t = Tokens.COMMA;
break; break;
case '{': case '=':
t = Tokens.OPEN_CURLY; t = Tokens.EQUALS;
break; break;
case '}': case '{':
t = Tokens.CLOSE_CURLY; t = Tokens.OPEN_CURLY;
break; break;
case '[': case '}':
t = Tokens.OPEN_SQUARE; t = Tokens.CLOSE_CURLY;
break; break;
case ']': case '[':
t = Tokens.CLOSE_SQUARE; t = Tokens.OPEN_SQUARE;
break; break;
} case ']':
t = Tokens.CLOSE_SQUARE;
break;
}
if (t == null) { if (t == null) {
if (firstNumberChars.indexOf(c) >= 0) { if (firstNumberChars.indexOf(c) >= 0) {
t = pullNumber(c); t = pullNumber(c);
} else if (notInUnquotedText.indexOf(c) >= 0) { } else if (notInUnquotedText.indexOf(c) >= 0) {
throw problem(asString(c), "Reserved character '" + asString(c) throw problem(asString(c), "Reserved character '" + asString(c)
+ "' is not allowed outside quotes", true /* suggestQuotes */); + "' is not allowed outside quotes", true /* suggestQuotes */);
} else { } else {
putBack(c); putBack(c);
t = pullUnquotedText(); t = pullUnquotedText();
}
} }
} }
@ -548,6 +562,7 @@ final class Tokenizer {
Token whitespace = whitespaceSaver.check(t, origin, lineNumber); Token whitespace = whitespaceSaver.check(t, origin, lineNumber);
if (whitespace != null) if (whitespace != null)
tokens.add(whitespace); tokens.add(whitespace);
tokens.add(t); tokens.add(t);
} }

View file

@ -52,7 +52,7 @@ final class Tokens {
@Override @Override
public String toString() { public String toString() {
return "'\n'@" + lineNumber(); return "'\\n'@" + lineNumber();
} }
@Override @Override
@ -167,6 +167,45 @@ final class Tokens {
} }
} }
static private class Comment extends Token {
final private String text;
Comment(ConfigOrigin origin, String text) {
super(TokenType.COMMENT, origin);
this.text = text;
}
String text() {
return text;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("'#");
sb.append(text);
sb.append("' (COMMENT)");
return sb.toString();
}
@Override
protected boolean canEqual(Object other) {
return other instanceof Comment;
}
@Override
public boolean equals(Object other) {
return super.equals(other) && ((Comment) other).text.equals(text);
}
@Override
public int hashCode() {
int h = 41 * (41 + super.hashCode());
h = 41 * (h + text.hashCode());
return h;
}
}
// This is not a Value, because it requires special processing // This is not a Value, because it requires special processing
static private class Substitution extends Token { static private class Substitution extends Token {
final private boolean optional; final private boolean optional;
@ -262,6 +301,18 @@ final class Tokens {
} }
} }
static boolean isComment(Token token) {
return token instanceof Comment;
}
static String getCommentText(Token token) {
if (token instanceof Comment) {
return ((Comment) token).text();
} else {
throw new ConfigException.BugOrBroken("tried to get comment text from " + token);
}
}
static boolean isUnquotedText(Token token) { static boolean isUnquotedText(Token token) {
return token instanceof UnquotedText; return token instanceof UnquotedText;
} }
@ -316,6 +367,10 @@ final class Tokens {
return new Problem(origin, what, message, suggestQuotes, cause); return new Problem(origin, what, message, suggestQuotes, cause);
} }
static Token newComment(ConfigOrigin origin, String text) {
return new Comment(origin, text);
}
static Token newUnquotedText(ConfigOrigin origin, String s) { static Token newUnquotedText(ConfigOrigin origin, String s) {
return new UnquotedText(origin, s); return new UnquotedText(origin, s);
} }

View file

@ -3,27 +3,37 @@
############################## ##############################
# This the reference config file has all the default settings. # This the reference config file has all the default settings.
# Make your edits/overrides in your akka.conf. # Make your edits/overrides in your application.conf.
akka { akka {
version = "2.0-SNAPSHOT" # Akka version, checked against the runtime version of Akka. # Akka version, checked against the runtime version of Akka.
version = "2.0-SNAPSHOT"
home = "" # Home directory of Akka, modules in the deploy directory will be loaded
enabled-modules = [] # Comma separated list of the enabled modules. Options: ["cluster", "camel", "http"] # Home directory of Akka, modules in the deploy directory will be loaded
home = ""
event-handlers = ["akka.event.Logging$DefaultLogger"] # Event handlers to register at boot time (Logging$DefaultLogger logs to STDOUT) # Comma separated list of the enabled modules. Options: ["cluster", "camel", "http"]
loglevel = "INFO" # Options: ERROR, WARNING, INFO, DEBUG enabled-modules = []
# this level is used by the configured loggers (see "event-handlers") as soon
# as they have been started; before that, see "stdout-loglevel"
stdout-loglevel = "WARNING" # Loglevel for the very basic logger activated during AkkaApplication startup
# FIXME: Is there any sensible reason why we have 2 different log levels?
logConfigOnStart = off # Log the complete configuration at INFO level when the actor system is started. # Event handlers to register at boot time (Logging$DefaultLogger logs to STDOUT)
# This is useful when you are uncertain of what configuration is used. event-handlers = ["akka.event.Logging$DefaultLogger"]
extensions = [] # List FQCN of extensions which shall be loaded at actor system startup. # Log level used by the configured loggers (see "event-handlers") as soon
# FIXME: clarify "extensions" here, "Akka Extensions (<link to docs>)" # as they have been started; before that, see "stdout-loglevel"
# Options: ERROR, WARNING, INFO, DEBUG
loglevel = "INFO"
# Log level for the very basic logger activated during AkkaApplication startup
# Options: ERROR, WARNING, INFO, DEBUG
stdout-loglevel = "WARNING"
# Log the complete configuration at INFO level when the actor system is started.
# This is useful when you are uncertain of what configuration is used.
logConfigOnStart = off
# List FQCN of extensions which shall be loaded at actor system startup.
# FIXME: clarify "extensions" here, "Akka Extensions (<link to docs>)"
extensions = []
# These boot classes are loaded (and created) automatically when the Akka Microkernel boots up # These boot classes are loaded (and created) automatically when the Akka Microkernel boots up
# Can be used to bootstrap your application(s) # Can be used to bootstrap your application(s)
@ -35,89 +45,150 @@ akka {
boot = [] boot = []
actor { actor {
provider = "akka.actor.LocalActorRefProvider" provider = "akka.actor.LocalActorRefProvider"
creation-timeout = 20s # Timeout for ActorSystem.actorOf
reaper-interval = 5s # frequency with which stopping actors are prodded in case they had to be removed from their parents # Timeout for ActorSystem.actorOf
timeout = 5s # Default timeout for Future based invocations creation-timeout = 20s
# - Actor: ask && ?
# - UntypedActor: ask # frequency with which stopping actors are prodded in case they had to be removed from their parents
# - TypedActor: methods with non-void return type reaper-interval = 5s
serialize-messages = off # Does a deep clone of (non-primitive) messages to ensure immutability
dispatcher-shutdown-timeout = 1s # How long dispatchers by default will wait for new actors until they shut down # Default timeout for Future based invocations
# - Actor: ask && ?
# - UntypedActor: ask
# - TypedActor: methods with non-void return type
timeout = 5s
# Does a deep clone of (non-primitive) messages to ensure immutability
serialize-messages = off
# How long dispatchers by default will wait for new actors until they shut down
dispatcher-shutdown-timeout = 1s
deployment { deployment {
default { # deployment id pattern, e.g. /app/service-ping
router = "direct" # routing (load-balance) scheme to use # deployment id pattern, e.g. /user/service-ping
# available: "direct", "round-robin", "random", "scatter-gather" default {
# or: fully qualified class name of the router class
# default is "direct";
# In case of non-direct routing, the actors to be routed to can be specified
# in several ways:
# - nr-of-instances: will create that many children given the actor factory
# supplied in the source code (overridable using create-as below)
# - target.paths: will look the paths up using actorFor and route to
# them, i.e. will not create children
nr-of-instances = 1 # number of children to create in case of a non-direct router; this setting
# is ignored if target.paths is given
create-as { # FIXME document 'create-as' # routing (load-balance) scheme to use
class = "" # fully qualified class name of recipe implementation # available: "direct", "round-robin", "random", "scatter-gather"
# or: fully qualified class name of the router class
# default is "direct";
# In case of non-direct routing, the actors to be routed to can be specified
# in several ways:
# - nr-of-instances: will create that many children given the actor factory
# supplied in the source code (overridable using create-as below)
# - target.paths: will look the paths up using actorFor and route to
# them, i.e. will not create children
router = "direct"
# number of children to create in case of a non-direct router; this setting
# is ignored if target.paths is given
nr-of-instances = 1
# FIXME document 'create-as', ticket 1511
create-as {
# fully qualified class name of recipe implementation
class = ""
} }
target { target {
paths = [] # Alternatively to giving nr-of-instances you can specify the full paths of # Alternatively to giving nr-of-instances you can specify the full paths of
# those actors which should be routed to. This setting takes precedence over # those actors which should be routed to. This setting takes precedence over
# nr-of-instances # nr-of-instances
paths = []
} }
} }
} }
default-dispatcher { default-dispatcher {
type = "Dispatcher" # Must be one of the following # Must be one of the following
# Dispatcher, (BalancingDispatcher, only valid when all actors using it are of the same type), # Dispatcher, (BalancingDispatcher, only valid when all actors using it are of the same type),
# A FQCN to a class inheriting MessageDispatcherConfigurator with a no-arg visible constructor # A FQCN to a class inheriting MessageDispatcherConfigurator with a no-arg visible constructor
name = "DefaultDispatcher" # Name used in log messages and thread names. type = "Dispatcher"
daemonic = off # Toggles whether the threads created by this dispatcher should be daemons or not
keep-alive-time = 60s # Keep alive time for threads # Name used in log messages and thread names.
core-pool-size-min = 8 # minimum number of threads to cap factor-based core number to name = "DefaultDispatcher"
core-pool-size-factor = 8.0 # No of core threads ... ceil(available processors * factor)
core-pool-size-max = 4096 # maximum number of threads to cap factor-based number to # Toggles whether the threads created by this dispatcher should be daemons or not
# Hint: max-pool-size is only used for bounded task queues daemonic = off
max-pool-size-min = 8 # minimum number of threads to cap factor-based max number to
max-pool-size-factor = 8.0 # Max no of threads ... ceil(available processors * factor) # Keep alive time for threads
max-pool-size-max = 4096 # maximum number of threads to cap factor-based max number to keep-alive-time = 60s
task-queue-size = -1 # Specifies the bounded capacity of the task queue (< 1 == unbounded)
task-queue-type = "linked" # Specifies which type of task queue will be used, can be "array" or "linked" (default) # minimum number of threads to cap factor-based core number to
allow-core-timeout = on # Allow core threads to time out core-pool-size-min = 8
throughput = 5 # Throughput defines the number of messages that are processed in a batch before the
# thread is returned to the pool. Set to 1 for as fair as possible. # No of core threads ... ceil(available processors * factor)
throughput-deadline-time = 0ms # Throughput deadline for Dispatcher, set to 0 or negative for no deadline core-pool-size-factor = 8.0
mailbox-capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default)
# If positive then a bounded mailbox is used and the capacity is set using the property # maximum number of threads to cap factor-based number to
# NOTE: setting a mailbox to 'blocking' can be a bit dangerous, could lead to deadlock, use with care core-pool-size-max = 4096
# The following are only used for Dispatcher and only if mailbox-capacity > 0
mailbox-push-timeout-time = 10s # Specifies the timeout to add a new message to a mailbox that is full - negative number means infinite timeout # Hint: max-pool-size is only used for bounded task queues
# minimum number of threads to cap factor-based max number to
max-pool-size-min = 8
# Max no of threads ... ceil(available processors * factor)
max-pool-size-factor = 8.0
# maximum number of threads to cap factor-based max number to
max-pool-size-max = 4096
# Specifies the bounded capacity of the task queue (< 1 == unbounded)
task-queue-size = -1
# Specifies which type of task queue will be used, can be "array" or "linked" (default)
task-queue-type = "linked"
# Allow core threads to time out
allow-core-timeout = on
# Throughput defines the number of messages that are processed in a batch before the
# thread is returned to the pool. Set to 1 for as fair as possible.
throughput = 5
# Throughput deadline for Dispatcher, set to 0 or negative for no deadline
throughput-deadline-time = 0ms
# If negative (or zero) then an unbounded mailbox is used (default)
# If positive then a bounded mailbox is used and the capacity is set using the property
# NOTE: setting a mailbox to 'blocking' can be a bit dangerous, could lead to deadlock, use with care
# The following are only used for Dispatcher and only if mailbox-capacity > 0
mailbox-capacity = -1
# Specifies the timeout to add a new message to a mailbox that is full -
# negative number means infinite timeout
mailbox-push-timeout-time = 10s
} }
debug { debug {
receive = off # enable function of Actor.loggable(), which is to log any received message at DEBUG level # enable function of Actor.loggable(), which is to log any received message at DEBUG level
autoreceive = off # enable DEBUG logging of all AutoReceiveMessages (Kill, PoisonPill and the like) receive = off
lifecycle = off # enable DEBUG logging of actor lifecycle changes
fsm = off # enable DEBUG logging of all LoggingFSMs for events, transitions and timers # enable DEBUG logging of all AutoReceiveMessages (Kill, PoisonPill and the like)
event-stream = off # enable DEBUG logging of subscription changes on the eventStream autoreceive = off
# enable DEBUG logging of actor lifecycle changes
lifecycle = off
# enable DEBUG logging of all LoggingFSMs for events, transitions and timers
fsm = off
# enable DEBUG logging of subscription changes on the eventStream
event-stream = off
} }
# Entries for pluggable serializers and their bindings. If a binding for a specific class is not found, # Entries for pluggable serializers and their bindings. If a binding for a specific class is not found,
# then the default serializer (Java serialization) is used. # then the default serializer (Java serialization) is used.
#
serializers { serializers {
# java = "akka.serialization.JavaSerializer" # java = "akka.serialization.JavaSerializer"
# proto = "akka.testing.ProtobufSerializer" # proto = "akka.testing.ProtobufSerializer"
# sjson = "akka.testing.SJSONSerializer" # sjson = "akka.testing.SJSONSerializer"
default = "akka.serialization.JavaSerializer" default = "akka.serialization.JavaSerializer"
} }
@ -138,7 +209,6 @@ akka {
# #
scheduler { scheduler {
# The HashedWheelTimer (HWT) implementation from Netty is used as the default scheduler in the system. # The HashedWheelTimer (HWT) implementation from Netty is used as the default scheduler in the system.
#
# HWT does not execute the scheduled tasks on exact time. # HWT does not execute the scheduled tasks on exact time.
# It will, on every tick, check if there are any tasks behind the schedule and execute them. # It will, on every tick, check if there are any tasks behind the schedule and execute them.
# You can increase or decrease the accuracy of the execution timing by specifying smaller or larger tick duration. # You can increase or decrease the accuracy of the execution timing by specifying smaller or larger tick duration.
@ -147,5 +217,5 @@ akka {
tickDuration = 100ms tickDuration = 100ms
ticksPerWheel = 512 ticksPerWheel = 512
} }
} }

View file

@ -11,26 +11,26 @@ Configuration
Specifying the configuration file Specifying the configuration file
--------------------------------- ---------------------------------
If you don't specify a configuration file then Akka uses default values, corresponding to the reference If you don't specify a configuration file then Akka uses default values, corresponding to the reference
configuration files that you see below. You can specify your own configuration file to override any configuration files that you see below. You can specify your own configuration file to override any
property in the reference config. You only have to define the properties that differ from the default property in the reference config. You only have to define the properties that differ from the default
configuration. configuration.
By default the ``ConfigFactory.load`` method is used, which will load all ``application.conf`` (and By default the ``ConfigFactory.load`` method is used, which will load all ``application.conf`` (and
``application.json`` and ``application.properties``) from the root of the classpath, if they exists. ``application.json`` and ``application.properties``) from the root of the classpath, if they exists.
It uses ``ConfigFactory.defaultOverrides``, i.e. system properties, before falling back to It uses ``ConfigFactory.defaultOverrides``, i.e. system properties, before falling back to
application and reference configuration. application and reference configuration.
Note that *all* ``application.{conf,json,properties}`` classpath resources, from all directories and Note that *all* ``application.{conf,json,properties}`` classpath resources, from all directories and
jar files, are loaded and merged. Therefore it is a good practice to define separate sub-trees in the jar files, are loaded and merged. Therefore it is a good practice to define separate sub-trees in the
configuration for each actor system, and grab the specific configuration when instantiating the ActorSystem. configuration for each actor system, and grab the specific configuration when instantiating the ActorSystem.
:: ::
myapp1 { myapp1 {
akka.loglevel = WARNING akka.loglevel = WARNING
} }
myapp2 { myapp2 {
akka.loglevel = ERROR akka.loglevel = ERROR
} }
@ -44,7 +44,7 @@ classpath resource, file, or URL specified in those properties will be used rath
``application.{conf,json,properties}`` classpath resources. Note that classpath resource names start ``application.{conf,json,properties}`` classpath resources. Note that classpath resource names start
with ``/``. ``-Dconfig.resource=/dev.conf`` will load the ``dev.conf`` from the root of the classpath. with ``/``. ``-Dconfig.resource=/dev.conf`` will load the ``dev.conf`` from the root of the classpath.
You may also specify and parse the configuration programmatically in other ways when instantiating You may also specify and parse the configuration programmatically in other ways when instantiating
the ``ActorSystem``. the ``ActorSystem``.
.. includecode:: code/akka/docs/config/ConfigDocSpec.scala .. includecode:: code/akka/docs/config/ConfigDocSpec.scala
@ -66,7 +66,7 @@ Each Akka module has a reference configuration file with the default values.
.. literalinclude:: ../../akka-remote/src/main/resources/reference.conf .. literalinclude:: ../../akka-remote/src/main/resources/reference.conf
:language: none :language: none
*akka-testkit:* *akka-testkit:*
.. literalinclude:: ../../akka-testkit/src/main/resources/reference.conf .. literalinclude:: ../../akka-testkit/src/main/resources/reference.conf
@ -103,30 +103,30 @@ A custom ``application.conf`` might look like this::
# Copy in parts of the reference files and modify as you please. # Copy in parts of the reference files and modify as you please.
akka { akka {
# Event handlers to register at boot time (Logging$DefaultLogger logs to STDOUT)
event-handlers = ["akka.event.slf4j.Slf4jEventHandler"] event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
loglevel = DEBUG # Options: ERROR, WARNING, INFO, DEBUG
# this level is used by the configured loggers (see "event-handlers") as soon
# as they have been started; before that, see "stdout-loglevel"
stdout-loglevel = DEBUG # Loglevel for the very basic logger activated during AkkaApplication startup
# Comma separated list of the enabled modules. # Log level used by the configured loggers (see "event-handlers") as soon
enabled-modules = ["camel", "remote"] # as they have been started; before that, see "stdout-loglevel"
# Options: ERROR, WARNING, INFO, DEBUG
loglevel = DEBUG
# These boot classes are loaded (and created) automatically when the Akka Microkernel boots up # Log level for the very basic logger activated during AkkaApplication startup
# Can be used to bootstrap your application(s) # Options: ERROR, WARNING, INFO, DEBUG
# Should be the FQN (Fully Qualified Name) of the boot class which needs to have a default constructor stdout-loglevel = DEBUG
boot = ["sample.camel.Boot",
"sample.myservice.Boot"]
actor { actor {
default-dispatcher { default-dispatcher {
throughput = 10 # Throughput for default Dispatcher, set to 1 for as fair as possible # Throughput for default Dispatcher, set to 1 for as fair as possible
throughput = 10
} }
} }
remote { remote {
server { server {
port = 2562 # The port clients should connect to. Default is 2552 (AKKA) # The port clients should connect to. Default is 2552 (AKKA)
port = 2562
} }
} }
} }
@ -136,7 +136,7 @@ Config file format
------------------ ------------------
The configuration file syntax is described in the `HOCON <https://github.com/typesafehub/config/blob/master/HOCON.md>`_ The configuration file syntax is described in the `HOCON <https://github.com/typesafehub/config/blob/master/HOCON.md>`_
specification. Note that it supports three formats; conf, json, and properties. specification. Note that it supports three formats; conf, json, and properties.
Including files Including files
@ -145,7 +145,7 @@ Including files
Sometimes it can be useful to include another configuration file, for example if you have one ``application.conf`` with all Sometimes it can be useful to include another configuration file, for example if you have one ``application.conf`` with all
environment independent settings and then override some settings for specific environments. environment independent settings and then override some settings for specific environments.
Specifying system property with ``-Dconfig.resource=/dev.conf`` will load the ``dev.conf`` file, which includes the ``application.conf`` Specifying system property with ``-Dconfig.resource=/dev.conf`` will load the ``dev.conf`` file, which includes the ``application.conf``
dev.conf: dev.conf:
@ -166,6 +166,6 @@ specification.
Logging of Configuration Logging of Configuration
------------------------ ------------------------
If the system or config property ``akka.logConfigOnStart`` is set to ``on``, then the If the system or config property ``akka.logConfigOnStart`` is set to ``on``, then the
complete configuration at INFO level when the actor system is started. This is useful complete configuration at INFO level when the actor system is started. This is useful
when you are uncertain of what configuration is used. when you are uncertain of what configuration is used.

View file

@ -6,7 +6,7 @@ Dispatchers (Java)
.. sidebar:: Contents .. sidebar:: Contents
.. contents:: :local: .. contents:: :local:
The Dispatcher is an important piece that allows you to configure the right semantics and parameters for optimal performance, throughput and scalability. Different Actors have different needs. The Dispatcher is an important piece that allows you to configure the right semantics and parameters for optimal performance, throughput and scalability. Different Actors have different needs.
Akka supports dispatchers for both event-driven lightweight threads, allowing creation of millions of threads on a single workstation, and thread-based Actors, where each dispatcher is bound to a dedicated OS thread. Akka supports dispatchers for both event-driven lightweight threads, allowing creation of millions of threads on a single workstation, and thread-based Actors, where each dispatcher is bound to a dedicated OS thread.
@ -44,7 +44,7 @@ There are 4 different types of message dispatchers:
It is recommended to define the dispatcher in :ref:`configuration` to allow for tuning for different environments. It is recommended to define the dispatcher in :ref:`configuration` to allow for tuning for different environments.
Example of a custom event-based dispatcher, which can be fetched with ``system.dispatcherFactory().lookup("my-dispatcher")`` Example of a custom event-based dispatcher, which can be fetched with ``system.dispatcherFactory().lookup("my-dispatcher")``
as in the example above: as in the example above:
.. includecode:: ../scala/code/akka/docs/dispatcher/DispatcherDocSpec.scala#my-dispatcher-config .. includecode:: ../scala/code/akka/docs/dispatcher/DispatcherDocSpec.scala#my-dispatcher-config
@ -115,7 +115,7 @@ Priority event-based
^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^
Sometimes it's useful to be able to specify priority order of messages, that is done by using Dispatcher and supply Sometimes it's useful to be able to specify priority order of messages, that is done by using Dispatcher and supply
an UnboundedPriorityMailbox or BoundedPriorityMailbox with a ``java.util.Comparator[Envelope]`` or use a an UnboundedPriorityMailbox or BoundedPriorityMailbox with a ``java.util.Comparator[Envelope]`` or use a
``akka.dispatch.PriorityGenerator`` (recommended). ``akka.dispatch.PriorityGenerator`` (recommended).
Creating a Dispatcher using PriorityGenerator: Creating a Dispatcher using PriorityGenerator:
@ -129,9 +129,9 @@ Work-sharing event-based
The ``BalancingDispatcher`` is a variation of the ``Dispatcher`` in which Actors of the same type can be set up to The ``BalancingDispatcher`` is a variation of the ``Dispatcher`` in which Actors of the same type can be set up to
share this dispatcher and during execution time the different actors will steal messages from other actors if they share this dispatcher and during execution time the different actors will steal messages from other actors if they
have less messages to process. have less messages to process.
Although the technique used in this implementation is commonly known as "work stealing", the actual implementation is probably Although the technique used in this implementation is commonly known as "work stealing", the actual implementation is probably
best described as "work donating" because the actor of which work is being stolen takes the initiative. best described as "work donating" because the actor of which work is being stolen takes the initiative.
This can be a great way to improve throughput at the cost of a little higher latency. This can be a great way to improve throughput at the cost of a little higher latency.
.. includecode:: ../scala/code/akka/docs/dispatcher/DispatcherDocSpec.scala#my-balancing-config .. includecode:: ../scala/code/akka/docs/dispatcher/DispatcherDocSpec.scala#my-balancing-config
@ -154,8 +154,9 @@ if not specified otherwise.
akka { akka {
actor { actor {
default-dispatcher { default-dispatcher {
task-queue-size = 1000 # If negative (or zero) then an unbounded mailbox is used (default) # If negative (or zero) then an unbounded mailbox is used (default)
# If positive then a bounded mailbox is used and the capacity is set to the number specified # If positive then a bounded mailbox is used and the capacity is set to the number specified
task-queue-size = 1000
} }
} }
} }

View file

@ -25,14 +25,14 @@ The source object is translated to a String according to the following rules:
* in case of a class an approximation of its simpleName * in case of a class an approximation of its simpleName
* and in all other cases the simpleName of its class * and in all other cases the simpleName of its class
The log message may contain argument placeholders ``{}``, which will be substituted if the log level The log message may contain argument placeholders ``{}``, which will be substituted if the log level
is enabled. is enabled.
Event Handler Event Handler
============= =============
Logging is performed asynchronously through an event bus. You can configure which event handlers that should Logging is performed asynchronously through an event bus. You can configure which event handlers that should
subscribe to the logging events. That is done using the 'event-handlers' element in the :ref:`configuration`. subscribe to the logging events. That is done using the 'event-handlers' element in the :ref:`configuration`.
Here you can also define the log level. Here you can also define the log level.
.. code-block:: ruby .. code-block:: ruby
@ -40,16 +40,17 @@ Here you can also define the log level.
akka { akka {
# Event handlers to register at boot time (Logging$DefaultLogger logs to STDOUT) # Event handlers to register at boot time (Logging$DefaultLogger logs to STDOUT)
event-handlers = ["akka.event.Logging$DefaultLogger"] event-handlers = ["akka.event.Logging$DefaultLogger"]
loglevel = "DEBUG" # Options: ERROR, WARNING, INFO, DEBUG # Options: ERROR, WARNING, INFO, DEBUG
loglevel = "DEBUG"
} }
The default one logs to STDOUT and is registered by default. It is not intended to be used for production. There is also an :ref:`slf4j-java` The default one logs to STDOUT and is registered by default. It is not intended to be used for production. There is also an :ref:`slf4j-java`
event handler available in the 'akka-slf4j' module. event handler available in the 'akka-slf4j' module.
Example of creating a listener: Example of creating a listener:
.. includecode:: code/akka/docs/event/LoggingDocTestBase.java .. includecode:: code/akka/docs/event/LoggingDocTestBase.java
:include: imports,imports-listener,my-event-listener :include: imports,imports-listener,my-event-listener
.. _slf4j-java: .. _slf4j-java:
@ -57,7 +58,7 @@ Example of creating a listener:
SLF4J SLF4J
===== =====
Akka provides an event handler for `SL4FJ <http://www.slf4j.org/>`_. This module is available in the 'akka-slf4j.jar'. Akka provides an event handler for `SL4FJ <http://www.slf4j.org/>`_. This module is available in the 'akka-slf4j.jar'.
It has one single dependency; the slf4j-api jar. In runtime you also need a SLF4J backend, we recommend `Logback <http://logback.qos.ch/>`_: It has one single dependency; the slf4j-api jar. In runtime you also need a SLF4J backend, we recommend `Logback <http://logback.qos.ch/>`_:
.. code-block:: xml .. code-block:: xml
@ -69,10 +70,10 @@ It has one single dependency; the slf4j-api jar. In runtime you also need a SLF4
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
You need to enable the Slf4jEventHandler in the 'event-handlers' element in You need to enable the Slf4jEventHandler in the 'event-handlers' element in
the :ref:`configuration`. Here you can also define the log level of the event bus. the :ref:`configuration`. Here you can also define the log level of the event bus.
More fine grained log levels can be defined in the configuration of the SLF4J backend More fine grained log levels can be defined in the configuration of the SLF4J backend
(e.g. logback.xml). The String representation of the source object that is used when (e.g. logback.xml). The String representation of the source object that is used when
creating the ``LoggingAdapter`` correspond to the name of the SL4FJ logger. creating the ``LoggingAdapter`` correspond to the name of the SL4FJ logger.
.. code-block:: ruby .. code-block:: ruby
@ -89,9 +90,9 @@ Since the logging is done asynchronously the thread in which the logging was per
Mapped Diagnostic Context (MDC) with attribute name ``sourceThread``. Mapped Diagnostic Context (MDC) with attribute name ``sourceThread``.
With Logback the thread name is available with ``%X{sourceThread}`` specifier within the pattern layout configuration:: With Logback the thread name is available with ``%X{sourceThread}`` specifier within the pattern layout configuration::
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout> <layout>
<pattern>%date{ISO8601} %-5level %logger{36} %X{sourceThread} - %msg%n</pattern> <pattern>%date{ISO8601} %-5level %logger{36} %X{sourceThread} - %msg%n</pattern>
</layout> </layout>
</appender> </appender>

View file

@ -1528,7 +1528,8 @@ when camel is added to the enabled-modules list in :ref:`configuration`, for exa
akka { akka {
... ...
enabled-modules = ["camel"] # Options: ["remote", "camel", "http"] # Options: ["remote", "camel", "http"]
enabled-modules = ["camel"]
... ...
} }

View file

@ -16,27 +16,35 @@ object DispatcherDocSpec {
val config = """ val config = """
//#my-dispatcher-config //#my-dispatcher-config
my-dispatcher { my-dispatcher {
type = Dispatcher # Dispatcher is the name of the event-based dispatcher # Dispatcher is the name of the event-based dispatcher
daemonic = off # Toggles whether the threads created by this dispatcher should be daemons or not type = Dispatcher
core-pool-size-min = 2 # minimum number of threads to cap factor-based core number to # Toggles whether the threads created by this dispatcher should be daemons or not
core-pool-size-factor = 2.0 # No of core threads ... ceil(available processors * factor) daemonic = off
core-pool-size-max = 10 # maximum number of threads to cap factor-based number to # minimum number of threads to cap factor-based core number to
throughput = 100 # Throughput defines the number of messages that are processed in a batch before the core-pool-size-min = 2
# thread is returned to the pool. Set to 1 for as fair as possible. # No of core threads ... ceil(available processors * factor)
core-pool-size-factor = 2.0
# maximum number of threads to cap factor-based number to
core-pool-size-max = 10
# Throughput defines the number of messages that are processed in a batch before the
# thread is returned to the pool. Set to 1 for as fair as possible.
throughput = 100
} }
//#my-dispatcher-config //#my-dispatcher-config
//#my-bounded-config //#my-bounded-config
my-dispatcher-bounded-queue { my-dispatcher-bounded-queue {
type = Dispatcher type = Dispatcher
core-pool-size-factor = 8.0 core-pool-size-factor = 8.0
max-pool-size-factor = 16.0 max-pool-size-factor = 16.0
task-queue-size = 100 # Specifies the bounded capacity of the task queue # Specifies the bounded capacity of the task queue
task-queue-type = "array" # Specifies which type of task queue will be used, can be "array" or "linked" (default) task-queue-size = 100
# Specifies which type of task queue will be used, can be "array" or "linked" (default)
task-queue-type = "array"
throughput = 3 throughput = 3
} }
//#my-bounded-config //#my-bounded-config
//#my-balancing-config //#my-balancing-config
my-balancing-dispatcher { my-balancing-dispatcher {
type = BalancingDispatcher type = BalancingDispatcher

View file

@ -6,7 +6,7 @@ Dispatchers (Scala)
.. sidebar:: Contents .. sidebar:: Contents
.. contents:: :local: .. contents:: :local:
The Dispatcher is an important piece that allows you to configure the right semantics and parameters for optimal performance, throughput and scalability. Different Actors have different needs. The Dispatcher is an important piece that allows you to configure the right semantics and parameters for optimal performance, throughput and scalability. Different Actors have different needs.
Akka supports dispatchers for both event-driven lightweight threads, allowing creation of millions of threads on a single workstation, and thread-based Actors, where each dispatcher is bound to a dedicated OS thread. Akka supports dispatchers for both event-driven lightweight threads, allowing creation of millions of threads on a single workstation, and thread-based Actors, where each dispatcher is bound to a dedicated OS thread.
@ -127,9 +127,9 @@ Work-sharing event-based
The ``BalancingDispatcher`` is a variation of the ``Dispatcher`` in which Actors of the same type can be set up to The ``BalancingDispatcher`` is a variation of the ``Dispatcher`` in which Actors of the same type can be set up to
share this dispatcher and during execution time the different actors will steal messages from other actors if they share this dispatcher and during execution time the different actors will steal messages from other actors if they
have less messages to process. have less messages to process.
Although the technique used in this implementation is commonly known as "work stealing", the actual implementation is probably Although the technique used in this implementation is commonly known as "work stealing", the actual implementation is probably
best described as "work donating" because the actor of which work is being stolen takes the initiative. best described as "work donating" because the actor of which work is being stolen takes the initiative.
This can be a great way to improve throughput at the cost of a little higher latency. This can be a great way to improve throughput at the cost of a little higher latency.
.. includecode:: code/akka/docs/dispatcher/DispatcherDocSpec.scala#my-balancing-config .. includecode:: code/akka/docs/dispatcher/DispatcherDocSpec.scala#my-balancing-config
@ -152,8 +152,9 @@ if not specified otherwise.
akka { akka {
actor { actor {
default-dispatcher { default-dispatcher {
task-queue-size = 1000 # If negative (or zero) then an unbounded mailbox is used (default) # If negative (or zero) then an unbounded mailbox is used (default)
# If positive then a bounded mailbox is used and the capacity is set to the number specified # If positive then a bounded mailbox is used and the capacity is set to the number specified
task-queue-size = 1000
} }
} }
} }

View file

@ -21,7 +21,7 @@ For convenience you can mixin the ``log`` member into actors, instead of definin
.. code-block:: scala .. code-block:: scala
class MyActor extends Actor with akka.actor.ActorLogging { class MyActor extends Actor with akka.actor.ActorLogging {
The second parameter to the ``Logging`` is the source of this logging channel. The second parameter to the ``Logging`` is the source of this logging channel.
The source object is translated to a String according to the following rules: The source object is translated to a String according to the following rules:
@ -31,14 +31,14 @@ The source object is translated to a String according to the following rules:
* in case of a class an approximation of its simpleName * in case of a class an approximation of its simpleName
* and in all other cases the simpleName of its class * and in all other cases the simpleName of its class
The log message may contain argument placeholders ``{}``, which will be substituted if the log level The log message may contain argument placeholders ``{}``, which will be substituted if the log level
is enabled. is enabled.
Event Handler Event Handler
============= =============
Logging is performed asynchronously through an event bus. You can configure which event handlers that should Logging is performed asynchronously through an event bus. You can configure which event handlers that should
subscribe to the logging events. That is done using the 'event-handlers' element in the :ref:`configuration`. subscribe to the logging events. That is done using the 'event-handlers' element in the :ref:`configuration`.
Here you can also define the log level. Here you can also define the log level.
.. code-block:: ruby .. code-block:: ruby
@ -46,10 +46,11 @@ Here you can also define the log level.
akka { akka {
# Event handlers to register at boot time (Logging$DefaultLogger logs to STDOUT) # Event handlers to register at boot time (Logging$DefaultLogger logs to STDOUT)
event-handlers = ["akka.event.Logging$DefaultLogger"] event-handlers = ["akka.event.Logging$DefaultLogger"]
loglevel = "DEBUG" # Options: ERROR, WARNING, INFO, DEBUG # Options: ERROR, WARNING, INFO, DEBUG
loglevel = "DEBUG"
} }
The default one logs to STDOUT and is registered by default. It is not intended to be used for production. There is also an :ref:`slf4j-scala` The default one logs to STDOUT and is registered by default. It is not intended to be used for production. There is also an :ref:`slf4j-scala`
event handler available in the 'akka-slf4j' module. event handler available in the 'akka-slf4j' module.
Example of creating a listener: Example of creating a listener:
@ -63,7 +64,7 @@ Example of creating a listener:
SLF4J SLF4J
===== =====
Akka provides an event handler for `SL4FJ <http://www.slf4j.org/>`_. This module is available in the 'akka-slf4j.jar'. Akka provides an event handler for `SL4FJ <http://www.slf4j.org/>`_. This module is available in the 'akka-slf4j.jar'.
It has one single dependency; the slf4j-api jar. In runtime you also need a SLF4J backend, we recommend `Logback <http://logback.qos.ch/>`_: It has one single dependency; the slf4j-api jar. In runtime you also need a SLF4J backend, we recommend `Logback <http://logback.qos.ch/>`_:
.. code-block:: scala .. code-block:: scala
@ -71,10 +72,10 @@ It has one single dependency; the slf4j-api jar. In runtime you also need a SLF4
lazy val logback = "ch.qos.logback" % "logback-classic" % "1.0.0" % "runtime" lazy val logback = "ch.qos.logback" % "logback-classic" % "1.0.0" % "runtime"
You need to enable the Slf4jEventHandler in the 'event-handlers' element in You need to enable the Slf4jEventHandler in the 'event-handlers' element in
the :ref:`configuration`. Here you can also define the log level of the event bus. the :ref:`configuration`. Here you can also define the log level of the event bus.
More fine grained log levels can be defined in the configuration of the SLF4J backend More fine grained log levels can be defined in the configuration of the SLF4J backend
(e.g. logback.xml). The String representation of the source object that is used when (e.g. logback.xml). The String representation of the source object that is used when
creating the ``LoggingAdapter`` correspond to the name of the SL4FJ logger. creating the ``LoggingAdapter`` correspond to the name of the SL4FJ logger.
.. code-block:: ruby .. code-block:: ruby
@ -91,9 +92,9 @@ Since the logging is done asynchronously the thread in which the logging was per
Mapped Diagnostic Context (MDC) with attribute name ``sourceThread``. Mapped Diagnostic Context (MDC) with attribute name ``sourceThread``.
With Logback the thread name is available with ``%X{sourceThread}`` specifier within the pattern layout configuration:: With Logback the thread name is available with ``%X{sourceThread}`` specifier within the pattern layout configuration::
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout> <layout>
<pattern>%date{ISO8601} %-5level %logger{36} %X{sourceThread} - %msg%n</pattern> <pattern>%date{ISO8601} %-5level %logger{36} %X{sourceThread} - %msg%n</pattern>
</layout> </layout>
</appender> </appender>

View file

@ -4,7 +4,7 @@ Typed Actors (Scala)
.. sidebar:: Contents .. sidebar:: Contents
.. contents:: :local: .. contents:: :local:
The Typed Actors are implemented through `Typed Actors <http://en.wikipedia.org/wiki/Active_object>`_. It uses AOP through `AspectWerkz <http://aspectwerkz.codehaus.org/>`_ to turn regular POJOs into asynchronous non-blocking Actors with semantics of the Actor Model. Each method dispatch is turned into a message that is put on a queue to be processed by the Typed Actor sequentially one by one. The Typed Actors are implemented through `Typed Actors <http://en.wikipedia.org/wiki/Active_object>`_. It uses AOP through `AspectWerkz <http://aspectwerkz.codehaus.org/>`_ to turn regular POJOs into asynchronous non-blocking Actors with semantics of the Actor Model. Each method dispatch is turned into a message that is put on a queue to be processed by the Typed Actor sequentially one by one.
If you are using the `Spring Framework <http://springsource.org>`_ then take a look at Akka's `Spring integration <spring-integration>`_. If you are using the `Spring Framework <http://springsource.org>`_ then take a look at Akka's `Spring integration <spring-integration>`_.
@ -182,7 +182,8 @@ Akka can help you in this regard. It allows you to turn on an option for seriali
akka { akka {
actor { actor {
serialize-messages = on # does a deep clone of messages to ensure immutability # does a deep clone of messages to ensure immutability
serialize-messages = on
} }
} }

View file

@ -3,7 +3,7 @@
################################################## ##################################################
# This the reference config file has all the default settings. # This the reference config file has all the default settings.
# Make your edits/overrides in your akka.conf. # Make your edits/overrides in your application.conf.
akka { akka {
actor { actor {

View file

@ -3,7 +3,7 @@
############################################# #############################################
# This the reference config file has all the default settings. # This the reference config file has all the default settings.
# Make your edits/overrides in your akka.conf. # Make your edits/overrides in your application.conf.
akka { akka {
actor { actor {

View file

@ -3,19 +3,23 @@
################################################ ################################################
# This the reference config file has all the default settings. # This the reference config file has all the default settings.
# Make your edits/overrides in your akka.conf. # Make your edits/overrides in your application.conf.
akka { akka {
actor { actor {
mailbox { mailbox {
mongodb { mongodb {
# Any specified collection name will be used as a prefix for collections that use durable mongo mailboxes # Any specified collection name will be used as a prefix for collections that use durable mongo mailboxes
uri = "mongodb://localhost/akka.mailbox" # Follow Mongo URI Spec - http://www.mongodb.org/display/DOCS/Connections # Follow Mongo URI Spec - http://www.mongodb.org/display/DOCS/Connections
uri = "mongodb://localhost/akka.mailbox"
# Configurable timeouts for certain ops # Configurable timeouts for certain ops
timeout { timeout {
read = 3000ms # time to wait for a read to succeed before timing out the future # time to wait for a read to succeed before timing out the future
write = 3000ms # time to wait for a write to succeed before timing out the future read = 3000ms
# time to wait for a write to succeed before timing out the future
write = 3000ms
} }
} }
} }

View file

@ -3,7 +3,7 @@
############################################## ##############################################
# This the reference config file has all the default settings. # This the reference config file has all the default settings.
# Make your edits/overrides in your akka.conf. # Make your edits/overrides in your application.conf.
akka { akka {
actor { actor {

View file

@ -3,7 +3,7 @@
################################################## ##################################################
# This the reference config file has all the default settings. # This the reference config file has all the default settings.
# Make your edits/overrides in your akka.conf. # Make your edits/overrides in your application.conf.
akka { akka {
actor { actor {

View file

@ -3,7 +3,7 @@
##################################### #####################################
# This the reference config file has all the default settings. # This the reference config file has all the default settings.
# Make your edits/overrides in your akka.conf. # Make your edits/overrides in your application.conf.
akka { akka {
@ -13,18 +13,22 @@ akka {
default { default {
remote = "" # if this is set to a valid remote address, the named actor will be deployed at that node # if this is set to a valid remote address, the named actor will be deployed at that node
# e.g. "akka://sys@host:port" # e.g. "akka://sys@host:port"
remote = ""
target { target {
nodes = [] # A list of hostnames and ports for instantiating the children of a non-direct router
# The format should be on "akka://sys@host:port", where: # A list of hostnames and ports for instantiating the children of a non-direct router
# - sys is the remote actor system name # The format should be on "akka://sys@host:port", where:
# - hostname can be either hostname or IP address the remote actor should connect to # - sys is the remote actor system name
# - port should be the port for the remote server on the other node # - hostname can be either hostname or IP address the remote actor should connect to
# The number of actor instances to be spawned is still taken from the nr-of-instances # - port should be the port for the remote server on the other node
# setting as for local routers; the instances will be distributed round-robin among the # The number of actor instances to be spawned is still taken from the nr-of-instances
# given nodes. # setting as for local routers; the instances will be distributed round-robin among the
# given nodes.
nodes = []
} }
} }
} }
@ -35,50 +39,76 @@ akka {
use-compression = off use-compression = off
secure-cookie = "" # Generate your own with '$AKKA_HOME/scripts/generate_config_with_secure_cookie.sh' # Generate your own with '$AKKA_HOME/scripts/generate_config_with_secure_cookie.sh'
# or using 'akka.util.Crypt.generateSecureCookie' # or using 'akka.util.Crypt.generateSecureCookie'
secure-cookie = ""
remote-daemon-ack-timeout = 30s # Timeout for ACK of cluster operations, lik checking actor out etc. # Timeout for ACK of cluster operations, lik checking actor out etc.
remote-daemon-ack-timeout = 30s
use-passive-connections = on # Reuse inbound connections for outbound messages # Reuse inbound connections for outbound messages
use-passive-connections = on
# accrual failure detection config
failure-detector {
# defines the failure detector threshold
# A low threshold is prone to generate many wrong suspicions but ensures a
# quick detection in the event of a real crash. Conversely, a high threshold
# generates fewer mistakes but needs more time to detect actual crashes
threshold = 8
failure-detector { # accrual failure detection config
threshold = 8 # defines the failure detector threshold
# A low threshold is prone to generate many wrong suspicions but ensures a
# quick detection in the event of a real crash. Conversely, a high threshold
# generates fewer mistakes but needs more time to detect actual crashes
max-sample-size = 1000 max-sample-size = 1000
} }
gossip { gossip {
initialDelay = 5s initialDelay = 5s
frequency = 1s frequency = 1s
} }
compute-grid-dispatcher { # The dispatcher used for remote system messages # The dispatcher used for remote system messages
name = ComputeGridDispatcher # defaults to same settings as default-dispatcher compute-grid-dispatcher {
# defaults to same settings as default-dispatcher
name = ComputeGridDispatcher
} }
server { server {
hostname = "" # The hostname or ip to bind the remoting to, InetAddress.getLocalHost.getHostAddress is used if empty # The hostname or ip to bind the remoting to, InetAddress.getLocalHost.getHostAddress is used if empty
port = 2552 # The default remote server port clients should connect to. Default is 2552 (AKKA) hostname = ""
message-frame-size = 1 MiB # Increase this if you want to be able to send messages with large payloads
connection-timeout = 120s # Timeout duration # The default remote server port clients should connect to. Default is 2552 (AKKA)
require-cookie = off # Should the remote server require that it peers share the same secure-cookie (defined in the 'remote' section)? port = 2552
untrusted-mode = off # Enable untrusted mode for full security of server managed actors, allows untrusted clients to connect.
backlog = 4096 # Sets the size of the connection backlog # Increase this if you want to be able to send messages with large payloads
message-frame-size = 1 MiB
# Timeout duration
connection-timeout = 120s
# Should the remote server require that it peers share the same secure-cookie (defined in the 'remote' section)?
require-cookie = off
# Enable untrusted mode for full security of server managed actors, allows untrusted clients to connect.
untrusted-mode = off
# Sets the size of the connection backlog
backlog = 4096
} }
client { client {
buffering { buffering {
retry-message-send-on-failure = off # Should message buffering on remote client error be used (buffer flushed on successful reconnect) # Should message buffering on remote client error be used (buffer flushed on successful reconnect)
capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default) retry-message-send-on-failure = off
# If positive then a bounded mailbox is used and the capacity is set using the property
# If negative (or zero) then an unbounded mailbox is used (default)
# If positive then a bounded mailbox is used and the capacity is set using the property
capacity = -1
} }
reconnect-delay = 5s reconnect-delay = 5s
read-timeout = 3600s read-timeout = 3600s
message-frame-size = 1 MiB message-frame-size = 1 MiB
reconnection-time-window = 600s # Maximum time window that a client should try to reconnect for # Maximum time window that a client should try to reconnect for
reconnection-time-window = 600s
} }
} }

View file

@ -3,19 +3,21 @@
################################## ##################################
# This the reference config file has all the default settings. # This the reference config file has all the default settings.
# Make your edits/overrides in your akka.conf. # Make your edits/overrides in your application.conf.
akka { akka {
stm { stm {
fair = on # Should global transactions be fair or non-fair (non fair yield better performance) # Should global transactions be fair or non-fair (non fair yield better performance)
fair = on
max-retries = 1000 max-retries = 1000
timeout = 5s # Default timeout for blocking transactions and transaction set # Default timeout for blocking transactions and transaction set
write-skew = on timeout = 5s
blocking-allowed = off write-skew = on
interruptible = off blocking-allowed = off
speculative = on interruptible = off
quick-release = on speculative = on
quick-release = on
propagation = "requires" propagation = "requires"
trace-level = "none" trace-level = "none"
} }

View file

@ -3,12 +3,15 @@
###################################### ######################################
# This the reference config file has all the default settings. # This the reference config file has all the default settings.
# Make your edits/overrides in your akka.conf. # Make your edits/overrides in your application.conf.
akka { akka {
test { test {
timefactor = 1.0 # factor by which to scale timeouts during tests, e.g. to account for shared build system load # factor by which to scale timeouts during tests, e.g. to account for shared build system load
filter-leeway = 3s # duration of EventFilter.intercept waits after the block is finished until all required messages are received timefactor = 1.0
single-expect-default = 3s # duration to wait in expectMsg and friends outside of within() block by default # duration of EventFilter.intercept waits after the block is finished until all required messages are received
filter-leeway = 3s
# duration to wait in expectMsg and friends outside of within() block by default
single-expect-default = 3s
} }
} }