aboutsummaryrefslogtreecommitdiffstats
path: root/config-lib/src/main/java
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /config-lib/src/main/java
Publish
Diffstat (limited to 'config-lib/src/main/java')
-rw-r--r--config-lib/src/main/java/com/yahoo/config/BooleanNode.java50
-rw-r--r--config-lib/src/main/java/com/yahoo/config/ChangesRequiringRestart.java162
-rw-r--r--config-lib/src/main/java/com/yahoo/config/ConfigBuilder.java11
-rw-r--r--config-lib/src/main/java/com/yahoo/config/ConfigInstance.java122
-rw-r--r--config-lib/src/main/java/com/yahoo/config/ConfigurationRuntimeException.java21
-rw-r--r--config-lib/src/main/java/com/yahoo/config/DoubleNode.java51
-rw-r--r--config-lib/src/main/java/com/yahoo/config/EnumNode.java24
-rw-r--r--config-lib/src/main/java/com/yahoo/config/FileNode.java42
-rwxr-xr-xconfig-lib/src/main/java/com/yahoo/config/FileReference.java67
-rw-r--r--config-lib/src/main/java/com/yahoo/config/InnerNode.java171
-rw-r--r--config-lib/src/main/java/com/yahoo/config/InnerNodeVector.java47
-rw-r--r--config-lib/src/main/java/com/yahoo/config/IntegerNode.java52
-rw-r--r--config-lib/src/main/java/com/yahoo/config/LeafNode.java120
-rw-r--r--config-lib/src/main/java/com/yahoo/config/LeafNodeMaps.java65
-rw-r--r--config-lib/src/main/java/com/yahoo/config/LeafNodeVector.java79
-rwxr-xr-xconfig-lib/src/main/java/com/yahoo/config/LongNode.java53
-rw-r--r--config-lib/src/main/java/com/yahoo/config/Node.java29
-rw-r--r--config-lib/src/main/java/com/yahoo/config/NodeVector.java151
-rw-r--r--config-lib/src/main/java/com/yahoo/config/PathNode.java71
-rw-r--r--config-lib/src/main/java/com/yahoo/config/ReferenceNode.java83
-rw-r--r--config-lib/src/main/java/com/yahoo/config/Serializer.java31
-rw-r--r--config-lib/src/main/java/com/yahoo/config/StringNode.java123
-rw-r--r--config-lib/src/main/java/com/yahoo/config/package-info.java7
-rw-r--r--config-lib/src/main/java/com/yahoo/config/text/StringUtilities.java88
24 files changed, 1720 insertions, 0 deletions
diff --git a/config-lib/src/main/java/com/yahoo/config/BooleanNode.java b/config-lib/src/main/java/com/yahoo/config/BooleanNode.java
new file mode 100644
index 00000000000..8347b800272
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/BooleanNode.java
@@ -0,0 +1,50 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * The BooleanNode class represents a boolean in a configuration tree.
+ */
+public class BooleanNode extends LeafNode<Boolean> {
+ public BooleanNode() {
+ }
+
+ public BooleanNode(boolean value) {
+ super(true);
+ this.value = value;
+ }
+
+ public Boolean value() {
+ return value;
+ }
+
+ @Override
+ public String getValue() {
+ return "" + value;
+ }
+
+ @Override
+ public String toString() {
+ return getValue();
+ }
+
+ @Override
+ protected boolean doSetValue(@NonNull String value) {
+ if (! value.equalsIgnoreCase("false") && ! value.equalsIgnoreCase("true")) {
+ return false;
+ }
+ this.value = Boolean.valueOf(value);
+ return true;
+ }
+
+ @Override
+ void serialize(String name, Serializer serializer) {
+ serializer.serialize(name, value);
+ }
+
+ @Override
+ void serialize(Serializer serializer) {
+ serializer.serialize(value);
+ }
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/ChangesRequiringRestart.java b/config-lib/src/main/java/com/yahoo/config/ChangesRequiringRestart.java
new file mode 100644
index 00000000000..6c17a21c649
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/ChangesRequiringRestart.java
@@ -0,0 +1,162 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.stream.Collectors.joining;
+
+/**
+ * @author <a href="mailto:magnarn@yahoo-inc.com">Magnar Nedland</a>
+ *
+ * This class aggregates information about config changes that causes a restart to be required.
+ */
+public class ChangesRequiringRestart {
+ static class ReportLine {
+ private String name;
+ private final Node from;
+ private final Node to;
+ private final String comment;
+
+ public ReportLine(String name, Node from, Node to, String comment) {
+ this.name = name;
+ this.from = from;
+ this.to = to;
+ this.comment = comment;
+ }
+
+ public void addNamePrefix(String prefix) {
+ if (!name.isEmpty()) {
+ name = prefix + "." + name;
+ } else {
+ name = prefix;
+ }
+ }
+
+ private String getCommentAndName(String indent, String namePrefix) {
+ return indent + (comment.isEmpty()? "" : "# " + comment.replace("\n", "\n" + indent + "# ") + "\n" + indent)
+ + namePrefix + name;
+ }
+
+ private static String formatValue(String indent, Node n) {
+ String str = n.toString();
+ if (str.contains("\n")) { // Struct
+ str = "\n" + indent + " { " + str.replace("\n", "\n" + indent + " ") + " }";
+ }
+ return str;
+ }
+
+ @Override
+ public String toString() {
+ return toString("", "");
+ }
+
+ public String toString(String indent, String namePrefix) {
+ if (from == null) {
+ return getCommentAndName(indent, namePrefix) + " was added with value " + formatValue(indent, to);
+ } else if (to == null) {
+ return getCommentAndName(indent, namePrefix) + " with value " + formatValue(indent, from) + " was removed";
+ }
+ return getCommentAndName(indent, namePrefix) + " has changed from " + formatValue(indent, from) + " to " + formatValue(indent, to);
+ }
+ }
+
+ private ArrayList<ReportLine> report = new ArrayList<>();
+ private String componentName;
+
+ public ChangesRequiringRestart(String componentName) {
+ this.componentName = componentName;
+ }
+
+ public String getName() {
+ return componentName;
+ }
+
+ public ChangesRequiringRestart compare(Node from, Node to, String name, String comment) {
+ if (!from.equals(to)) {
+ report.add(new ReportLine(name, from, to, comment));
+ }
+ return this;
+ }
+
+ public void mergeChanges(String prefix, ChangesRequiringRestart childReport) {
+ for (ReportLine line : childReport.getReportLines()) {
+ line.addNamePrefix(prefix);
+ report.add(line);
+ }
+ }
+
+ /**
+ * Interface used to pass lambda functions from generated code to compareArray/-Map functions.
+ */
+ public interface CompareFunc {
+ // Generates a report based on a config change.
+ ChangesRequiringRestart getChangesRequiringRestart(Node from, Node to);
+ }
+
+ public ChangesRequiringRestart compareArray(List<? extends Node> from,
+ List<? extends Node> to,
+ String name,
+ String comment,
+ CompareFunc func) {
+ if (!from.equals(to)) {
+ int commonElements = Math.min(from.size(), to.size());
+ for (int i = 0; i < commonElements; ++i) {
+ ChangesRequiringRestart childReport = func.getChangesRequiringRestart(from.get(i), to.get(i));
+ String prefix = childReport.componentName + "[" + Integer.toString(i) + "]";
+ mergeChanges(prefix, childReport);
+ }
+ for (int i = commonElements; i < from.size(); ++i) {
+ report.add(new ReportLine(name + "[" + Integer.toString(i) + "]", from.get(i), null, comment));
+ }
+ for (int i = commonElements; i < to.size(); ++i) {
+ report.add(new ReportLine(name + "[" + Integer.toString(i) + "]", null, to.get(i), comment));
+ }
+ }
+ return this;
+ }
+
+ public ChangesRequiringRestart compareMap(Map<String, ? extends Node> from,
+ Map<String, ? extends Node> to,
+ String name,
+ String comment,
+ CompareFunc func) {
+ if (!from.equals(to)) {
+ for (String key : from.keySet()) {
+ if (to.containsKey(key)) {
+ ChangesRequiringRestart childReport = func.getChangesRequiringRestart(from.get(key), to.get(key));
+ String prefix = childReport.componentName + "{" + key + "}";
+ mergeChanges(prefix, childReport);
+ } else {
+ report.add(new ReportLine(name + "{" + key + "}", from.get(key), null, comment));
+ }
+ }
+ for (String key : to.keySet()) {
+ if (!from.containsKey(key)) {
+ report.add(new ReportLine(name + "{" + key + "}", null, to.get(key), comment));
+ }
+ }
+ }
+ return this;
+ }
+
+ List<ReportLine> getReportLines() {
+ return report;
+ }
+
+ @Override
+ public String toString() {
+ return toString("");
+ }
+
+ public String toString(String indent) {
+ return report.stream()
+ .map(line -> line.toString(indent, componentName + "."))
+ .collect(joining("\n"));
+ }
+
+ public boolean needsRestart() {
+ return !report.isEmpty();
+ }
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/ConfigBuilder.java b/config-lib/src/main/java/com/yahoo/config/ConfigBuilder.java
new file mode 100644
index 00000000000..dc90df9b12f
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/ConfigBuilder.java
@@ -0,0 +1,11 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+/**
+ * Root interface for all config builders.
+ *
+ * @author gjoranv
+ * @since 5.1.6
+ */
+public interface ConfigBuilder {
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/ConfigInstance.java b/config-lib/src/main/java/com/yahoo/config/ConfigInstance.java
new file mode 100644
index 00000000000..bb091a6b4e1
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/ConfigInstance.java
@@ -0,0 +1,122 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents an instance of an application config with a specific configId.
+ * <p>
+ * An instance of this class contains all values (represented by Nodes) for the config object as it
+ * is the superclass of the generated config class used by the client.
+ */
+public abstract class ConfigInstance extends InnerNode {
+
+ public interface Builder extends ConfigBuilder {
+ /**
+ * Dispatches a getConfig() call if this instance's producer is of the right type
+ * @param producer a config producer
+ * @return true if this instance's producer was the correct type, and hence a getConfig call was dispatched
+ */
+ public boolean dispatchGetConfig(Producer producer);
+
+ public String getDefName();
+ public String getDefNamespace();
+ public String getDefMd5();
+ }
+
+ public interface Producer {}
+
+ private String configMd5 = "";
+
+ String configId;
+
+ /**
+ * Gets the name of the given config instance
+ */
+ public static String getDefName(Class<?> type) {
+ return getStaticStringField(type, "CONFIG_DEF_NAME");
+ }
+
+ /**
+ * Gets the namespace of the given config instance
+ */
+ public static String getDefNamespace(Class<?> type) {
+ return getStaticStringField(type, "CONFIG_DEF_NAMESPACE");
+ }
+
+ /**
+ * Returns the serialized representation of the given node.
+ * <p>
+ * Declared static, instead of InnerNode member, to avoid a public 0-arg method with a commonly used name.
+ *
+ * @param node The inner node
+ * @return a list of strings, containing the serialized representation of this config
+ */
+ public static List<String> serialize(InnerNode node) {
+ List<String> ret = new ArrayList<>();
+ for (Map.Entry<String, LeafNode<?>> entry : getAllDescendantLeafNodes(node).entrySet()) {
+ ret.add(entry.getKey() + " " + entry.getValue().toString());
+ }
+ return ret;
+ }
+
+ public static void serialize(InnerNode node, Serializer serializer) {
+ serializeMap(node.getChildren(), serializer);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void serializeObject(String name, Object child, Serializer serializer) {
+ if (child instanceof InnerNode) {
+ Serializer childSerializer = serializer.createInner(name);
+ serialize((InnerNode) child, childSerializer);
+ } else if (child instanceof Map) {
+ Serializer mapSerializer = serializer.createMap(name);
+ serializeMap((Map<String, Object>)child, mapSerializer);
+ } else if (child instanceof NodeVector) {
+ Serializer arraySerializer = serializer.createArray(name);
+ serializeArray((NodeVector) child, arraySerializer);
+ } else if (child instanceof LeafNode) {
+ ((LeafNode) child).serialize(name, serializer);
+ }
+ }
+
+ private static void serializeMap(Map<String, Object> childMap, Serializer serializer) {
+ for (Map.Entry<String, Object> entry : childMap.entrySet()) {
+ String name = entry.getKey();
+ Object child = entry.getValue();
+ serializeObject(name, child, serializer);
+ }
+ }
+
+ private static void serializeArray(NodeVector<?> nodeVector, Serializer arraySerializer) {
+ for (Object child : nodeVector.vector) {
+ if (child instanceof InnerNode) {
+ Serializer childSerializer = arraySerializer.createInner();
+ serialize((InnerNode) child, childSerializer);
+ } else if (child instanceof LeafNode) {
+ ((LeafNode) child).serialize(arraySerializer);
+ }
+ }
+ }
+
+
+ public String getConfigMd5() {
+ return configMd5;
+ }
+
+ public void setConfigMd5(String configMd5) {
+ this.configMd5 = configMd5;
+ }
+
+ private static String getStaticStringField(Class<?> type, String fieldName) {
+ try {
+ return (String) type.getField(fieldName).get(null);
+ } catch (Exception e) {
+ throw new RuntimeException
+ (e.getMessage() + ": Static field " + fieldName + " not " + "accessible in " + type.getName());
+ }
+ }
+
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/ConfigurationRuntimeException.java b/config-lib/src/main/java/com/yahoo/config/ConfigurationRuntimeException.java
new file mode 100644
index 00000000000..56020906499
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/ConfigurationRuntimeException.java
@@ -0,0 +1,21 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+/**
+ * This exception is thrown on internal errors in the configuration system.
+ */
+@SuppressWarnings("serial")
+public class ConfigurationRuntimeException extends RuntimeException {
+ public ConfigurationRuntimeException(String message) {
+ super(message);
+ }
+
+ public ConfigurationRuntimeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ConfigurationRuntimeException(Throwable cause) {
+ super(cause);
+ }
+
+} \ No newline at end of file
diff --git a/config-lib/src/main/java/com/yahoo/config/DoubleNode.java b/config-lib/src/main/java/com/yahoo/config/DoubleNode.java
new file mode 100644
index 00000000000..6ff97df579f
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/DoubleNode.java
@@ -0,0 +1,51 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * The DoubleNode class represents a double in a configuration tree.
+ */
+public class DoubleNode extends LeafNode<Double> {
+ public DoubleNode() {
+ }
+
+ public DoubleNode(double value) {
+ super(true);
+ this.value = value;
+ }
+
+ public Double value() {
+ return value;
+ }
+
+ @Override
+ public String getValue() {
+ return "" + value;
+ }
+
+ @Override
+ public String toString() {
+ return getValue();
+ }
+
+ @Override
+ protected boolean doSetValue(@NonNull String value) {
+ try {
+ this.value = Double.parseDouble(value);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ @Override
+ void serialize(String name, Serializer serializer) {
+ serializer.serialize(name, value);
+ }
+
+ @Override
+ void serialize(Serializer serializer) {
+ serializer.serialize(value);
+ }
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/EnumNode.java b/config-lib/src/main/java/com/yahoo/config/EnumNode.java
new file mode 100644
index 00000000000..5443d063bbd
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/EnumNode.java
@@ -0,0 +1,24 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+/**
+ * The EnumNode class is a superclass for Enumerations in a configuration tree.
+ */
+public abstract class EnumNode<ENUM extends Enum<?>> extends LeafNode<ENUM> {
+ public EnumNode() {
+ }
+
+ public EnumNode(boolean b) {
+ super(b);
+ }
+
+ @Override
+ public String toString() {
+ return (value == null) ? "(null)" : value.toString();
+ }
+
+ @Override
+ public String getValue() {
+ return (value == null) ? null : value.toString();
+ }
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/FileNode.java b/config-lib/src/main/java/com/yahoo/config/FileNode.java
new file mode 100644
index 00000000000..0d6bccb59a5
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/FileNode.java
@@ -0,0 +1,42 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * Represents a 'file' in a {@link ConfigInstance}, usually a filename.
+ *
+ * @author gjoranv
+ */
+public class FileNode extends LeafNode<FileReference> {
+
+ public FileNode() {
+ }
+
+ public FileNode(String stringVal) {
+ super(true);
+ this.value = new FileReference(ReferenceNode.stripQuotes(stringVal));
+ }
+
+ public FileReference value() {
+ return value;
+ }
+
+ @Override
+ public String getValue() {
+ return value.value();
+ }
+
+ @Override
+ public String toString() {
+ return (value == null) ? "(null)" : '"' + getValue() + '"';
+ }
+
+ @Override
+ protected boolean doSetValue(@NonNull String stringVal) {
+ value = new FileReference(ReferenceNode.stripQuotes(stringVal));
+ return true;
+ }
+
+
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/FileReference.java b/config-lib/src/main/java/com/yahoo/config/FileReference.java
new file mode 100755
index 00000000000..de86fc09be7
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/FileReference.java
@@ -0,0 +1,67 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An immutable file reference that can only be created from classes within the same package.
+ * This is to prevent clients from creating arbitrary and invalid file references.
+ *
+ * @author tonytv
+ */
+public final class FileReference {
+
+ private final String value;
+
+ FileReference(String value) {
+ this.value = value;
+ }
+
+ public String value() {
+ return value;
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof FileReference &&
+ value.equals(((FileReference)other).value);
+ }
+
+ @Override
+ public String toString() {
+ return "file '" + value + "'";
+ }
+
+ public static List<String> toValues(Collection<FileReference> references) {
+ List<String> ret = new ArrayList<String>();
+ for (FileReference r: references) {
+ ret.add(r.value());
+ }
+ return ret;
+ }
+
+ public static Map<String, String> toValueMap(Map<String, FileReference> map) {
+ Map<String, String> ret = new LinkedHashMap<>();
+ for (Map.Entry<String, FileReference> e : map.entrySet()) {
+ ret.put(e.getKey(), e.getValue().value());
+ }
+ return ret;
+ }
+
+ public static FileReference mockFileReferenceForUnitTesting(File file) {
+ if (! file.exists())
+ throw new IllegalArgumentException("File '" + file.getAbsolutePath() + "' does not exist.");
+ return new FileReference(file.getPath());
+ }
+
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/InnerNode.java b/config-lib/src/main/java/com/yahoo/config/InnerNode.java
new file mode 100644
index 00000000000..eb7f36bbaea
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/InnerNode.java
@@ -0,0 +1,171 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Superclass for all inner nodes in a {@link ConfigInstance}.
+ * <p>
+ * This class cannot have non-private members because such members
+ * will interfere with the members in the generated subclass.
+ *
+ * @author gjoranv
+ */
+public abstract class InnerNode extends Node {
+
+ /**
+ * Creates a new InnerNode.
+ */
+ public InnerNode() {
+ }
+
+ @Override
+ public String toString() {
+ return mkString(ConfigInstance.serialize(this), "\n");
+ }
+
+ private static <T> String mkString(Collection<T> collection, String sep) {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+
+ for (T elem : collection) {
+ if (! first)
+ sb.append(sep);
+ first = false;
+ sb.append(elem);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Overrides {@link Node#postInitialize(String)}.
+ * Perform post initialization on this nodes children.
+ *
+ * @param configId The config id of this instance.
+ */
+ @Override
+ public void postInitialize(String configId) {
+ Map<String, Node> children = getChildrenWithVectorsFlattened();
+ for (Node node : children.values()) {
+ node.postInitialize(configId);
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this)
+ return true;
+
+ if ( !(other instanceof InnerNode) || (other.getClass() != this.getClass()))
+ return false;
+
+ Collection<Object> children = getChildren().values();
+ Collection<Object> otherChildren = ((InnerNode)other).getChildren().values();
+
+ Iterator<Object> e1 = children.iterator();
+ Iterator<Object> e2 = otherChildren.iterator();
+ while(e1.hasNext() && e2.hasNext()) {
+ Object o1 = e1.next();
+ Object o2 = e2.next();
+ if (!(o1 == null ? o2 == null : o1.equals(o2)))
+ return false;
+ }
+ return !(e1.hasNext() || e2.hasNext());
+ }
+
+ @Override
+ public int hashCode() {
+ int res = 17;
+ for (Object child : getChildren().values())
+ res = 31 * res + child.hashCode();
+ return res;
+ }
+
+ protected Map<String, Object> getChildren() {
+ HashMap<String, Object> ret = new LinkedHashMap<String, Object>();
+ Field fields[] = getClass().getDeclaredFields();
+ for (Field field : fields) {
+ field.setAccessible(true);
+ Object fieldValue;
+ try {
+ fieldValue = field.get(this);
+ } catch (IllegalAccessException e) {
+ throw new ConfigurationRuntimeException(e);
+ }
+ if (fieldValue instanceof Node
+ || fieldValue instanceof NodeVector<?>
+ || fieldValue instanceof Map<?,?>)
+ ret.put(field.getName(), fieldValue);
+ }
+ return ret;
+ }
+
+ /**
+ * Returns a flat map of this node's direct children, including all NodeVectors' elements.
+ * Keys are the node name, including index for vector elements, e.g. 'arr[0]'.
+ */
+ @SuppressWarnings("unchecked")
+ protected Map<String, Node> getChildrenWithVectorsFlattened() {
+ HashMap<String, Node> ret = new LinkedHashMap<String, Node>();
+
+ Map<String, Object> children = getChildren();
+ for (Map.Entry<String, Object> childEntry : children.entrySet()) {
+ String name = childEntry.getKey();
+ Object child = childEntry.getValue();
+ if (child instanceof NodeVector) {
+ addNodeVectorEntries(ret, name, (NodeVector<?>) child);
+ } else if (child instanceof Map<?,?>) {
+ addMapEntries(ret, name, (Map<String, Node>) child);
+ } else if (child instanceof Node) {
+ ret.put(name, (Node)child);
+ }
+ }
+ return ret;
+ }
+
+ private static void addMapEntries(HashMap<String, Node> ret, String name, Map<String, Node> map) {
+ for (Map.Entry<String, Node> entry : map.entrySet())
+ ret.put(name + "{" + entry.getKey() + "}", entry.getValue());
+ }
+
+
+ private static void addNodeVectorEntries(HashMap<String, Node> ret, String name, NodeVector<?> vector) {
+ for (int j = 0; j < vector.length(); j++)
+ ret.put(name + "[" + j + "]", (Node) vector.get(j));
+ }
+
+ protected static Map<String, LeafNode<?>> getAllDescendantLeafNodes(InnerNode node) {
+ return getAllDescendantLeafNodes("", node);
+ }
+
+ /**
+ * Generates a map of all leaf nodes, with full.paths[3] in key
+ *
+ * @param parentName Name of the parent node, can be empty.
+ * @param node The node to get leaf nodes for.
+ * @return map of leaf nodes
+ */
+ private static Map<String, LeafNode<?>> getAllDescendantLeafNodes(String parentName, InnerNode node) {
+ Map<String, LeafNode<?>> ret = new LinkedHashMap<String, LeafNode<?>>();
+ String prefix = parentName.isEmpty() ? "" : parentName + ".";
+ Map<String, Node> children = node.getChildrenWithVectorsFlattened();
+ for (Map.Entry<String, Node> childEntry : children.entrySet()) {
+ String name = childEntry.getKey();
+ String prefixedName = prefix + name;
+ name=name.replaceAll("\\[.*", "");
+ Node child = childEntry.getValue();
+ if (child instanceof LeafNode) {
+ ret.put(prefixedName, (LeafNode<?>) child);
+ } else if (child instanceof InnerNode) {
+ ret.putAll(getAllDescendantLeafNodes(prefixedName, (InnerNode) child));
+ }
+ }
+ return ret;
+ }
+
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/InnerNodeVector.java b/config-lib/src/main/java/com/yahoo/config/InnerNodeVector.java
new file mode 100644
index 00000000000..72ca0138a53
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/InnerNodeVector.java
@@ -0,0 +1,47 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import java.util.List;
+
+/**
+ * @author gjoranv
+ * @since 5.1.4
+ */
+public class InnerNodeVector<NODE extends InnerNode> extends NodeVector<NODE> {
+
+ NODE defaultNode;
+
+ /**
+ * Creates a new vector with the given default node.
+ */
+ // TODO: remove this ctor when the library uses reflection via builders, and resizing won't be necessary
+ public InnerNodeVector(NODE defaultNode) {
+ assert (defaultNode != null) : "The default node cannot be null";
+
+ this.defaultNode = defaultNode;
+ if (createNew() == null) {
+ throw new NullPointerException("Unable to duplicate the default node.");
+ }
+ }
+
+ public InnerNodeVector(List<NODE> nodes, NODE defaultNode) {
+ this(defaultNode);
+ for (NODE node : nodes) {
+ vector.add(node);
+ }
+ }
+
+ /**
+ * Creates a new Node by creating a new instance with the 0-argument constructor
+ */
+ // TODO: remove when the library uses reflection via builders
+ @SuppressWarnings("unchecked")
+ protected NODE createNew() {
+ try {
+ return (NODE) defaultNode.getClass().newInstance();
+ } catch (IllegalAccessException | InstantiationException ex) {
+ throw new ConfigurationRuntimeException(ex);
+ }
+ }
+
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/IntegerNode.java b/config-lib/src/main/java/com/yahoo/config/IntegerNode.java
new file mode 100644
index 00000000000..82db4cab030
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/IntegerNode.java
@@ -0,0 +1,52 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * The IntegerNode class represents an integer in a configuration tree.
+ */
+public class IntegerNode extends LeafNode<Integer> {
+
+ public IntegerNode() {
+ }
+
+ public IntegerNode(int value) {
+ super(true);
+ this.value = value;
+ }
+
+ public Integer value() {
+ return value;
+ }
+
+ @Override
+ public String getValue() {
+ return "" + value;
+ }
+
+ @Override
+ public String toString() {
+ return getValue();
+ }
+
+ @Override
+ protected boolean doSetValue(@NonNull String value) {
+ try {
+ this.value = Integer.parseInt(value);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ @Override
+ void serialize(String name, Serializer serializer) {
+ serializer.serialize(name, value);
+ }
+
+ @Override
+ void serialize(Serializer serializer) {
+ serializer.serialize(value);
+ }
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/LeafNode.java b/config-lib/src/main/java/com/yahoo/config/LeafNode.java
new file mode 100644
index 00000000000..24ec534e222
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/LeafNode.java
@@ -0,0 +1,120 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * Superclass for all leaf nodes in a {@link ConfigInstance}.
+ * <p>
+ * Subclasses represents leaf nodes with different types. These
+ * implementations should implement method value() with return-value
+ * corresponding to the actual type.
+ *
+ */
+public abstract class LeafNode<T> extends Node implements Cloneable {
+
+ protected boolean initialized;
+ protected T value;
+
+ /**
+ * Creates a new, uninitialized LeafNode
+ */
+ protected LeafNode() {
+ initialized = false;
+ }
+
+ /**
+ * Creates a new LeafNode.
+ *
+ * @param initialized true if this node is initialized.
+ */
+ protected LeafNode(boolean initialized) {
+ this.initialized = initialized;
+ }
+
+ public T value() {
+ return value;
+ }
+
+ /**
+ * Try to initialize this node with the given value. Returns true
+ * on success, false otherwise.
+ *
+ * @param value the string represention of the desired node value.
+ * @return true on success, false otherwise.
+ */
+ final boolean initialize(String value) {
+ boolean success = setValue(value);
+ initialized |= success;
+ return success;
+ }
+
+ /**
+ * Subclasses must implement this, in compliance with the rules given in the return tag.
+ *
+ * @return the String representation of the node value, or the string "(null)" if the value is null.
+ */
+ public abstract String toString();
+
+ /**
+ * Subclasses must implement this, in compliance with the rules given in the return tag.
+ *
+ * @return the String representation of the node value, or the 'null' object if the node value is null.
+ */
+ public abstract String getValue();
+
+ /**
+ * Sets the value based on a string representation. Returns false if the value could
+ * not be set from the given string.
+ * TODO: return void (see doSetValue)
+ *
+ * @param value the value to set
+ * @return true on success, false otherwise
+ * @throws IllegalArgumentException when value is null
+ */
+ protected final boolean setValue(String value) {
+ if (value == null)
+ throw new IllegalArgumentException("Null value is not allowed");
+ return doSetValue(value);
+ }
+
+ // TODO: should throw exception instead of return false.
+ protected abstract boolean doSetValue(@NonNull String value);
+
+ /**
+ * This method is meant for internal use in the configuration
+ * system. Overrides Object.clone().
+ *
+ * @return a new instance similar to this object.
+ */
+ @Override
+ protected Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new ConfigurationRuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (! (o instanceof LeafNode))
+ return false;
+
+ LeafNode<?> other = (LeafNode)o;
+ return value == null ? other.value == null : value().equals(other.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return (value == null) ? 0 : value.hashCode();
+ }
+
+ void serialize(String name, Serializer serializer) {
+ serializer.serialize(name, getValue());
+ }
+
+ void serialize(Serializer serializer) {
+ serializer.serialize(getValue());
+ }
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/LeafNodeMaps.java b/config-lib/src/main/java/com/yahoo/config/LeafNodeMaps.java
new file mode 100644
index 00000000000..789969662da
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/LeafNodeMaps.java
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author gjoranv
+ * @since 5.1.17
+ */
+public class LeafNodeMaps {
+
+ /**
+ * Converts a map of String→NODE to String→REAL, where REAL is the underlying value type.
+ */
+ public static <NODE extends LeafNode<REAL>, REAL>
+ Map<String, REAL> asValueMap(Map<String, NODE> input)
+ {
+ Map<String, REAL> ret = new LinkedHashMap<>();
+ for(String key : input.keySet()) {
+ ret.put(key, input.get(key).value());
+ }
+ return Collections.unmodifiableMap(ret);
+ }
+
+ /**
+ * Converts a map of String→REAL to String→NODE, where REAL is the underlying value type.
+ */
+ @SuppressWarnings("unchecked")
+ public static <NODE extends LeafNode<REAL>, REAL>
+ Map<String, NODE> asNodeMap(Map<String, REAL> input, NODE defaultNode)
+ {
+ Map<String, NODE> ret = new LinkedHashMap<>();
+ for(String key : input.keySet()) {
+ NODE node = (NODE)defaultNode.clone();
+ node.value = input.get(key);
+ ret.put(key, node);
+ }
+ return Collections.unmodifiableMap(ret);
+ }
+
+
+ /**
+ * Special case for file type, since FileNode param type (FileReference) is not same as type (String) in config builder
+ */
+ public static Map<String, FileNode> asFileNodeMap(Map<String, String> stringMap) {
+ Map<String, FileNode> fileNodeMap = new LinkedHashMap<>();
+ for (Map.Entry<String, String> e : stringMap.entrySet()) {
+ fileNodeMap.put(e.getKey(), new FileNode(e.getValue()));
+ }
+ return Collections.unmodifiableMap(fileNodeMap);
+ }
+
+ public static Map<String, PathNode> asPathNodeMap(Map<String, FileReference> fileReferenceMap) {
+ Map<String, PathNode> pathNodeMap = new LinkedHashMap<>();
+ for (Map.Entry<String, FileReference> e : fileReferenceMap.entrySet()) {
+ pathNodeMap.put(e.getKey(), new PathNode(e.getValue()));
+ }
+ return Collections.unmodifiableMap(pathNodeMap);
+ }
+
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/LeafNodeVector.java b/config-lib/src/main/java/com/yahoo/config/LeafNodeVector.java
new file mode 100644
index 00000000000..59e070f1d56
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/LeafNodeVector.java
@@ -0,0 +1,79 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A vector of leaf nodes.
+ *
+ * @author gjoranv
+ * @since 5.1.4
+ */
+public class LeafNodeVector<REAL, NODE extends LeafNode<REAL>> extends NodeVector<NODE> {
+
+ NODE defaultNode;
+
+ /**
+ * Creates a new vector with the given default node.
+ */
+ // TODO: remove this ctor when the library uses reflection via builders, and resizing won't be necessary
+ public LeafNodeVector(NODE defaultNode) {
+ assert (defaultNode != null) : "The default node cannot be null";
+
+ this.defaultNode = defaultNode;
+ if (createNew() == null) {
+ throw new NullPointerException("Unable to duplicate the default node.");
+ }
+ }
+
+ // TODO: take class instead of default node when the library uses reflection via builders
+ public LeafNodeVector(List<REAL> values, NODE defaultNode) {
+ this(defaultNode);
+ for (REAL value : values) {
+ NODE node = createNew();
+ node.value = value;
+ vector.add(node);
+ }
+ }
+
+ /**
+ * Creates a new Node by cloning the default node.
+ */
+ @SuppressWarnings("unchecked")
+ protected NODE createNew() {
+ return (NODE) (defaultNode).clone();
+ }
+
+ // TODO: create unmodifiable list in ctor when the library uses reflection via builders
+ @SuppressWarnings("unchecked")
+ public List<REAL> asList() {
+ List<REAL> ret = new ArrayList<REAL>();
+ for(NODE node : vector) {
+ ret.add(node.value());
+ }
+ return Collections.unmodifiableList(ret);
+ }
+
+ // TODO: Try to eliminate the need for this method when we have moved FileAcquirer to the config library
+ // It is needed now because the builder has a list of String, while REAL=FileReference.
+ public static LeafNodeVector<FileReference, FileNode> createFileNodeVector(Collection<String> values) {
+ List<FileReference> fileReferences = new ArrayList<FileReference>();
+ for (String s : values)
+ fileReferences.add(new FileReference(ReferenceNode.stripQuotes(s)));
+
+ return new LeafNodeVector<FileReference, FileNode>(fileReferences, new FileNode());
+ }
+
+ public static LeafNodeVector<Path, PathNode> createPathNodeVector(Collection<FileReference> values) {
+ List<Path> paths = new ArrayList<>();
+ for (FileReference fileReference : values)
+ paths.add(Paths.get(fileReference.value()));
+
+ return new LeafNodeVector<Path, PathNode>(paths, new PathNode());
+ }
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/LongNode.java b/config-lib/src/main/java/com/yahoo/config/LongNode.java
new file mode 100755
index 00000000000..7a3b29c1101
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/LongNode.java
@@ -0,0 +1,53 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * Represents a long in a configuration tree.
+ * @author gjoranv
+ */
+public class LongNode extends LeafNode<Long> {
+
+ public LongNode() {
+ }
+
+ public LongNode(long value) {
+ super(true);
+ this.value = value;
+ }
+
+ public Long value() {
+ return value;
+ }
+
+ @Override
+ public String getValue() {
+ return "" + value;
+ }
+
+ @Override
+ public String toString() {
+ return getValue();
+ }
+
+ @Override
+ protected boolean doSetValue(@NonNull String value) {
+ try {
+ this.value = Long.parseLong(value);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ @Override
+ void serialize(String name, Serializer serializer) {
+ serializer.serialize(name, value);
+ }
+
+ @Override
+ void serialize(Serializer serializer) {
+ serializer.serialize(value);
+ }
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/Node.java b/config-lib/src/main/java/com/yahoo/config/Node.java
new file mode 100644
index 00000000000..3dba5d083f4
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/Node.java
@@ -0,0 +1,29 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+/**
+ * The Node class is superclass for all nodes in a {@link
+ * ConfigInstance}. Important subclasses of this node are {@link
+ * InnerNode} and {@link LeafNode}.
+ *
+ */
+public abstract class Node {
+
+ /**
+ * Postinitialize this node. Any node needing to process its values depending on the config
+ * id should override this method.
+ *
+ * @param configId the configId of the ConfigInstance that owns (or is) this node
+ */
+ public void postInitialize(String configId) { return; }
+
+ /**
+ * This method is meant for internal use in the configuration system.
+ * Overrides Object.clone(), and is overriden by LeafNode.clone().
+ *
+ * @return a new instance similar to this object.
+ */
+ protected Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/NodeVector.java b/config-lib/src/main/java/com/yahoo/config/NodeVector.java
new file mode 100644
index 00000000000..8ce5689937e
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/NodeVector.java
@@ -0,0 +1,151 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import java.util.*;
+
+
+/**
+ * A NodeVector represents an array declared with '[]' in a config definition file.
+ * It is a List that stores nodes with a given type. A given default node must
+ * be given, and this node will be cloned as the NodeVector size are increased.
+ *
+ */
+public abstract class NodeVector<NODE> implements java.util.List<NODE> {
+
+ protected final ArrayList<NODE> vector = new ArrayList<NODE>();
+
+ /**
+ * Creates a new Node.
+ */
+ protected abstract NODE createNew();
+
+ /**
+ * Returns the number of elements in this NodeVector.
+ * Alias for size().
+ *
+ * @return the number of elements in this NodeVector.
+ */
+ public int length() {
+ return size();
+ }
+
+ /**
+ * Resizes this NodeVector. Removes or adds new nodes as needed.
+ *
+ * @param n the new size of this NodeVector
+ */
+ // TODO: remove when the library uses reflection via builders, and resizing won't be necessary
+ public void setSize(int n) {
+ while (size() > n) vector.remove(n);
+ while (size() < n) vector.add(createNew());
+ }
+
+ @SuppressWarnings("serial")
+ public static class ReadOnlyException extends RuntimeException {
+ }
+
+ private static final ReadOnlyException e = new ReadOnlyException();
+
+ public void add(int index, NODE element) {
+ throw e;
+ }
+
+ public boolean add(NODE o) {
+ throw e;
+ }
+
+ public boolean addAll(Collection<? extends NODE> c) {
+ throw e;
+ }
+
+ public boolean addAll(int index, Collection<? extends NODE> c) {
+ throw e;
+ }
+
+ public void clear() {
+ throw e;
+ }
+
+ public NODE remove(int index) {
+ throw e;
+ }
+
+ public boolean remove(Object o) {
+ throw e;
+ }
+
+ public boolean removeAll(Collection<?> c) {
+ throw e;
+ }
+
+ public boolean retainAll(Collection<?> c) {
+ throw e;
+ }
+
+ public NODE set(int index, NODE element) {
+ throw e;
+ }
+
+ public boolean contains(Object o) {
+ return vector.contains(o);
+ }
+
+ public boolean containsAll(Collection<?> c) {
+ return vector.containsAll(c);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof NodeVector && vector.equals(((NodeVector) o).vector);
+ }
+
+ @Override
+ public int hashCode() {
+ return vector.hashCode();
+ }
+
+ @SuppressWarnings("unchecked")
+ public NODE get(int index) {
+ return vector.get(index);
+ }
+
+ public int indexOf(Object o) {
+ return vector.indexOf(o);
+ }
+
+ public boolean isEmpty() {
+ return vector.isEmpty();
+ }
+
+ public Iterator<NODE> iterator() {
+ return vector.iterator();
+ }
+
+ public int lastIndexOf(Object o) {
+ return vector.lastIndexOf(o);
+ }
+
+ public ListIterator<NODE> listIterator() {
+ return vector.listIterator();
+ }
+
+ public ListIterator<NODE> listIterator(int index) {
+ return vector.listIterator(index);
+ }
+
+ public int size() {
+ return vector.size();
+ }
+
+ public List<NODE> subList(int fromIndex, int toIndex) {
+ return vector.subList(fromIndex, toIndex);
+ }
+
+ public Object[] toArray() {
+ return vector.toArray();
+ }
+
+ public <T> T[] toArray(T[] a) {
+ return vector.toArray(a);
+ }
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/PathNode.java b/config-lib/src/main/java/com/yahoo/config/PathNode.java
new file mode 100644
index 00000000000..91676137214
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/PathNode.java
@@ -0,0 +1,71 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a 'path' in a {@link ConfigInstance}, usually a filename.
+ *
+ * @author gjoranv
+ * @since 5.1.30
+ */
+public class PathNode extends LeafNode<Path> {
+
+ private final FileReference fileReference;
+
+ public PathNode() {
+ fileReference = null;
+ }
+
+ public PathNode(FileReference fileReference) {
+ super(true);
+ this.value = new File(fileReference.value()).toPath();
+ this.fileReference = fileReference;
+ }
+
+ public Path value() {
+ return value;
+ }
+
+ @Override
+ public String getValue() {
+ return value.toString();
+ }
+
+ @Override
+ public String toString() {
+ return (value == null) ? "(null)" : '"' + getValue() + '"';
+ }
+
+ @Override
+ protected boolean doSetValue(@NonNull String stringVal) {
+ throw new UnsupportedOperationException("doSetValue should not be necessary since the library anymore!");
+ }
+
+ public FileReference getFileReference() {
+ return fileReference;
+ }
+
+ public static List<FileReference> toFileReferences(List<PathNode> pathNodes) {
+ List<FileReference> fileReferences = new ArrayList<>();
+ for (PathNode pathNode : pathNodes)
+ fileReferences.add(pathNode.getFileReference());
+ return fileReferences;
+ }
+
+ public static Map<String, FileReference> toFileReferenceMap(Map<String, PathNode> map) {
+ Map<String, FileReference> ret = new LinkedHashMap<>();
+ for (Map.Entry<String, PathNode> e : map.entrySet()) {
+ ret.put(e.getKey(), e.getValue().getFileReference());
+ }
+ return ret;
+ }
+
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/ReferenceNode.java b/config-lib/src/main/java/com/yahoo/config/ReferenceNode.java
new file mode 100644
index 00000000000..a5e6400f456
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/ReferenceNode.java
@@ -0,0 +1,83 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * A ReferenceNode class represents a reference (that is a config-id)
+ * in a {@link ConfigInstance}.
+ */
+public class ReferenceNode extends LeafNode<String> {
+
+ public ReferenceNode() {
+ }
+
+ /**
+ * Creates a new ReferenceNode with the given value.
+ *
+ * @param value the value of this ReferenceNode
+ */
+ public ReferenceNode(String value) {
+ super(true);
+ this.value = stripQuotes(value);
+ }
+
+ /**
+ * Returns the value of this reference node. Same as {@link
+ * #toString()} since the value of a ReferenceNode is a String (but
+ * implementations in other {@link LeafNode} sub-classes differ).
+ *
+ * @return the string representation of this ReferenceNode.
+ */
+ public String value() {
+ return value;
+ }
+
+ @Override
+ public String getValue() {
+ return value();
+ }
+
+ @Override
+ public String toString() {
+ return (value == null) ? "(null)" : getValue();
+ }
+
+ @Override
+ protected boolean doSetValue(@NonNull String value) {
+ this.value = stripQuotes(value);
+ return true;
+ }
+
+ /**
+ * Overrides {@link Node#postInitialize(String)}
+ * Checks for ":parent:" values, which will be replaced by the configId.
+ *
+ * @param configId the configId of the ConfigInstance that owns (or is) this node
+ */
+ @Override
+ public void postInitialize(String configId) {
+ super.postInitialize(configId);
+ if (":parent:".equals(value())) {
+ doSetValue(configId);
+ }
+ }
+
+ /**
+ * Strips the quotes before or after the value, if present.
+ */
+ static String stripQuotes(String value) {
+ if (value == null) {
+ return value;
+ }
+ StringBuffer buffer = new StringBuffer(value.trim());
+ if (buffer.length() > 0 && buffer.charAt(0) == '"') {
+ buffer.deleteCharAt(0);
+ }
+ if (buffer.length() > 0 && buffer.charAt(buffer.length() - 1) == '"') {
+ buffer.setLength(buffer.length() - 1);
+ }
+ return buffer.toString();
+ }
+
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/Serializer.java b/config-lib/src/main/java/com/yahoo/config/Serializer.java
new file mode 100644
index 00000000000..95d20011f2c
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/Serializer.java
@@ -0,0 +1,31 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+/**
+* @author lulf
+* @since 5.1
+*/
+public interface Serializer {
+ Serializer createInner(String name);
+ Serializer createArray(String name);
+ Serializer createInner();
+ Serializer createMap(String name);
+
+ /**
+ * Serialize leaf values.
+ */
+ void serialize(String name, boolean value);
+ void serialize(String name, double value);
+ void serialize(String name, long value);
+ void serialize(String name, int value);
+ void serialize(String name, String value);
+
+ /**
+ * Serialize array values.
+ */
+ void serialize(boolean value);
+ void serialize(double value);
+ void serialize(long value);
+ void serialize(int value);
+ void serialize(String value);
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/StringNode.java b/config-lib/src/main/java/com/yahoo/config/StringNode.java
new file mode 100644
index 00000000000..344a7e2dd40
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/StringNode.java
@@ -0,0 +1,123 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config;
+
+import com.yahoo.config.text.StringUtilities;
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * A StringNode class represents a string in a {@link ConfigInstance}.
+ *
+ * @author larschr
+ */
+public class StringNode extends LeafNode<String> {
+
+ /**
+ * Creates a new un-initialized StringNode.
+ */
+ public StringNode() {
+ }
+
+ /**
+ * Creates a new StringNode, initialized to <code>value</code>.
+ *
+ * @param value the value of this StringNode.
+ */
+ public StringNode(String value) {
+ super(true);
+ this.value = value;
+ }
+
+ /**
+ * Returns the value of this string. Same as {@link #getValue()}
+ * since the value of this node is a String (but implementations
+ * in other {@link LeafNode} sub-classes differ).
+ *
+ * @return the string representation of this StringNode, or null if
+ * the vlaue is explicitly set to null
+ */
+ public String value() {
+ return value;
+ }
+
+ @Override
+ public String getValue() {
+ return value();
+ }
+
+ @Override
+ public String toString() {
+ return (value == null) ? "(null)" : '"' + StringUtilities.escape(getValue()) + '"';
+ }
+
+ /**
+ * Remove character escape codes.
+ *
+ * @param string escaped string
+ * @return unescaped string
+ */
+ public static String unescapeQuotedString(String string) {
+ StringBuilder sb = new StringBuilder(string);
+ for (int i = 0; i < sb.length(); i++) {
+ if (sb.charAt(i) == '\\') {
+ sb.deleteCharAt(i);
+ if (i == sb.length()) {
+ throw new IllegalArgumentException("Parse error" + string);
+ }
+ switch (sb.charAt(i)) {
+ case'n':
+ sb.setCharAt(i, '\n');
+ break;
+ case'r':
+ sb.setCharAt(i, '\r');
+ break;
+ case't':
+ sb.setCharAt(i, '\t');
+ break;
+ case'f':
+ sb.setCharAt(i, '\f');
+ break;
+ case'x':
+ if (i + 2 >= sb.length()) {
+ throw new IllegalArgumentException
+ ("Could not parse hex value " + string);
+ }
+ sb.setCharAt(i, (char) Integer.parseInt
+ (sb.substring(i + 1, i + 3), 16));
+ sb.delete(i + 1, i + 3);
+ break;
+ case'\\':
+ sb.setCharAt(i, '\\');
+ break;
+ }
+ }
+ }
+
+ if (sb.length() > 0 && (sb.charAt(0) == '"') && sb.charAt(sb.length() - 1) == '"') {
+ sb.deleteCharAt(sb.length() - 1);//remove last quote
+ if (sb.length() > 0) {
+ sb.deleteCharAt(0); //remove first quote
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Sets the value of this string from a the string representation
+ * of this value in the (escaped) input configuration. The value
+ * supplied to this method needs un-escaping and will be
+ * un-escaped.
+ *
+ * @param value the new value of this node.
+ */
+ @Override
+ protected boolean doSetValue(@NonNull String value) {
+ if (value.startsWith("\"") && value.endsWith("\""))
+ this.value = unescapeQuotedString(value);
+ else {
+ //TODO: unquoted strings can be probably be prohibited now.(?) -gv
+ this.value = value;
+ }
+ return true;
+ }
+
+}
diff --git a/config-lib/src/main/java/com/yahoo/config/package-info.java b/config-lib/src/main/java/com/yahoo/config/package-info.java
new file mode 100644
index 00000000000..c1069b6649a
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/package-info.java
@@ -0,0 +1,7 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+@PublicApi
+package com.yahoo.config;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/config-lib/src/main/java/com/yahoo/config/text/StringUtilities.java b/config-lib/src/main/java/com/yahoo/config/text/StringUtilities.java
new file mode 100644
index 00000000000..1b05ae779bf
--- /dev/null
+++ b/config-lib/src/main/java/com/yahoo/config/text/StringUtilities.java
@@ -0,0 +1,88 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.text;
+
+import java.nio.charset.Charset;
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Escapes strings into and out of a format where they only contain printable characters.
+ *
+ * Need to duplicate escape / unescape of strings as we have in C++ for java version of system states.
+ *
+ * @author <a href="mailto:humbe@yahoo-inc.com">Haakon Humberset</a>
+ */
+public class StringUtilities {
+ private static Charset UTF8 = Charset.forName("utf8");
+
+ private static byte toHex(int val) { return (byte) (val < 10 ? '0' + val : 'a' + (val - 10)); }
+
+ private static class ReplacementCharacters {
+ public byte needEscape[] = new byte[256];
+ public byte replacement1[] = new byte[256];
+ public byte replacement2[] = new byte[256];
+
+ public ReplacementCharacters() {
+ for (int i=0; i<256; ++i) {
+ if (i >= 32 && i <= 126) {
+ needEscape[i] = 0;
+ } else if (i > 127) {
+ needEscape[i] = 0;
+ } else {
+ needEscape[i] = 3;
+ replacement1[i] = toHex((i >> 4) & 0xF);
+ replacement2[i] = toHex(i & 0xF);
+ }
+ }
+ makeSimpleEscape('"', '"');
+ makeSimpleEscape('\\', '\\');
+ makeSimpleEscape('\t', 't');
+ makeSimpleEscape('\n', 'n');
+ makeSimpleEscape('\r', 'r');
+ makeSimpleEscape('\f', 'f');
+ }
+
+ private void makeSimpleEscape(char source, char dest) {
+ needEscape[source] = 1;
+ replacement1[source] = '\\';
+ replacement2[source] = (byte) dest;
+ }
+ }
+
+ private final static ReplacementCharacters replacementCharacters = new ReplacementCharacters();
+
+ public static String escape(String source) { return escape(source, '\0'); }
+
+ /**
+ * Escapes strings into a format with only printable ASCII characters.
+ *
+ * @param source The string to escape
+ * @param delimiter Escape this character too, even if it is printable.
+ * @return The escaped string
+ */
+ public static String escape(String source, char delimiter) {
+ byte bytes[] = source.getBytes(UTF8);
+ ByteArrayOutputStream result = new ByteArrayOutputStream();
+ for (byte b : bytes) {
+ int val = b;
+ if (val < 0) val += 256;
+ if (b == delimiter) {
+ result.write('\\');
+ result.write('x');
+ result.write(toHex((val >> 4) & 0xF));
+ result.write(toHex(val & 0xF));
+ } else if (replacementCharacters.needEscape[val] == 0) {
+ result.write(b);
+ } else {
+ if (replacementCharacters.needEscape[val] == 3) {
+ result.write('\\');
+ result.write('x');
+ }
+ result.write(replacementCharacters.replacement1[val]);
+ result.write(replacementCharacters.replacement2[val]);
+ }
+ }
+ return new String(result.toByteArray(), UTF8);
+ }
+
+
+}