summaryrefslogtreecommitdiffstats
path: root/config-lib
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
Publish
Diffstat (limited to 'config-lib')
-rw-r--r--config-lib/.gitignore2
-rw-r--r--config-lib/OWNERS1
-rw-r--r--config-lib/pom.xml77
-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
-rw-r--r--config-lib/src/test/java/com/yahoo/config/BooleanNodeTest.java24
-rw-r--r--config-lib/src/test/java/com/yahoo/config/ConfigInstanceBuilderTest.java399
-rw-r--r--config-lib/src/test/java/com/yahoo/config/ConfigInstanceEqualsTest.java184
-rw-r--r--config-lib/src/test/java/com/yahoo/config/DoubleNodeTest.java21
-rw-r--r--config-lib/src/test/java/com/yahoo/config/EnumNodeTest.java42
-rw-r--r--config-lib/src/test/java/com/yahoo/config/FileNodeTest.java26
-rw-r--r--config-lib/src/test/java/com/yahoo/config/IntegerNodeTest.java23
-rw-r--r--config-lib/src/test/java/com/yahoo/config/LongNodeTest.java23
-rw-r--r--config-lib/src/test/java/com/yahoo/config/NodeVectorTest.java116
-rw-r--r--config-lib/src/test/java/com/yahoo/config/PathNodeTest.java26
-rw-r--r--config-lib/src/test/java/com/yahoo/config/StringNodeTest.java46
-rw-r--r--config-lib/src/test/resources/configdefinitions/app.def8
-rw-r--r--config-lib/src/test/resources/configdefinitions/arraytypes.def11
-rw-r--r--config-lib/src/test/resources/configdefinitions/chains-test.def42
-rw-r--r--config-lib/src/test/resources/configdefinitions/datastructures.def11
-rw-r--r--config-lib/src/test/resources/configdefinitions/defaulttest.def8
-rw-r--r--config-lib/src/test/resources/configdefinitions/function-test.def93
-rwxr-xr-xconfig-lib/src/test/resources/configdefinitions/int.def4
-rw-r--r--config-lib/src/test/resources/configdefinitions/maptypes.def13
-rw-r--r--config-lib/src/test/resources/configdefinitions/md5test.def24
-rw-r--r--config-lib/src/test/resources/configdefinitions/namespace.def6
-rwxr-xr-xconfig-lib/src/test/resources/configdefinitions/restart.def4
-rw-r--r--config-lib/src/test/resources/configdefinitions/simpletypes.def11
-rw-r--r--config-lib/src/test/resources/configdefinitions/specialtypes.def5
-rw-r--r--config-lib/src/test/resources/configdefinitions/standard.def8
-rwxr-xr-xconfig-lib/src/test/resources/configdefinitions/string.def4
-rw-r--r--config-lib/src/test/resources/configdefinitions/structtypes.def21
-rw-r--r--config-lib/src/test/resources/configdefinitions/test-nodefs.def16
-rw-r--r--config-lib/src/test/resources/configdefinitions/test-nonstring.def9
-rw-r--r--config-lib/src/test/resources/configdefinitions/test-reference.def4
-rw-r--r--config-lib/src/test/resources/configdefinitions/testnamespace.def3
-rw-r--r--config-lib/src/test/resources/configdefinitions/unicode.def5
59 files changed, 3040 insertions, 0 deletions
diff --git a/config-lib/.gitignore b/config-lib/.gitignore
new file mode 100644
index 00000000000..3cc25b51fc4
--- /dev/null
+++ b/config-lib/.gitignore
@@ -0,0 +1,2 @@
+/pom.xml.build
+/target
diff --git a/config-lib/OWNERS b/config-lib/OWNERS
new file mode 100644
index 00000000000..3b2ba1ede81
--- /dev/null
+++ b/config-lib/OWNERS
@@ -0,0 +1 @@
+gjoranv
diff --git a/config-lib/pom.xml b/config-lib/pom.xml
new file mode 100644
index 00000000000..80568a1ea26
--- /dev/null
+++ b/config-lib/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+ http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>parent</artifactId>
+ <version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
+ </parent>
+ <artifactId>config-lib</artifactId>
+ <packaging>container-plugin</packaging>
+ <version>6-SNAPSHOT</version>
+ <name>${project.artifactId}</name>
+ <dependencies>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>annotations</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <compilerArgs>
+ <arg>-Xlint:all</arg>
+ <arg>-Werror</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>config-class-plugin</artifactId>
+ <version>${project.version}</version>
+ <executions>
+ <execution>
+ <id>configgen-test-defs</id>
+ <phase>generate-test-sources</phase>
+ <goals>
+ <goal>config-gen</goal>
+ </goals>
+ <configuration>
+ <defFilesDirectories>src/test/resources/configdefinitions</defFilesDirectories>
+ <outputDirectory>target/generated-test-sources/vespa-configgen-plugin</outputDirectory>
+ <testConfig>true</testConfig>
+ <requireNamespace>false</requireNamespace>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
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);
+ }
+
+
+}
diff --git a/config-lib/src/test/java/com/yahoo/config/BooleanNodeTest.java b/config-lib/src/test/java/com/yahoo/config/BooleanNodeTest.java
new file mode 100644
index 00000000000..f484c2080d1
--- /dev/null
+++ b/config-lib/src/test/java/com/yahoo/config/BooleanNodeTest.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;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class BooleanNodeTest {
+ @Test
+ public void testSetValue() {
+ BooleanNode n = new BooleanNode();
+ assertTrue(n.doSetValue("true"));
+ assertTrue(n.doSetValue("TRUE"));
+ assertTrue(n.doSetValue("false"));
+ assertTrue(n.doSetValue("FALSE"));
+ assertFalse(n.doSetValue("FALSEa"));
+ assertFalse(n.doSetValue("aFALSE"));
+ }
+}
diff --git a/config-lib/src/test/java/com/yahoo/config/ConfigInstanceBuilderTest.java b/config-lib/src/test/java/com/yahoo/config/ConfigInstanceBuilderTest.java
new file mode 100644
index 00000000000..78c97935d5a
--- /dev/null
+++ b/config-lib/src/test/java/com/yahoo/config/ConfigInstanceBuilderTest.java
@@ -0,0 +1,399 @@
+// 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.foo.MaptypesConfig;
+import com.yahoo.test.FunctionTestConfig;
+import com.yahoo.test.IntConfig;
+import com.yahoo.test.RestartConfig;
+import org.junit.Test;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.yahoo.test.FunctionTestConfig.BasicStruct;
+import static com.yahoo.test.FunctionTestConfig.Enum_val;
+import static com.yahoo.test.FunctionTestConfig.Enumarr;
+import static com.yahoo.test.FunctionTestConfig.Enumwithdef;
+import static com.yahoo.test.FunctionTestConfig.Myarray;
+import static com.yahoo.test.FunctionTestConfig.RootStruct;
+import static com.yahoo.test.FunctionTestConfig.MyStructMap;
+import static com.yahoo.foo.MaptypesConfig.Innermap;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author gjoranv
+ * @since 5.1.11
+ */
+public class ConfigInstanceBuilderTest
+{
+
+ @Test
+ public void leaf_map_setter_merges_maps() {
+ MaptypesConfig.Builder builder = new MaptypesConfig.Builder()
+ .intmap("one", 1);
+
+ Map<String, Integer> newMap = new HashMap<>();
+ newMap.put("two", 2);
+ builder.intmap(newMap);
+
+ MaptypesConfig config = new MaptypesConfig(builder);
+ assertThat(config.intmap("one"), is(1));
+ assertThat(config.intmap("two"), is(2));
+ }
+
+ @Test
+ public void inner_map_setter_merges_maps() {
+ MaptypesConfig.Builder builder = new MaptypesConfig.Builder()
+ .innermap("one", new Innermap.Builder()
+ .foo(1));
+
+ Map<String, Innermap.Builder> newMap = new HashMap<>();
+ newMap.put("two", new Innermap.Builder().foo(2));
+ builder.innermap(newMap);
+
+ MaptypesConfig config = new MaptypesConfig(builder);
+ assertThat(config.innermap("one").foo(), is(1));
+ assertThat(config.innermap("two").foo(), is(2));
+ }
+
+ @Test
+ public void testVariableAccessWithBuilder() {
+ FunctionTestConfig config = createVariableAccessConfigWithBuilder();
+ assertVariableAccessValues(config, ":parent:");
+ }
+
+ @Test
+ public void require_that_unset_builder_fields_are_null() throws Exception {
+ FunctionTestConfig.Builder builder = new FunctionTestConfig.Builder();
+ assertNull(getMember(builder, "bool_val"));
+ assertNull(getMember(builder, "bool_with_def"));
+ assertNull(getMember(builder, "int_val"));
+ assertNull(getMember(builder, "int_with_def"));
+ assertNull(getMember(builder, "long_val"));
+ assertNull(getMember(builder, "long_with_def"));
+ assertNull(getMember(builder, "double_val"));
+ assertNull(getMember(builder, "double_with_def"));
+ assertNull(getMember(builder, "string_val"));
+ assertNull(getMember(builder, "stringwithdef"));
+ assertNull(getMember(builder, "enum_val"));
+ assertNull(getMember(builder, "enumwithdef"));
+ assertNull(getMember(builder, "refval"));
+ assertNull(getMember(builder, "refwithdef"));
+ assertNull(getMember(builder, "fileVal"));
+
+ BasicStruct.Builder basicStructBuilder = new BasicStruct.Builder();
+ assertNull(getMember(basicStructBuilder, "foo"));
+ assertNull(getMember(basicStructBuilder, "bar"));
+ }
+
+ @Test
+ public void require_that_set_builder_fields_are_nonNull() throws Exception {
+ FunctionTestConfig.Builder builder = createVariableAccessBuilder();
+ assertNotNull(getMember(builder, "bool_val"));
+ assertNotNull(getMember(builder, "bool_with_def"));
+ assertNotNull(getMember(builder, "int_val"));
+ assertNotNull(getMember(builder, "int_with_def"));
+ assertNotNull(getMember(builder, "long_val"));
+ assertNotNull(getMember(builder, "long_with_def"));
+ assertNotNull(getMember(builder, "double_val"));
+ assertNotNull(getMember(builder, "double_with_def"));
+ assertNotNull(getMember(builder, "string_val"));
+ assertNotNull(getMember(builder, "stringwithdef"));
+ assertNotNull(getMember(builder, "enum_val"));
+ assertNotNull(getMember(builder, "enumwithdef"));
+ assertNotNull(getMember(builder, "refval"));
+ assertNotNull(getMember(builder, "refwithdef"));
+ assertNotNull(getMember(builder, "fileVal"));
+
+ BasicStruct.Builder basicStructBuilder = (BasicStruct.Builder)getMember(builder, "basicStruct");
+ assertNotNull(getMember(basicStructBuilder, "foo"));
+ assertNotNull(getMember(basicStructBuilder, "bar"));
+ }
+
+ @Test
+ public void require_that_config_can_be_recreated_from_another_configs_builder() {
+ FunctionTestConfig original = createVariableAccessConfigWithBuilder();
+ FunctionTestConfig copy = new FunctionTestConfig(new FunctionTestConfig.Builder(original));
+ assertVariableAccessValues(copy, ":parent:");
+ }
+
+ private Object getMember(Object builder, String memberName) throws Exception {
+ Field field = builder.getClass().getDeclaredField(memberName);
+ field.setAccessible(true);
+ return field.get(builder);
+ }
+
+ static FunctionTestConfig createVariableAccessConfigWithBuilder() {
+ return new FunctionTestConfig(createVariableAccessBuilder());
+ }
+
+ static FunctionTestConfig.Builder createVariableAccessBuilder() {
+ return new FunctionTestConfig.Builder().
+ bool_val(false).
+ bool_with_def(true).
+ int_val(5).
+ int_with_def(-14).
+ long_val(12345678901L).
+ long_with_def(-9876543210L).
+ double_val(41.23).
+ double_with_def(-12).
+ string_val("foo").
+ stringwithdef("bar and foo").
+ enum_val(Enum_val.FOOBAR).
+ enumwithdef(Enumwithdef.BAR2).
+ refval(":parent:").
+ refwithdef(":parent:").
+ fileVal("etc").
+ pathVal(FileReference.mockFileReferenceForUnitTesting(new File("pom.xml"))).
+ boolarr(false).
+ longarr(9223372036854775807L).
+ longarr(-9223372036854775808L).
+ doublearr(2344.0).
+ doublearr(123.0).
+ stringarr("bar").
+ enumarr(Enumarr.VALUES).
+ refarr(Arrays.asList(":parent:", ":parent", "parent:")). // test collection based setter
+ fileArr("bin").
+ intMap("one", 1).
+ intMap("two", 2).
+ stringMap("one", "first").
+ filemap("f1", "/var").
+ filemap("f2", "/store").
+
+ basicStruct(new BasicStruct.Builder().
+ foo("basicFoo").
+ bar(3).
+ intArr(310).intArr(311)).
+
+ rootStruct(new RootStruct.Builder().
+ inner0(new RootStruct.Inner0.Builder().
+ index(11)).
+ inner1(new RootStruct.Inner1.Builder().
+ index(12)).
+ innerArr(new RootStruct.InnerArr.Builder().
+ boolVal(true).
+ stringVal("deep")).
+ innerArr(new RootStruct.InnerArr.Builder().
+ boolVal(false).
+ stringVal("blue a=\"escaped\""))).
+
+ myarray(new Myarray.Builder().
+ intval(-5).
+ stringval("baah").
+ stringval("yikes").
+ enumval(Myarray.Enumval.INNER).
+ refval(":parent:").
+ fileVal("file0").
+ anotherarray(new Myarray.Anotherarray.Builder().
+ foo(7)).
+ myStruct(new Myarray.MyStruct.Builder().
+ a(1).
+ b(2))).
+
+ myarray(new Myarray.Builder().
+ intval(5).
+ enumval(Myarray.Enumval.INNER).
+ refval(":parent:").
+ fileVal("file1").
+ anotherarray(new Myarray.Anotherarray.Builder().
+ foo(1).
+ foo(2)).
+ myStruct(new Myarray.MyStruct.Builder().
+ a(-1).
+ b(-2))).
+
+ myStructMap("one", new MyStructMap.Builder().
+ myInt(1).
+ myString("bull").
+ myIntDef(2).
+ myStringDef("bear").
+ anotherMap("anotherOne", new MyStructMap.AnotherMap.Builder().
+ anInt(3).
+ anIntDef(4)));
+ }
+
+ public static void assertVariableAccessValues(FunctionTestConfig config, String configId) {
+ assertTrue(config.bool_with_def());
+ assertEquals(5, config.int_val());
+ assertEquals(-14, config.int_with_def());
+ assertEquals(12345678901L, config.long_val());
+ assertEquals(-9876543210L, config.long_with_def());
+ assertEquals(41.23, config.double_val(), 0.000001);
+ assertEquals(-12, config.double_with_def(), 0.000001);
+ assertEquals("foo", config.string_val());
+ assertEquals("bar and foo", config.stringwithdef());
+ assertEquals(FunctionTestConfig.Enum_val.FOOBAR, config.enum_val());
+ assertEquals(FunctionTestConfig.Enumwithdef.BAR2, config.enumwithdef());
+ assertEquals(configId, config.refval());
+ assertEquals(configId, config.refwithdef());
+ assertEquals("etc", config.fileVal().value());
+ assertEquals(1, config.boolarr().size());
+ assertEquals(1, config.boolarr().size()); // new api with accessor for a List of the original Java type
+ assertEquals(false, config.boolarr().get(0)); // new List api
+ assertEquals(false, config.boolarr(0)); // short-hand
+ assertEquals(0, config.intarr().size());
+ assertEquals(2, config.longarr().size());
+ assertEquals(Long.MAX_VALUE, config.longarr(0));
+ assertThat(config.longarr().get(1), is(Long.MIN_VALUE));
+ assertEquals(2, config.doublearr().size());
+ assertEquals(1, config.stringarr().size());
+ assertEquals(1, config.enumarr().size());
+ assertEquals(FunctionTestConfig.Enumarr.VALUES, config.enumarr().get(0)); // new List api, don't have to call value()
+ assertEquals(3, config.refarr().size());
+ assertEquals(1, config.fileArr().size());
+ assertEquals(configId, config.refarr(0));
+ assertEquals(":parent", config.refarr(1));
+ assertEquals("parent:", config.refarr(2));
+ assertEquals("bin", config.fileArr(0).value());
+
+ assertThat(config.intMap("one"), is(1));
+ assertThat(config.intMap("two"), is(2));
+ assertThat(config.stringMap("one"), is("first"));
+ assertThat(config.filemap("f1").value(), is("/var"));
+ assertThat(config.filemap("f2").value(), is("/store"));
+ assertEquals("basicFoo", config.basicStruct().foo());
+ assertEquals(3, config.basicStruct().bar()); // new List api
+ assertEquals(2, config.basicStruct().intArr().size());
+ assertThat(config.basicStruct().intArr().get(0), is(310)); // new List api
+ assertThat(config.basicStruct().intArr().get(1), is(311)); // new List api
+ assertEquals(310, config.basicStruct().intArr(0)); // short-hand
+ assertEquals("inner0", config.rootStruct().inner0().name()); // new List api
+ assertEquals(11, config.rootStruct().inner0().index());
+ assertEquals("inner1", config.rootStruct().inner1().name());
+ assertEquals(12, config.rootStruct().inner1().index());
+ assertEquals(2, config.rootStruct().innerArr().size());
+ assertEquals(true, config.rootStruct().innerArr(0).boolVal());
+ assertEquals("deep", config.rootStruct().innerArr(0).stringVal());
+ assertEquals(false, config.rootStruct().innerArr(1).boolVal());
+ assertEquals("blue a=\"escaped\"", config.rootStruct().innerArr(1).stringVal());
+
+ assertEquals(2, config.myarray().size()); // new List api
+ assertEquals(configId, config.myarray().get(0).refval()); // new List api
+ assertEquals(configId, config.myarray(0).refval()); // short-hand
+ assertEquals("file0", config.myarray(0).fileVal().value());
+ assertEquals(1, config.myarray(0).myStruct().a());
+ assertEquals(2, config.myarray(0).myStruct().b());
+ assertEquals(configId, config.myarray(1).refval());
+ assertEquals("file1", config.myarray(1).fileVal().value());
+ assertEquals(-1, config.myarray(1).myStruct().a());
+ assertEquals(-2, config.myarray(1).myStruct().b());
+
+ assertThat(config.myStructMap("one").myInt(), is(1));
+ assertThat(config.myStructMap("one").myString(), is("bull"));
+ assertThat(config.myStructMap("one").myIntDef(), is(2));
+ assertThat(config.myStructMap("one").myStringDef(), is("bear"));
+ assertThat(config.myStructMap("one").anotherMap("anotherOne").anInt(), is(3));
+ assertThat(config.myStructMap("one").anotherMap("anotherOne").anIntDef(), is(4));
+ }
+
+ private boolean callContainsFieldsFlaggedWithRestart(Class<?> configClass)
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ Method m = configClass.getDeclaredMethod("containsFieldsFlaggedWithRestart");
+ m.setAccessible(true);
+ return (boolean) m.invoke(null);
+ }
+
+ private ChangesRequiringRestart callGetChangesRequiringRestart(ConfigInstance config1, ConfigInstance config2)
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ Method m = config1.getClass().getDeclaredMethod("getChangesRequiringRestart", config2.getClass());
+ m.setAccessible(true);
+ return (ChangesRequiringRestart) m.invoke(config1, config2);
+ }
+
+ @Test
+ public void require_that_config_class_reports_any_restart_values() throws Exception {
+ assertTrue(callContainsFieldsFlaggedWithRestart(RestartConfig.class));
+ assertFalse(callContainsFieldsFlaggedWithRestart(IntConfig.class));
+ }
+
+ @Test
+ public void require_that_config_class_can_make_change_report() throws Exception {
+ IntConfig noRestart1 = new IntConfig(new IntConfig.Builder().intVal(42));
+ IntConfig noRestart2 = new IntConfig(new IntConfig.Builder().intVal(21));
+ ChangesRequiringRestart report = callGetChangesRequiringRestart(noRestart1, noRestart2);
+ assertFalse(report.needsRestart());
+
+ RestartConfig config1 = new RestartConfig(new RestartConfig.Builder().intVal(42));
+ RestartConfig config2 = new RestartConfig(new RestartConfig.Builder().intVal(21));
+ report = callGetChangesRequiringRestart(config1, config1);
+ assertFalse(report.needsRestart());
+ report = callGetChangesRequiringRestart(config1, config2);
+ assertTrue(report.needsRestart());
+
+ FunctionTestConfig function1 = createVariableAccessConfigWithBuilder();
+ FunctionTestConfig.Builder funcBuilder = createVariableAccessBuilder();
+ funcBuilder.myStructMap.get("one").myInt(42);
+ funcBuilder.int_val(100);
+ funcBuilder.stringarr.set(0, "foo");
+ funcBuilder.intMap.put("one", 42);
+ funcBuilder.intMap.remove("two");
+ funcBuilder.intMap.put("three", 3);
+ funcBuilder.myarray.get(1).intval(17);
+ funcBuilder.myarray.get(0).anotherarray.get(0).foo(32);
+ funcBuilder.myarray.add(new Myarray.Builder().refval("refval").fileVal("fileval").myStruct(new Myarray.MyStruct.Builder().a(4)));
+ funcBuilder.myStructMap.put("new", new MyStructMap.Builder().myString("string").myInt(13));
+ funcBuilder.basicStruct(new BasicStruct.Builder().bar(1234));
+ FunctionTestConfig function2 = new FunctionTestConfig(funcBuilder);
+ report = callGetChangesRequiringRestart(function1, function1);
+ assertFalse(report.needsRestart());
+ report = callGetChangesRequiringRestart(function1, function2);
+ assertTrue(report.needsRestart());
+ assertEquals("function-test", report.getName());
+ assertThat(
+ report.toString(),
+ startsWith(
+ "# An int value\n" +
+ "# Also test that multiline comments\n" +
+ "# work.\n" +
+ "function-test.int_val has changed from 5 to 100\n" +
+ "function-test.stringarr[0] has changed from \"bar\" to \"foo\"\n" +
+ "# This is a map of ints.\n" +
+ "function-test.intMap{one} has changed from 1 to 42\n" +
+ "# This is a map of ints.\n" +
+ "function-test.intMap{two} with value 2 was removed\n" +
+ "# This is a map of ints.\n" +
+ "function-test.intMap{three} was added with value 3\n" +
+ "# A basic struct\n" +
+ "function-test.basicStruct.foo has changed from \"basicFoo\" to \"basic\"\n" +
+ "function-test.basicStruct.bar has changed from 3 to 1234\n" +
+ "function-test.basicStruct.intArr[0] with value 310 was removed\n" +
+ "function-test.basicStruct.intArr[1] with value 311 was removed\n" +
+ "function-test.myarray[0].anotherarray[0].foo has changed from 7 to 32\n" +
+ "# This is my array\n" +
+ "function-test.myarray[1].intval has changed from 5 to 17\n" +
+ "function-test.myarray[2] was added with value \n"
+ )
+ );
+
+ assertThat(
+ report.toString(),
+ containsString(
+ "function-test.myStructMap{one}.myInt has changed from 1 to 42\n" +
+ "function-test.myStructMap{new} was added with value \n"
+ )
+ );
+
+ funcBuilder.myStructMap.remove("one");
+ FunctionTestConfig function3 = new FunctionTestConfig(funcBuilder);
+ report = callGetChangesRequiringRestart(function2, function3);
+ assertEquals(1, report.getReportLines().size());
+ assertThat(
+ report.toString(),
+ containsString("function-test.myStructMap{one} with value \n")
+ );
+ }
+}
diff --git a/config-lib/src/test/java/com/yahoo/config/ConfigInstanceEqualsTest.java b/config-lib/src/test/java/com/yahoo/config/ConfigInstanceEqualsTest.java
new file mode 100644
index 00000000000..f99639420e2
--- /dev/null
+++ b/config-lib/src/test/java/com/yahoo/config/ConfigInstanceEqualsTest.java
@@ -0,0 +1,184 @@
+// 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.test.AppConfig;
+import com.yahoo.test.FunctionTestConfig;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.Arrays;
+
+import static com.yahoo.test.FunctionTestConfig.BasicStruct;
+import static com.yahoo.test.FunctionTestConfig.Enum_val;
+import static com.yahoo.test.FunctionTestConfig.Enumarr;
+import static com.yahoo.test.FunctionTestConfig.Myarray;
+import static com.yahoo.test.FunctionTestConfig.RootStruct;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+
+public class ConfigInstanceEqualsTest {
+ FunctionTestConfig config1;
+ FunctionTestConfig.Builder builder2;
+ FunctionTestConfig config2;
+
+ @Before
+ public void reset() {
+ config1 = new FunctionTestConfig(newBuilder());
+ builder2 = newBuilder();
+ config2 = new FunctionTestConfig(builder2);
+ }
+
+ @Test
+ public void require_same_hashCode_for_equal_instances() {
+ assertThat(config1.hashCode(), is(config2.hashCode()));
+ }
+
+ @Test
+ public void require_true_for_equal_instances() {
+ assertThat(config1, is(config2));
+ }
+
+ @Test
+ public void require_false_for_null() {
+ assertThat(config1, not((FunctionTestConfig) null));
+
+ }
+
+ @Test
+ public void require_false_for_different_subclass() {
+ assertFalse(config1.equals(new AppConfig(new AppConfig.Builder())));
+ }
+
+ @Test
+ public void require_false_for_different_scalars_at_root_node() {
+ assertThat(config1, not(new FunctionTestConfig(newBuilder().bool_val(true))));
+ assertThat(config1, not(new FunctionTestConfig(newBuilder().int_val(0))));
+ assertThat(config1, not(new FunctionTestConfig(newBuilder().long_val(0L))));
+ assertThat(config1, not(new FunctionTestConfig(newBuilder().double_val(0.0))));
+ assertThat(config1, not(new FunctionTestConfig(newBuilder().string_val(""))));
+ assertThat(config1, not(new FunctionTestConfig(newBuilder().enum_val(Enum_val.FOO))));
+ assertThat(config1, not(new FunctionTestConfig(newBuilder().refval(""))));
+ assertThat(config1, not(new FunctionTestConfig(newBuilder().fileVal(""))));
+ }
+
+ @Test
+ public void require_false_for_different_leaf_array_at_root_node() {
+ builder2.longarr.set(0, 0L);
+ assertThat(config1, not(new FunctionTestConfig(builder2)));
+ }
+
+ @Test
+ public void require_false_for_different_scalar_in_struct() {
+ builder2.basicStruct(new BasicStruct.Builder(config1.basicStruct()).bar(0));
+ assertThat(config1, not(new FunctionTestConfig(builder2)));
+ }
+
+ @Test
+ public void require_false_for_different_scalar_in_inner_array() {
+ builder2.myarray.get(0).intval(0);
+ assertThat(config1, not(new FunctionTestConfig(builder2)));
+ }
+
+ @Test
+ public void require_false_for_different_leaf_array_in_inner_array() {
+ builder2.myarray.get(0).stringval.set(0, "");
+ assertThat(config1, not(new FunctionTestConfig(builder2)));
+ }
+
+ @Test
+ public void require_equal_structs_for_equal_configs() {
+ assertThat(config1.basicStruct(), is(config2.basicStruct()));
+ assertThat(config1.rootStruct(), is(config2.rootStruct()));
+ assertThat(config1.rootStruct().inner0(), is(config2.rootStruct().inner0()));
+ }
+
+ @Test
+ public void require_equal_inner_arrays_for_equal_configs() {
+ assertThat(config1.myarray(), is(config2.myarray()));
+ assertThat(config1.myarray(0).anotherarray(), is(config2.myarray(0).anotherarray()));
+ }
+
+ @Test
+ public void require_equal_inner_array_elements_for_equal_configs() {
+ assertThat(config1.myarray(0), is(config2.myarray(0)));
+ assertThat(config1.myarray(0).anotherarray(0), is(config2.myarray(0).anotherarray(0)));
+ }
+
+ @Test
+ public void require_equal_leaf_arrays_for_equal_configs() {
+ assertThat(config1.intarr(), is(config2.intarr()));
+ assertThat(config1.boolarr(), is(config2.boolarr()));
+ assertThat(config1.longarr(), is(config2.longarr()));
+ assertThat(config1.doublearr(), is(config2.doublearr()));
+ assertThat(config1.stringarr(), is(config2.stringarr()));
+ assertThat(config1.enumarr(), is(config2.enumarr()));
+ assertThat(config1.refarr(), is(config2.refarr()));
+ assertThat(config1.fileArr(), is(config2.fileArr()));
+ }
+
+ private static FunctionTestConfig.Builder newBuilder() {
+ FunctionTestConfig.Builder builder = new FunctionTestConfig.Builder();
+
+ return builder.bool_val(false).
+ int_val(5).
+ long_val(12345678901L).
+ double_val(41.23).
+ string_val("foo").
+ enum_val(Enum_val.FOOBAR).
+ refval(":parent:").
+ fileVal("etc").
+ pathVal(FileReference.mockFileReferenceForUnitTesting(new File("pom.xml"))).
+ boolarr(false).
+ longarr(9223372036854775807L).
+ longarr(-9223372036854775808L).
+ doublearr(2344.0).
+ doublearr(123.0).
+ stringarr("bar").
+ enumarr(Enumarr.VALUES).
+ refarr(Arrays.asList(":parent:", ":parent", "parent:")). // test collection based setter
+ fileArr("bin").
+
+ basicStruct(new BasicStruct.Builder().
+ foo("basicFoo").
+ bar(3).
+ intArr(310)).
+
+ rootStruct(new RootStruct.Builder().
+ inner0(new RootStruct.Inner0.Builder().
+ index(11)).
+ inner1(new RootStruct.Inner1.Builder().
+ index(12)).
+ innerArr(new RootStruct.InnerArr.Builder().
+ boolVal(true).
+ stringVal("deep"))).
+
+ myarray(new Myarray.Builder().
+ intval(-5).
+ stringval("baah").
+ stringval("yikes").
+ enumval(Myarray.Enumval.INNER).
+ refval(":parent:").
+ fileVal("file0").
+ anotherarray(new Myarray.Anotherarray.Builder().
+ foo(7)).
+ myStruct(new Myarray.MyStruct.Builder().
+ a(1).
+ b(2))).
+
+ myarray(new Myarray.Builder().
+ intval(5).
+ enumval(Myarray.Enumval.INNER).
+ refval(":parent:").
+ fileVal("file1").
+ anotherarray(new Myarray.Anotherarray.Builder().
+ foo(1).
+ foo(2)).
+ myStruct(new Myarray.MyStruct.Builder().
+ a(-1).
+ b(-2)));
+
+ }
+} \ No newline at end of file
diff --git a/config-lib/src/test/java/com/yahoo/config/DoubleNodeTest.java b/config-lib/src/test/java/com/yahoo/config/DoubleNodeTest.java
new file mode 100644
index 00000000000..0c0d85db951
--- /dev/null
+++ b/config-lib/src/test/java/com/yahoo/config/DoubleNodeTest.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;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class DoubleNodeTest {
+ @Test
+ public void testSetValue() {
+ DoubleNode n = new DoubleNode();
+ assertFalse(n.doSetValue("invalid"));
+ assertTrue(n.doSetValue("3.14"));
+ assertEquals(3.14, n.value(), 0.001);
+ }
+}
diff --git a/config-lib/src/test/java/com/yahoo/config/EnumNodeTest.java b/config-lib/src/test/java/com/yahoo/config/EnumNodeTest.java
new file mode 100644
index 00000000000..c7801d3eecb
--- /dev/null
+++ b/config-lib/src/test/java/com/yahoo/config/EnumNodeTest.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;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class EnumNodeTest {
+ private static class MyNode extends EnumNode<MyNode.Enum> {
+ public enum Enum { ONE, TWO }
+ public final static Enum ONE = Enum.ONE;
+ public final static Enum TWO = Enum.TWO;
+
+ @Override
+ protected boolean doSetValue(@NonNull String name) {
+ try {
+ value = Enum.valueOf(name);
+ return true;
+ } catch (IllegalArgumentException e) {
+ }
+ return false;
+ }
+
+ }
+
+ @Test
+ public void testEnumNode() {
+ MyNode n = new MyNode();
+ assertNull(n.getValue());
+ assertThat(n.toString(), is("(null)"));
+ assertTrue(n.doSetValue("ONE"));
+ assertThat(n.getValue(), is("ONE"));
+ assertThat(n.toString(), is("ONE"));
+ assertFalse(n.doSetValue("THREE"));
+ }
+}
diff --git a/config-lib/src/test/java/com/yahoo/config/FileNodeTest.java b/config-lib/src/test/java/com/yahoo/config/FileNodeTest.java
new file mode 100644
index 00000000000..2b0816a5b81
--- /dev/null
+++ b/config-lib/src/test/java/com/yahoo/config/FileNodeTest.java
@@ -0,0 +1,26 @@
+// 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 org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class FileNodeTest {
+ @Test
+ public void testSetValue() {
+ FileNode n = new FileNode();
+ assertThat(n.toString(), is("(null)"));
+ assertTrue(n.doSetValue("foo.txt"));
+ assertThat(n.value().value(), is("foo.txt"));
+ assertTrue(n.doSetValue("\"foo.txt\""));
+ assertThat(n.value().value(), is("foo.txt"));
+ assertThat(n.toString(), is("\"foo.txt\""));
+ }
+}
diff --git a/config-lib/src/test/java/com/yahoo/config/IntegerNodeTest.java b/config-lib/src/test/java/com/yahoo/config/IntegerNodeTest.java
new file mode 100644
index 00000000000..bc675f63af8
--- /dev/null
+++ b/config-lib/src/test/java/com/yahoo/config/IntegerNodeTest.java
@@ -0,0 +1,23 @@
+// 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 org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class IntegerNodeTest {
+ @Test
+ public void testSetValue() {
+ IntegerNode n = new IntegerNode();
+ assertFalse(n.setValue("invalid"));
+ assertTrue(n.setValue("10"));
+ assertThat(n.value(), is(10));
+ }
+}
diff --git a/config-lib/src/test/java/com/yahoo/config/LongNodeTest.java b/config-lib/src/test/java/com/yahoo/config/LongNodeTest.java
new file mode 100644
index 00000000000..f403d075aca
--- /dev/null
+++ b/config-lib/src/test/java/com/yahoo/config/LongNodeTest.java
@@ -0,0 +1,23 @@
+// 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 org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class LongNodeTest {
+ @Test
+ public void testSetValue() {
+ LongNode n = new LongNode();
+ assertFalse(n.setValue("invalid"));
+ assertTrue(n.setValue("10"));
+ assertThat(n.value(), is(10l));
+ }
+}
diff --git a/config-lib/src/test/java/com/yahoo/config/NodeVectorTest.java b/config-lib/src/test/java/com/yahoo/config/NodeVectorTest.java
new file mode 100644
index 00000000000..4e869632335
--- /dev/null
+++ b/config-lib/src/test/java/com/yahoo/config/NodeVectorTest.java
@@ -0,0 +1,116 @@
+// 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 org.junit.Test;
+
+import java.util.Arrays;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class NodeVectorTest {
+ @Test
+ public void require_vector_is_resized() {
+ TestNodeVector v = new TestNodeVector("foo");
+ v.setSize(2);
+ assertThat(v.size(), is(2));
+ v.setSize(1);
+ assertThat(v.size(), is(1));
+ }
+
+ @Test(expected = NodeVector.ReadOnlyException.class)
+ public void require_that_add_throws_exception() { new TestNodeVector("foo").add("bar"); }
+
+ @Test(expected = NodeVector.ReadOnlyException.class)
+ public void require_that_addindex_throws_exception() { new TestNodeVector("foo").add(0, "bar"); }
+
+ @Test(expected = NodeVector.ReadOnlyException.class)
+ public void require_that_addAll_throws_exception() { new TestNodeVector("foo").addAll(Arrays.asList("bar")); }
+
+ @Test(expected = NodeVector.ReadOnlyException.class)
+ public void require_that_addAllindex_throws_exception() { new TestNodeVector("foo").addAll(0, Arrays.asList("bar")); }
+
+ @Test(expected = NodeVector.ReadOnlyException.class)
+ public void require_that_clear_throws_exception() { new TestNodeVector("foo").clear(); }
+
+ @Test(expected = NodeVector.ReadOnlyException.class)
+ public void require_that_remove_index_throws_exception() { new TestNodeVector("foo").remove(0); }
+
+ @Test(expected = NodeVector.ReadOnlyException.class)
+ public void require_that_remove_object_throws_exception() { new TestNodeVector("foo").remove(null); }
+
+ @Test(expected = NodeVector.ReadOnlyException.class)
+ public void require_that_removeAll_throws_exception() { new TestNodeVector("foo").removeAll(null); }
+
+ @Test(expected = NodeVector.ReadOnlyException.class)
+ public void require_that_retainAll_throws_exception() { new TestNodeVector("foo").retainAll(null); }
+
+ @Test(expected = NodeVector.ReadOnlyException.class)
+ public void require_that_set_throws_exception() { new TestNodeVector("foo").set(0, null); }
+
+ @Test
+ public void require_that_contains_works() {
+ String val = "foo";
+ TestNodeVector v = new TestNodeVector(val);
+ v.setSize(1);
+ assertTrue(v.contains(val));
+ assertFalse(v.contains("bar"));
+ assertTrue(v.containsAll(Arrays.asList(val)));
+ assertFalse(v.containsAll(Arrays.asList(val, "bar")));
+ }
+
+ @Test
+ public void require_that_indexOf_works() {
+ String val = "foo";
+ TestNodeVector v = new TestNodeVector(val);
+ assertTrue(v.isEmpty());
+ v.setSize(1);
+ assertFalse(v.isEmpty());
+ assertThat(v.indexOf(val), is(0));
+ assertThat(v.indexOf("bar"), is(-1));
+ assertThat(v.lastIndexOf(val), is(0));
+ assertThat(v.lastIndexOf("bar"), is(-1));
+ }
+
+ @Test
+ public void require_that_iterators_work() {
+ String val = "foo";
+ TestNodeVector v = new TestNodeVector(val);
+ v.setSize(3);
+ assertTrue(v.listIterator().hasNext());
+ assertTrue(v.listIterator(0).hasNext());
+ assertTrue(v.listIterator(1).hasNext());
+ assertTrue(v.listIterator(2).hasNext());
+ assertFalse(v.listIterator(3).hasNext());
+ }
+
+ @Test
+ public void require_that_sublisting_works() {
+ String val = "foo";
+ TestNodeVector v = new TestNodeVector(val);
+ v.setSize(3);
+ assertThat(v.subList(0, 1).size(), is(1));
+ assertThat(v.subList(0, 2).size(), is(2));
+ assertThat(v.subList(0, 3).size(), is(3));
+ String[] vals = v.toArray(new String[0]);
+ assertThat(vals.length, is(3));
+ }
+
+ private static class TestNodeVector extends NodeVector<String> {
+ private final String value;
+ public TestNodeVector(String value) {
+ this.value = value;
+ }
+
+ @Override
+ protected String createNew() {
+ return value;
+ }
+ }
+}
diff --git a/config-lib/src/test/java/com/yahoo/config/PathNodeTest.java b/config-lib/src/test/java/com/yahoo/config/PathNodeTest.java
new file mode 100644
index 00000000000..d587834d3b2
--- /dev/null
+++ b/config-lib/src/test/java/com/yahoo/config/PathNodeTest.java
@@ -0,0 +1,26 @@
+// 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 org.junit.Test;
+
+import java.io.File;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author gjoranv
+ * @since 5.1.28
+ */
+public class PathNodeTest {
+
+ @Test
+ public void testSetValue() {
+ PathNode n = new PathNode();
+ assertThat(n.toString(), is("(null)"));
+
+ n = new PathNode(new FileReference("foo.txt"));
+ assertThat(n.value(), is(new File("foo.txt").toPath()));
+ }
+
+}
diff --git a/config-lib/src/test/java/com/yahoo/config/StringNodeTest.java b/config-lib/src/test/java/com/yahoo/config/StringNodeTest.java
new file mode 100644
index 00000000000..c0169a06559
--- /dev/null
+++ b/config-lib/src/test/java/com/yahoo/config/StringNodeTest.java
@@ -0,0 +1,46 @@
+// 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 org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author <a href="mailto:musum@yahoo-inc.com">Harald Musum</a>
+ * @since 5.1.7
+ */
+public class StringNodeTest {
+
+ @Test
+ public void testUnescapeQuotedString() {
+ String a = "\"Hei\"";
+ assertThat(StringNode.unescapeQuotedString(a), is("Hei"));
+ assertThat(StringNode.unescapeQuotedString("foo\"bar\""), is("foo\"bar\""));
+ assertThat(StringNode.unescapeQuotedString("foo\\\"bar\\\""), is("foo\"bar\""));
+ assertThat(StringNode.unescapeQuotedString("a\\rb\\tc\\fd"), is("a\rb\tc\fd"));
+ assertThat(StringNode.unescapeQuotedString("\\x55"), is("U"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testUnescapedQuotedStringExceptions() {
+ StringNode.unescapeQuotedString("foo\\");
+ }
+
+ @Test
+ public void testToString() {
+ StringNode n = new StringNode();
+ assertThat(n.toString(), is("(null)"));
+ n.setValue("foo");
+ assertThat(n.toString(), is("\"foo\""));
+ }
+
+ @Test
+ public void testSetValue() {
+ StringNode n = new StringNode();
+ n.setValue("\"foo\"");
+ assertThat(n.getValue(), is("foo"));
+ n.setValue("foo");
+ assertThat(n.getValue(), is("foo"));
+ }
+}
diff --git a/config-lib/src/test/resources/configdefinitions/app.def b/config-lib/src/test/resources/configdefinitions/app.def
new file mode 100644
index 00000000000..40db5310927
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/app.def
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=test
+
+message string default="Hello!"
+
+times int default=1
+
+a[].name string
diff --git a/config-lib/src/test/resources/configdefinitions/arraytypes.def b/config-lib/src/test/resources/configdefinitions/arraytypes.def
new file mode 100644
index 00000000000..3529b906c4a
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/arraytypes.def
@@ -0,0 +1,11 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Config containing only simple array types that can be used for testing
+# individual types in detail.
+namespace=test
+
+boolarr[] bool
+doublearr[] double
+enumarr[] enum { VAL1, VAL2 }
+intarr[] int
+longarr[] long
+stringarr[] string
diff --git a/config-lib/src/test/resources/configdefinitions/chains-test.def b/config-lib/src/test/resources/configdefinitions/chains-test.def
new file mode 100644
index 00000000000..5cc593b1443
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/chains-test.def
@@ -0,0 +1,42 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Chains configuration
+namespace=test
+
+component[].id string
+
+# Configured functionality provided by this - comes in addition to those set in the code
+component[].dependencies.provides[] string
+
+# Configured "before" dependencies provided by this - comes in addition to those set in the code
+component[].dependencies.before[] string
+
+# Configured "after" dependencies provided by this - comes in addition to those set in the code
+component[].dependencies.after[] string
+
+# The id of this chain. The id has the form name(:version)?
+# where the version has the form 1(.2(.3(.identifier)?)?)?.
+# The default chain must be called "default".
+chain[].id string
+
+#The type of this chain
+chain[].type enum {DOCPROC, SEARCH} default=SEARCH
+
+# The id of a component to include in this chain.
+# The id has the form fullclassname(:version)?
+# where the version has the form 1(.2(.3(.identifier)?)?)?.
+chain[].component[] string
+
+# The optional list of chain ids this inherits.
+# The ids has the form name(:version)?
+# where the version has the form 1(.2(.3(.identifier)?)?)?.
+# If the version is not specified the newest version is used.
+chain[].inherit[] string
+
+# The optional list of component ids to exclude from this chain even if they exists in inherited chains
+# If versions are specified in these ids, they are ignored.
+chain[].exclude[] string
+
+# The phases for a chain
+chain[].phase[].id string
+chain[].phase[].before[] string
+chain[].phase[].after[] string
diff --git a/config-lib/src/test/resources/configdefinitions/datastructures.def b/config-lib/src/test/resources/configdefinitions/datastructures.def
new file mode 100644
index 00000000000..648fe569020
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/datastructures.def
@@ -0,0 +1,11 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=test
+
+date[] string
+
+stock[].ticker string
+stock[].type enum { COMMON, ETF, ETC } default=COMMON
+stock[].volume[] int
+
+basicstruct.foo string default="foo"
+basicstruct.bar int default=0
diff --git a/config-lib/src/test/resources/configdefinitions/defaulttest.def b/config-lib/src/test/resources/configdefinitions/defaulttest.def
new file mode 100644
index 00000000000..eec012ffbb9
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/defaulttest.def
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=test
+
+nondefaultstring string
+defaultstring string default="thedefault"
+
+nondefaultreference reference
+defaultreference reference default="thedefault"
diff --git a/config-lib/src/test/resources/configdefinitions/function-test.def b/config-lib/src/test/resources/configdefinitions/function-test.def
new file mode 100644
index 00000000000..935efe9ca21
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/function-test.def
@@ -0,0 +1,93 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#
+# This def file should test most aspects of def files that makes a difference
+# for the autogenerated config classes. The goal is to trigger all blocks of
+# code in the code generators. This includes:
+#
+# - Use all legal special characters in the def file name, to ensure that those
+# that needs to be replaced in type names are actually replaced.
+# - Use the same enum type twice to verify that we dont declare or define it
+# twice.
+# - Use the same struct type twice for the same reason.
+# - Include arrays of primitives and structs.
+# - Include enum primitives and array of enums. Arrays of enums must be handled
+# specially by the C++ code.
+# - Include enums both with and without default values.
+# - Include primitive string, numbers & doubles both with and without default
+# values.
+# - Have an array within a struct, to verify that we correctly recurse.
+# - Reuse type name further within to ensure that this works.
+
+version=8 # deprecated, remove in Vespa 7
+namespace=test
+
+# Some random bool without a default value. These comments exist to check
+ # that comment parsing works.
+bool_val bool restart
+ ## A bool with a default value set.
+bool_with_def bool default=false restart
+# An int value
+# Also test that multiline comments
+# work.
+int_val int restart
+int_with_def int default=-545 restart
+long_val long restart
+long_with_def long default=-50000000000 restart
+double_val double restart
+double_with_def double default=-6.43 restart
+# Another comment
+string_val string restart
+stringwithdef string default="foobar" restart
+enum_val enum { FOO, BAR, FOOBAR } restart
+enumwithdef enum { FOO2, BAR2, FOOBAR2 } default=BAR2 restart
+onechoice enum { ONLYFOO } default=ONLYFOO restart
+refval reference restart
+refwithdef reference default=":parent:" restart
+fileVal file restart
+pathVal path restart
+
+boolarr[] bool restart
+intarr[] int restart
+longarr[] long restart
+doublearr[] double restart
+stringarr[] string restart
+enumarr[] enum { ARRAY, VALUES } restart
+refarr[] reference restart
+fileArr[] file restart
+pathArr[] path restart
+
+#This is a map of ints.
+intMap{} int restart
+stringMap{} string restart
+filemap{} file restart
+pathMap{} path restart
+
+# A basic struct
+basicStruct.foo string default="basic" restart
+basicStruct.bar int restart
+basicStruct.intArr[] int restart
+
+# A struct of struct
+rootStruct.inner0.name string default="inner0" restart
+rootStruct.inner0.index int restart
+rootStruct.inner1.name string default="inner1" restart
+rootStruct.inner1.index int restart
+rootStruct.innerArr[].boolVal bool default=false restart
+rootStruct.innerArr[].stringVal string restart
+
+# This is my array
+myarray[].intval int default=14 restart
+myarray[].stringval[] string restart
+myarray[].enumval enum { INNER, ENUM, TYPE } default=TYPE restart
+myarray[].refval reference # Value in array without default restart
+myarray[].fileVal file restart
+myarray[].anotherarray[].foo int default=-4 restart
+myarray[].myStruct.a int restart
+myarray[].myStruct.b int default=2 restart
+
+myStructMap{}.myInt int restart
+myStructMap{}.myString string restart
+myStructMap{}.myIntDef int default=56 restart
+myStructMap{}.myStringDef string default="g" restart
+myStructMap{}.anotherMap{}.anInt int restart
+myStructMap{}.anotherMap{}.anIntDef int default=11 restart
diff --git a/config-lib/src/test/resources/configdefinitions/int.def b/config-lib/src/test/resources/configdefinitions/int.def
new file mode 100755
index 00000000000..0bf82ed9987
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/int.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=test
+
+intVal int default=1
diff --git a/config-lib/src/test/resources/configdefinitions/maptypes.def b/config-lib/src/test/resources/configdefinitions/maptypes.def
new file mode 100644
index 00000000000..389a9b71012
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/maptypes.def
@@ -0,0 +1,13 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Config containing only structs in various forms
+namespace=foo
+
+boolmap{} bool
+intmap{} int
+longmap{} long
+doublemap{} double
+stringmap{} string
+filemap{} file
+
+innermap{}.foo int
+nestedmap{}.inner{} int
diff --git a/config-lib/src/test/resources/configdefinitions/md5test.def b/config-lib/src/test/resources/configdefinitions/md5test.def
new file mode 100644
index 00000000000..86a199ea785
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/md5test.def
@@ -0,0 +1,24 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# version=4 , version in comment does not count.
+
+# Added empty line to see if we can confuse
+# the server's md5 calculation
+namespace=test
+
+#even adding a variable name starting with 'version'
+versiontag int default=3
+
+blabla string default=""
+tabs string default=" "
+test int
+
+# test multiple spaces/tabs
+spaces int
+singletab string
+multitabs double
+
+# test enum
+normal enum { VAL1, VAL2 } default=VAL1
+spacevalues enum { V1 , V2 , V3 , V4 } default=V3
+
+# Comments and empty lines at the end
diff --git a/config-lib/src/test/resources/configdefinitions/namespace.def b/config-lib/src/test/resources/configdefinitions/namespace.def
new file mode 100644
index 00000000000..e51a06e87b8
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/namespace.def
@@ -0,0 +1,6 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=test
+
+namespace=myproject.config
+
+a int
diff --git a/config-lib/src/test/resources/configdefinitions/restart.def b/config-lib/src/test/resources/configdefinitions/restart.def
new file mode 100755
index 00000000000..417a015a3d0
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/restart.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=test
+
+intVal int default=1 restart
diff --git a/config-lib/src/test/resources/configdefinitions/simpletypes.def b/config-lib/src/test/resources/configdefinitions/simpletypes.def
new file mode 100644
index 00000000000..314c67ae709
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/simpletypes.def
@@ -0,0 +1,11 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Config containing only simple leaf types with default values, that can be used
+# for testing individual types in detail.
+namespace=test
+
+boolval bool default=false
+doubleval double default=0.0
+enumval enum { VAL1, VAL2 } default=VAL1
+intval int default=0
+longval long default=0
+stringval string default="s"
diff --git a/config-lib/src/test/resources/configdefinitions/specialtypes.def b/config-lib/src/test/resources/configdefinitions/specialtypes.def
new file mode 100644
index 00000000000..3243288f0af
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/specialtypes.def
@@ -0,0 +1,5 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=test
+
+myfile file
+myref reference
diff --git a/config-lib/src/test/resources/configdefinitions/standard.def b/config-lib/src/test/resources/configdefinitions/standard.def
new file mode 100644
index 00000000000..e065535f97d
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/standard.def
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Config containing only simple leaf types with default values, that can be used
+# for testing individual types in detail.
+namespace=test
+
+basicStruct.intVal int default=0
+basicStruct.stringVal string default="s"
+stringArr[] string
diff --git a/config-lib/src/test/resources/configdefinitions/string.def b/config-lib/src/test/resources/configdefinitions/string.def
new file mode 100755
index 00000000000..80ac3f4b1e6
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/string.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=test
+
+stringVal string default="_default_"
diff --git a/config-lib/src/test/resources/configdefinitions/structtypes.def b/config-lib/src/test/resources/configdefinitions/structtypes.def
new file mode 100644
index 00000000000..0a3c8e23a45
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/structtypes.def
@@ -0,0 +1,21 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Config containing only structs in various forms
+namespace=test
+
+simple.name string default="_default_"
+simple.gender enum { MALE, FEMALE } default=MALE
+simple.emails[] string
+
+nested.inner.name string default="_default_"
+nested.inner.gender enum { MALE, FEMALE } default=MALE
+nested.inner.emails[] string
+
+simplearr[].name string
+simplearr[].gender enum { MALE, FEMALE }
+
+nestedarr[].inner.name string
+nestedarr[].inner.gender enum { MALE, FEMALE }
+nestedarr[].inner.emails[] string
+
+complexarr[].innerarr[].name string
+complexarr[].innerarr[].gender enum { MALE, FEMALE }
diff --git a/config-lib/src/test/resources/configdefinitions/test-nodefs.def b/config-lib/src/test/resources/configdefinitions/test-nodefs.def
new file mode 100644
index 00000000000..4a80231d709
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/test-nodefs.def
@@ -0,0 +1,16 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=test
+
+# test config vars with no defaults
+
+s string
+j int
+b bool
+f double
+e enum { AA, BB, CC }
+
+basicstruct.foo string
+basicstruct.bar int
+
+arr[].s string
+arr[].i int
diff --git a/config-lib/src/test/resources/configdefinitions/test-nonstring.def b/config-lib/src/test/resources/configdefinitions/test-nonstring.def
new file mode 100644
index 00000000000..3e54a3bb8bd
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/test-nonstring.def
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=test
+
+# Test non-string config vars with defaults
+
+i int default=0
+b bool default=false
+d double default=0.0
+e enum { AA, BB, CC } default=AA
diff --git a/config-lib/src/test/resources/configdefinitions/test-reference.def b/config-lib/src/test/resources/configdefinitions/test-reference.def
new file mode 100644
index 00000000000..96c5f62030a
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/test-reference.def
@@ -0,0 +1,4 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=test
+
+configId reference default=":parent:"
diff --git a/config-lib/src/test/resources/configdefinitions/testnamespace.def b/config-lib/src/test/resources/configdefinitions/testnamespace.def
new file mode 100644
index 00000000000..b77eb5d81da
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/testnamespace.def
@@ -0,0 +1,3 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=foo
+basicStruct.stringVal string
diff --git a/config-lib/src/test/resources/configdefinitions/unicode.def b/config-lib/src/test/resources/configdefinitions/unicode.def
new file mode 100644
index 00000000000..52b2353e60e
--- /dev/null
+++ b/config-lib/src/test/resources/configdefinitions/unicode.def
@@ -0,0 +1,5 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=test
+
+unicodestring1 string
+unicodestring2 string default="abc æøå 囲碁 ÆØÅ ABC"