summaryrefslogtreecommitdiffstats
path: root/configgen/src/main/java/com/yahoo
diff options
context:
space:
mode:
Diffstat (limited to 'configgen/src/main/java/com/yahoo')
-rw-r--r--configgen/src/main/java/com/yahoo/config/codegen/BuilderGenerator.java351
-rw-r--r--configgen/src/main/java/com/yahoo/config/codegen/ConfigGenerator.java444
-rw-r--r--configgen/src/main/java/com/yahoo/config/codegen/ConfiggenUtil.java2
-rw-r--r--configgen/src/main/java/com/yahoo/config/codegen/JavaClassBuilder.java170
4 files changed, 966 insertions, 1 deletions
diff --git a/configgen/src/main/java/com/yahoo/config/codegen/BuilderGenerator.java b/configgen/src/main/java/com/yahoo/config/codegen/BuilderGenerator.java
new file mode 100644
index 00000000000..bf3fc2902a1
--- /dev/null
+++ b/configgen/src/main/java/com/yahoo/config/codegen/BuilderGenerator.java
@@ -0,0 +1,351 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.codegen;
+
+import com.yahoo.config.codegen.LeafCNode.FileLeaf;
+import com.yahoo.config.codegen.LeafCNode.PathLeaf;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.yahoo.config.codegen.ConfigGenerator.boxedDataType;
+import static com.yahoo.config.codegen.ConfigGenerator.indentCode;
+import static com.yahoo.config.codegen.ConfigGenerator.nodeClass;
+import static com.yahoo.config.codegen.ConfigGenerator.userDataType;
+import static com.yahoo.config.codegen.JavaClassBuilder.INDENTATION;
+import static com.yahoo.config.codegen.JavaClassBuilder.createUniqueSymbol;
+import static com.yahoo.config.codegen.ReservedWords.INTERNAL_PREFIX;
+import static java.util.Arrays.stream;
+
+/**
+ * @author gjoranv
+ * @author ollivir
+ */
+
+public class BuilderGenerator {
+ public static String getBuilder(InnerCNode node) {
+ return getDeclaration(node) + "\n" + //
+ indentCode(INDENTATION, getUninitializedScalars(node) + "\n\n" + //
+ stream(node.getChildren()).map(BuilderGenerator::getBuilderFieldDefinition).collect(Collectors.joining("\n"))
+ + "\n\n" + //
+ getBuilderConstructors(node, nodeClass(node)) + "\n\n" + //
+ getOverrideMethod(node) + "\n\n" + //
+ getBuilderSetters(node) + "\n" + //
+ getSpecialRootBuilderCode(node))
+ + "}";
+ }
+
+ private static String getDeclaration(InnerCNode node) {
+ String getInterfaces = (node.getParent() == null) ? "implements ConfigInstance.Builder" : "implements ConfigBuilder";
+
+ return "public static class Builder " + getInterfaces + " {";
+ }
+
+ private static String getSpecialRootBuilderCode(InnerCNode node) {
+ return (node.getParent() == null) ? "\n" + getDispatchCode() + "\n" : "";
+ }
+
+ private static String getDispatchCode() {
+ // Use full path to @Override, as users are free to define an inner node called
+ // 'override'. (summarymap.def does)
+ // The generated inner 'Override' class would otherwise be mistaken for the
+ // annotation.
+ return "@java.lang.Override\n" + //
+ "public final boolean dispatchGetConfig(ConfigInstance.Producer producer) {\n" + //
+ " if (producer instanceof Producer) {\n" + //
+ " ((Producer)producer).getConfig(this);\n" + //
+ " return true;\n" + //
+ " }\n" + //
+ " return false;\n" + //
+ "}\n" + //
+ "\n" + //
+ "@java.lang.Override\n" + //
+ "public final String getDefMd5() { return CONFIG_DEF_MD5; }\n" + //
+ "@java.lang.Override\n" + //
+ "public final String getDefName() { return CONFIG_DEF_NAME; }\n" + //
+ "@java.lang.Override\n" + //
+ "public final String getDefNamespace() { return CONFIG_DEF_NAMESPACE; }";
+ }
+
+ private static String getUninitializedScalars(InnerCNode node) {
+ List<String> scalarsWithoutDefault = new ArrayList<>();
+ for (CNode child : node.getChildren()) {
+ if (child instanceof LeafCNode && (!child.isArray && !child.isMap && ((LeafCNode) child).getDefaultValue() == null)) {
+ scalarsWithoutDefault.add("\"" + child.getName() + "\"");
+ }
+ }
+
+ String uninitializedList = (scalarsWithoutDefault.size() > 0)
+ ? "Arrays.asList(\n" + indentCode(INDENTATION, String.join(",\n", scalarsWithoutDefault) + "\n)")
+ : "";
+
+ return "private Set<String> " + INTERNAL_PREFIX + "uninitialized = new HashSet<String>(" + uninitializedList + ");";
+ }
+
+ private static String getBuilderFieldDefinition(CNode node) {
+ if (node.isArray) {
+ return String.format("public List<%s> %s = new ArrayList<>();", builderType(node), node.getName());
+ } else if (node.isMap) {
+ return String.format("public Map<String, %s> %s = new LinkedHashMap<>();", builderType(node), node.getName());
+ } else if (node instanceof InnerCNode) {
+ return String.format("public %s %s = new %s();", builderType(node), node.getName(), builderType(node));
+ } else if (node instanceof LeafCNode) {
+ return String.format("private %s %s = null;", boxedBuilderType((LeafCNode) node), node.getName());
+ } else {
+ throw new IllegalStateException("Cannot produce builder field definition for node"); // Should not happen
+ }
+ }
+
+ private static String getBuilderSetters(CNode node) {
+ List<String> elem = new ArrayList<>();
+ CNode[] children = node.getChildren();
+
+ for (CNode child : children) {
+ if (child instanceof InnerCNode && child.isArray) {
+ elem.add(BuilderSetters.innerArraySetters((InnerCNode) child));
+ } else if (child instanceof InnerCNode && child.isMap) {
+ elem.add(BuilderSetters.innerMapSetters(child));
+ } else if (child instanceof LeafCNode && child.isArray) {
+ elem.add(BuilderSetters.leafArraySetters((LeafCNode) child));
+ } else if (child instanceof LeafCNode && child.isMap) {
+ elem.add(BuilderSetters.leafMapSetters(child));
+ } else if (child instanceof InnerCNode) {
+ elem.add(BuilderSetters.structSetter((InnerCNode) child));
+ } else if (child instanceof LeafCNode) {
+ elem.add(BuilderSetters.scalarSetters((LeafCNode) child));
+ }
+ }
+ return String.join("\n\n", elem);
+ }
+
+ private static class BuilderSetters {
+ private static String structSetter(InnerCNode n) {
+ return "public Builder " + n.getName() + "(" + builderType(n) + " " + INTERNAL_PREFIX + "builder) {\n" + //
+ " " + n.getName() + " = " + INTERNAL_PREFIX + "builder;\n" + //
+ " return this;\n" + //
+ "}";
+ }
+
+ private static String innerArraySetters(InnerCNode n) {
+ return "/**\n" + //
+ " * Add the given builder to this builder's list of " + nodeClass(n) + " builders\n" + //
+ " * @param " + INTERNAL_PREFIX + "builder a builder\n" + //
+ " * @return this builder\n" + //
+ " */\n" + //
+ "public Builder " + n.getName() + "(" + builderType(n) + " " + INTERNAL_PREFIX + "builder) {\n" + //
+ " " + n.getName() + ".add(" + INTERNAL_PREFIX + "builder);\n" + //
+ " return this;\n" + //
+ "}\n" + //
+ "\n" + //
+ "/**\n" + //
+ " * Set the given list as this builder's list of " + nodeClass(n) + " builders\n" + //
+ " * @param __builders a list of builders\n" + //
+ " * @return this builder\n" + //
+ " */\n" + //
+ "public Builder " + n.getName() + "(List<" + builderType(n) + "> __builders) {\n" + //
+ " " + n.getName() + " = __builders;\n" + //
+ " return this;\n" + //
+ "}";
+ }
+
+ private static String publicLeafNodeSetters(LeafCNode n) {
+ return "public Builder " + n.getName() + "(" + builderType(n) + " " + INTERNAL_PREFIX + "value) {\n" + //
+ " " + n.getName() + ".add(" + INTERNAL_PREFIX + "value);\n" + //
+ " return this;\n" + //
+ "}\n" + //
+ "\n" + //
+ "public Builder " + n.getName() + "(Collection<" + builderType(n) + "> " + INTERNAL_PREFIX + "values) {\n" + //
+ " " + n.getName() + ".addAll(" + INTERNAL_PREFIX + "values);\n" + //
+ " return this;\n" + //
+ "}";
+ }
+
+ private static String privateLeafNodeSetter(LeafCNode n) {
+ if ("String".equals(builderType(n)) || "FileReference".equals(builderType(n))) {
+ return "";
+ } else {
+ return "\n\n" + //
+ "private Builder " + n.getName() + "(String " + INTERNAL_PREFIX + "value) {\n" + //
+ " return " + n.getName() + "(" + builderType(n) + ".valueOf(" + INTERNAL_PREFIX + "value));\n" + //
+ "}";
+ }
+ }
+
+ private static String leafArraySetters(LeafCNode n) {
+ return publicLeafNodeSetters(n) + privateLeafNodeSetter(n);
+ }
+
+ private static String innerMapSetters(CNode n) {
+ return "public Builder " + n.getName() + "(String " + INTERNAL_PREFIX + "key, " + builderType(n) + " " + INTERNAL_PREFIX
+ + "value) {\n" + //
+ " " + n.getName() + ".put(" + INTERNAL_PREFIX + "key, " + INTERNAL_PREFIX + "value);\n" + //
+ " return this;\n" + //
+ "}\n" + //
+ "\n" + //
+ "public Builder " + n.getName() + "(Map<String, " + builderType(n) + "> " + INTERNAL_PREFIX + "values) {\n" + //
+ " " + n.getName() + ".putAll(" + INTERNAL_PREFIX + "values);\n" + //
+ " return this;\n" + //
+ "}";
+ }
+
+ private static String privateLeafMapSetter(CNode n) {
+ if ("String".equals(builderType(n)) || "FileReference".equals(builderType(n))) {
+ return "";
+ } else {
+ return "\n\n" + //
+ "private Builder " + n.getName() + "(String " + INTERNAL_PREFIX + "key, String " + INTERNAL_PREFIX + "value) {\n" + //
+ " return " + n.getName() + "(" + INTERNAL_PREFIX + "key, " + builderType(n) + ".valueOf(" + INTERNAL_PREFIX
+ + "value));\n" + //
+ "}";
+ }
+ }
+
+ private static String leafMapSetters(CNode n) {
+ return innerMapSetters(n) + privateLeafMapSetter(n);
+ }
+
+ private static String scalarSetters(LeafCNode n) {
+ String name = n.getName();
+
+ String signalInitialized = (n.getDefaultValue() == null) ? " " + INTERNAL_PREFIX + "uninitialized.remove(\"" + name + "\");\n"
+ : "";
+
+ String bType = builderType(n);
+ String stringSetter = "String".equals(bType) || "FileReference".equals(bType) ? ""
+ : String.format("\nprivate Builder %s(String %svalue) {\n" + //
+ " return %s(%s.valueOf(%svalue));\n" + //
+ "}", name, INTERNAL_PREFIX, name, boxedDataType(n), INTERNAL_PREFIX);
+
+ String getNullGuard = bType.equals(boxedBuilderType(n)) ? String.format(
+ "\nif (%svalue == null) throw new IllegalArgumentException(\"Null value is not allowed.\");", INTERNAL_PREFIX) : "";
+
+ return String.format("public Builder %s(%s %svalue) {%s\n" + //
+ " %s = %svalue;\n" + //
+ "%s", name, bType, INTERNAL_PREFIX, getNullGuard, name, INTERNAL_PREFIX, signalInitialized) + //
+ " return this;" + "\n}\n" + stringSetter;
+ }
+ }
+
+ private static String setBuilderValueFromConfig(CNode child, CNode node) {
+ final String name = child.getName();
+ final boolean isArray = child.isArray;
+ final boolean isMap = child.isMap;
+
+ if (child instanceof FileLeaf && isArray) {
+ return name + "(" + userDataType(child) + ".toValues(config." + name + "()));";
+ } else if (child instanceof FileLeaf && isMap) {
+ return name + "(" + userDataType(child) + ".toValueMap(config." + name + "()));";
+ } else if (child instanceof FileLeaf) {
+ return name + "(config." + name + "().value());";
+ } else if (child instanceof PathLeaf && isArray) {
+ return name + "(" + nodeClass(child) + ".toFileReferences(config." + name + "));";
+ } else if (child instanceof PathLeaf && isMap) {
+ return name + "(" + nodeClass(child) + ".toFileReferenceMap(config." + name + "));";
+ } else if (child instanceof PathLeaf) {
+ return name + "(config." + name + ".getFileReference());";
+ } else if (child instanceof LeafCNode) {
+ return name + "(config." + name + "());";
+ } else if (child instanceof InnerCNode && isArray) {
+ return setInnerArrayBuildersFromConfig((InnerCNode) child, node);
+ } else if (child instanceof InnerCNode && isMap) {
+ return setInnerMapBuildersFromConfig((InnerCNode) child);
+ } else {
+ return name + "(new " + builderType(child) + "(config." + name + "()));";
+ }
+ }
+
+ private static String setInnerArrayBuildersFromConfig(InnerCNode innerArr, CNode node) {
+ final String elemName = createUniqueSymbol(node, innerArr.getName());
+
+ return "for (" + userDataType(innerArr) + " " + elemName + " : config." + innerArr.getName() + "()) {\n" + //
+ " " + innerArr.getName() + "(new " + builderType(innerArr) + "(" + elemName + "));\n" + //
+ "}";
+ }
+
+ private static String setInnerMapBuildersFromConfig(InnerCNode innerMap) {
+ final String entryName = INTERNAL_PREFIX + "entry";
+ return "for (Map.Entry<String, " + userDataType(innerMap) + "> " + entryName + " : config." + innerMap.getName()
+ + "().entrySet()) {\n" + //
+ " " + innerMap.getName() + "(" + entryName + ".getKey(), new " + userDataType(innerMap) + ".Builder(" + entryName
+ + ".getValue()));\n" + //
+ "}";
+ }
+
+ private static String getBuilderConstructors(CNode node, String className) {
+ return "public Builder() { }\n" + //
+ "\n" + //
+ "public Builder(" + className + " config) {\n" + //
+ indentCode(INDENTATION,
+ stream(node.getChildren()).map(child -> setBuilderValueFromConfig(child, node)).collect(Collectors.joining("\n")))
+ + //
+ "\n}";
+ }
+
+ private static String conditionStatement(CNode child) {
+ final String superior = INTERNAL_PREFIX + "superior";
+
+ if (child.isArray) {
+ return "if (!" + superior + "." + child.getName() + ".isEmpty())";
+ } else if (child.isMap) {
+ return "";
+ } else if (child instanceof LeafCNode) {
+ return "if (" + superior + "." + child.getName() + " != null)";
+ } else {
+ return "";
+ }
+ }
+
+ private static String overrideBuilderValue(CNode child) {
+ final String superior = INTERNAL_PREFIX + "superior";
+ final String method = "override";
+ final String name = child.getName();
+ final String callSetter = name + "(" + superior + "." + name + ");";
+
+ if (child.isArray) {
+ String arrayOverride = INDENTATION + name + ".addAll(" + superior + "." + name + ");";
+ return conditionStatement(child) + "\n" + arrayOverride;
+ } else if (child instanceof InnerCNode && !child.isArray && !child.isMap) {
+ return name + "(" + name + "." + method + "(" + superior + "." + name + "));";
+ } else if (child.isMap) {
+ return callSetter;
+ } else {
+ return conditionStatement(child) + "\n" + INDENTATION + callSetter;
+ }
+ }
+
+ private static String getOverrideMethod(CNode node) {
+ final String superior = INTERNAL_PREFIX + "superior";
+ final String method = "override";
+
+ return "private Builder " + method + "(Builder " + superior + ") {\n" + //
+ indentCode(INDENTATION,
+ stream(node.getChildren()).map(BuilderGenerator::overrideBuilderValue).collect(Collectors.joining("\n")))
+ + "\n" + //
+ " return this;\n" + //
+ "}";
+ }
+
+ private static String builderType(CNode node) {
+ if (node instanceof InnerCNode) {
+ return boxedDataType(node) + ".Builder";
+ } else if (node instanceof FileLeaf) {
+ return "String";
+ } else if (node instanceof PathLeaf) {
+ return "FileReference";
+ } else if (node instanceof LeafCNode && (node.isArray || node.isMap)) {
+ return boxedDataType(node);
+ } else {
+ return userDataType(node);
+ }
+ }
+
+ private static String boxedBuilderType(LeafCNode node) {
+ if (node instanceof FileLeaf) {
+ return "String";
+ } else if (node instanceof PathLeaf) {
+ return "FileReference";
+ } else {
+ return boxedDataType(node);
+ }
+ }
+}
diff --git a/configgen/src/main/java/com/yahoo/config/codegen/ConfigGenerator.java b/configgen/src/main/java/com/yahoo/config/codegen/ConfigGenerator.java
new file mode 100644
index 00000000000..9980cf565b1
--- /dev/null
+++ b/configgen/src/main/java/com/yahoo/config/codegen/ConfigGenerator.java
@@ -0,0 +1,444 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.codegen;
+
+import com.yahoo.config.codegen.LeafCNode.BooleanLeaf;
+import com.yahoo.config.codegen.LeafCNode.DoubleLeaf;
+import com.yahoo.config.codegen.LeafCNode.EnumLeaf;
+import com.yahoo.config.codegen.LeafCNode.FileLeaf;
+import com.yahoo.config.codegen.LeafCNode.IntegerLeaf;
+import com.yahoo.config.codegen.LeafCNode.LongLeaf;
+import com.yahoo.config.codegen.LeafCNode.PathLeaf;
+import com.yahoo.config.codegen.LeafCNode.ReferenceLeaf;
+import com.yahoo.config.codegen.LeafCNode.StringLeaf;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.yahoo.config.codegen.BuilderGenerator.getBuilder;
+import static com.yahoo.config.codegen.JavaClassBuilder.INDENTATION;
+import static com.yahoo.config.codegen.ReservedWords.INTERNAL_PREFIX;
+import static java.util.Arrays.stream;
+
+/**
+ * @author gjoranv
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class ConfigGenerator {
+ // TODO: don't take indent as method param - the caller should indent
+ public static String generateContent(String indent, InnerCNode node, boolean isOuter) {
+ CNode[] children = node.getChildren();
+
+ return indentCode(indent,
+ getBuilder(node) + "\n\n" +
+ stream(children).map(ConfigGenerator::getFieldDefinition).collect(Collectors.joining("\n")) + "\n\n" +
+ getConstructors(node) + "\n\n" +
+ getAccessors(children) + "\n\n" +
+ getGetChangesRequiringRestart(node) + "\n\n" +
+ getContainsFieldsFlaggedWithRestart(node, isOuter) +
+ getStaticMethods(node) +
+ generateCodeForChildren(children, indent)
+ );
+ }
+
+ private static String generateCodeForChildren(CNode[] children, String indent) {
+ List<String> pieces = new LinkedList<>();
+ for (CNode child : children) {
+ if (child instanceof EnumLeaf) {
+ pieces.add(getEnumCode((EnumLeaf) child) + "\n");
+ } else if (child instanceof InnerCNode) {
+ pieces.add(getInnerDefinition((InnerCNode) child, indent) + "\n");
+ }
+ }
+ return String.join("\n", pieces);
+ }
+
+ private static String getInnerDefinition(InnerCNode inner, String indent) {
+ return (getClassDoc(inner) + "\n" +//
+ getClassDeclaration(inner) + "\n" +//
+ generateContent(indent, inner, false)).trim() + "\n}";
+ }
+
+ private static String getClassDeclaration(CNode node) {
+ return "public final static class " + nodeClass(node) + " extends InnerNode { \n";
+ }
+
+ private static String getFieldDefinition(CNode node) {
+ String fieldDef;
+ if (node instanceof LeafCNode && node.isArray) {
+ fieldDef = String.format("LeafNodeVector<%s, %s> %s;", boxedDataType(node), nodeClass(node), node.getName());
+ } else if (node instanceof InnerCNode && node.isArray) {
+ fieldDef = String.format("InnerNodeVector<%s> %s;", nodeClass(node), node.getName());
+ } else if (node.isMap) {
+ fieldDef = String.format("Map<String, %s> %s;", nodeClass(node), node.getName());
+ } else {
+ fieldDef = String.format("%s %s;", nodeClass(node), node.getName());
+ }
+ return node.getCommentBlock("//") + "private final " + fieldDef;
+ }
+
+ private static String getStaticMethods(InnerCNode node) {
+ if (node.isArray) {
+ return getStaticMethodsForInnerArray(node) + "\n\n";
+ } else if (node.isMap) {
+ return getStaticMethodsForInnerMap(node) + "\n\n";
+ } else {
+ return "";
+ }
+ }
+
+ private static String getContainsFieldsFlaggedWithRestart(CNode node, boolean isOuter) {
+ if (isOuter) {
+ return String.format("private static boolean containsFieldsFlaggedWithRestart() {\n" +//
+ " return %b;\n" +//
+ "}\n\n", node.needRestart());
+ } else {
+ return "";
+ }
+ }
+
+ private static String getGetChangesRequiringRestart(InnerCNode node) {
+ List<String> comparisons = new LinkedList<>();
+ for (CNode child : node.getChildren()) {
+ if (child.needRestart()) {
+ comparisons.add("\n " + getComparison(child));
+ }
+ }
+
+ return "private ChangesRequiringRestart getChangesRequiringRestart(" + nodeClass(node) + " newConfig) {\n" +//
+ " ChangesRequiringRestart changes = new ChangesRequiringRestart(\"" + node.getName() + "\");" + String.join("", comparisons) + "\n" +//
+ " return changes;\n" +//
+ "}";
+ }
+
+ private static String quotedComment(CNode node) {
+ return node.getComment().replace("\n", "\\n").replace("\"", "\\\"");
+ }
+
+ private static String getComparison(CNode node) {
+ if (node instanceof InnerCNode && node.isArray) {
+ return " changes.compareArray(this." + node.getName() + ", newConfig." + node.getName() + ", \"" + node.getName() + "\", \"" + quotedComment(node) + "\",\n" +//
+ " (a,b) -> ((" + nodeClass(node) + ")a).getChangesRequiringRestart((" + nodeClass(node) + ")b));";
+ } else if (node instanceof InnerCNode && node.isMap) {
+ return " changes.compareMap(this." + node.getName() + ", newConfig." + node.getName() + ", \"" + node.getName() + "\", \"" + quotedComment(node) + "\",\n" +//
+ " (a,b) -> ((" + nodeClass(node) + ")a).getChangesRequiringRestart((" + nodeClass(node) + ")b));";
+ } else if (node instanceof InnerCNode) {
+ return " changes.mergeChanges(\"" + node.getName() + "\", this." + node.getName() + ".getChangesRequiringRestart(newConfig." + node.getName() + "));";
+ } else if (node.isArray) {
+ return " changes.compareArray(this." + node.getName() + ", newConfig." + node.getName() + ", \"" + node.getName() + "\", \"" + quotedComment(node) + "\",\n" +//
+ " (a,b) -> new ChangesRequiringRestart(\"" + node.getName() + "\").compare(a,b,\"\",\"" + quotedComment(node) + "\"));";
+ } else if (node.isMap) {
+ return " changes.compareMap(this." + node.getName() + ", newConfig." + node.getName() + ", \"" + node.getName() + "\", \"" + quotedComment(node) + "\",\n" +//
+ " (a,b) -> new ChangesRequiringRestart(\"" + node.getName() + "\").compare(a,b,\"\",\"" + quotedComment(node) + "\"));";
+ } else {
+ return " changes.compare(this." + node.getName() + ", newConfig." + node.getName() + ", \"" + node.getName() + "\", \"" + quotedComment(node) + "\");";
+ }
+ }
+
+ private static String scalarDefault(LeafCNode scalar) {
+ if (scalar.getDefaultValue() == null) {
+ return "";
+ } else if (scalar instanceof EnumLeaf && scalar.getDefaultValue().getValue() == null) {
+ return "";
+ } else if (scalar instanceof EnumLeaf) {
+ return nodeClass(scalar) + "." + scalar.getDefaultValue().getStringRepresentation();
+ } else if (scalar instanceof LongLeaf) {
+ return scalar.getDefaultValue().getStringRepresentation() + "L";
+ } else if (scalar instanceof DoubleLeaf) {
+ return scalar.getDefaultValue().getStringRepresentation() + "D";
+ } else {
+ return scalar.getDefaultValue().getStringRepresentation();
+ }
+ }
+
+ private static String assignFromBuilder(CNode child) {
+ final String name = child.getName();
+ final String className = nodeClass(child);
+ final boolean isArray = child.isArray;
+ final boolean isMap = child.isMap;
+
+ if (child instanceof FileLeaf && isArray) {
+ return name + " = LeafNodeVector.createFileNodeVector(builder." + name + ");";
+ } else if (child instanceof PathLeaf && isArray) {
+ return name + " = LeafNodeVector.createPathNodeVector(builder." + name + ");";
+ } else if (child instanceof LeafCNode && isArray) {
+ return name + " = new LeafNodeVector<>(builder." + name + ", new " + className + "());";
+ } else if (child instanceof FileLeaf && isMap) {
+ return name + " = LeafNodeMaps.asFileNodeMap(builder." + name + ");";
+ } else if (child instanceof PathLeaf && isMap) {
+ return name + " = LeafNodeMaps.asPathNodeMap(builder." + name + ");";
+ } else if (child instanceof LeafCNode && isMap) {
+ return name + " = LeafNodeMaps.asNodeMap(builder." + name + ", new " + className + "());";
+ } else if (child instanceof InnerCNode && isArray) {
+ return name + " = " + className + ".createVector(builder." + name + ");";
+ } else if (child instanceof InnerCNode && isMap) {
+ return name + " = " + className + ".createMap(builder." + name + ");";
+ } else if (child instanceof InnerCNode) {
+ return name + " = new " + className + "(builder." + name + ", throwIfUninitialized);";
+ } else if (child instanceof LeafCNode) {
+ return name + " = (builder." + name + " == null) ?\n" +//
+ " new " + className + "(" + scalarDefault((LeafCNode) child) + ") : new " + className + "(builder." + name + ");";
+ } else {
+ throw new IllegalStateException("Cannot create assignment for node"); // should not happen
+ }
+ }
+
+ private static String getConstructors(InnerCNode inner) {
+ // TODO: merge these two constructors into one when the config library uses builders to set values from payload.
+ return "public " + nodeClass(inner) + "(Builder builder) {\n" +//
+ " this(builder, true);\n" +//
+ "}\n" +//
+ "\n" +//
+ "private " + nodeClass(inner) + "(Builder builder, boolean throwIfUninitialized) {\n" +//
+ " if (throwIfUninitialized && ! builder." + INTERNAL_PREFIX + "uninitialized.isEmpty())\n" +//
+ " throw new IllegalArgumentException(\"The following builder parameters for \" +\n" +//
+ " \"" + inner.getFullName() + " must be initialized: \" + builder." + INTERNAL_PREFIX + "uninitialized);\n" +//
+ "\n" +//
+ indentCode(INDENTATION, stream(inner.getChildren()).map(ConfigGenerator::assignFromBuilder).collect(Collectors.joining("\n"))) + "\n" +//
+ "}";
+ }
+
+ private static String getAccessorCode(CNode node) {
+ if (node.isArray) {
+ return accessorsForArray(node);
+ } else if (node.isMap) {
+ return accessorsForMap(node);
+ } else {
+ return accessorForStructOrScalar(node);
+ }
+ }
+
+ private static String valueAccessor(CNode node) {
+ if (node instanceof LeafCNode) {
+ return ".value()";
+ } else {
+ return "";
+ }
+ }
+
+ private static String listAccessor(CNode node) {
+ if (node instanceof LeafCNode) {
+ return node.getName() + ".asList()";
+ } else {
+ return node.getName();
+ }
+ }
+
+ private static String mapAccessor(CNode node) {
+ if (node instanceof LeafCNode) {
+ return "LeafNodeMaps.asValueMap(" + node.getName() + ")";
+ } else {
+ return "Collections.unmodifiableMap(" + node.getName() + ")";
+ }
+ }
+
+ private static String accessorsForArray(CNode node) {
+ final String name = node.getName();
+ final String fullName = node.getFullName();
+ return "/**\n" +//
+ " * @return " + fullName + "\n" +//
+ " */\n" +//
+ "public List<" + boxedDataType(node) + "> " + name + "() {\n" +//
+ " return " + listAccessor(node) + ";\n" +//
+ "}\n" +//
+ "\n" +//
+ "/**\n" +//
+ " * @param i the index of the value to return\n" +//
+ " * @return " + fullName + "\n" +//
+ " */\n" +//
+ "public " + userDataType(node) + " " + name + "(int i) {\n" +//
+ " return " + name + ".get(i)" + valueAccessor(node) + ";\n" +//
+ "}";
+ }
+
+ private static String accessorsForMap(CNode node) {
+ final String name = node.getName();
+ final String fullName = node.getFullName();
+
+ return "/**\n" +//
+ " * @return " + fullName + "\n" +//
+ " */\n" +//
+ "public Map<String, " + boxedDataType(node) + "> " + name + "() {\n" +//
+ " return " + mapAccessor(node) + ";\n" +//
+ "}\n" +//
+ "\n" +//
+ "/**\n" +//
+ " * @param key the key of the value to return\n" +//
+ " * @return " + fullName + "\n" +//
+ " */\n" +//
+ "public " + userDataType(node) + " " + name + "(String key) {\n" +//
+ " return " + name + ".get(key)" + valueAccessor(node) + ";\n" +//
+ "}";
+ }
+
+ private static String accessorForStructOrScalar(CNode node) {
+ return "/**\n" +//
+ " * @return " + node.getFullName() + "\n" +//
+ " */\n" +//
+ "public " + userDataType(node) + " " + node.getName() + "() {\n" +//
+ " return " + node.getName() + valueAccessor(node) + ";\n" +//
+ "}";
+ }
+
+ private static String getAccessors(CNode[] children) {
+ List<String> accessors = new LinkedList<>();
+ for (CNode child : children) {
+ String accessor = getAccessorCode(child);
+ if (accessor.isEmpty() == false) {
+ accessors.add(accessor);
+ }
+ }
+ return String.join("\n\n", accessors);
+ }
+
+ private static String getStaticMethodsForInnerArray(InnerCNode inner) {
+ final String nc = nodeClass(inner);
+ return String.format("private static InnerNodeVector<%s> createVector(List<Builder> builders) {\n" +//
+ " List<%s> elems = new ArrayList<>();\n" +//
+ " for (Builder b : builders) {\n" +//
+ " elems.add(new %s(b));\n" +//
+ " }\n" +//
+ " return new InnerNodeVector<%s>(elems);\n" +//
+ "}", nc, nc, nc, nc);
+ }
+
+ private static String getStaticMethodsForInnerMap(InnerCNode inner) {
+ final String nc = nodeClass(inner);
+ return String.format(
+ "private static Map<String, %s> createMap(Map<String, Builder> builders) {\n" +//
+ " Map<String, %s> ret = new LinkedHashMap<>();\n" +//
+ " for(String key : builders.keySet()) {\n" +//
+ " ret.put(key, new %s(builders.get(key)));\n" +//
+ " }\n" +//
+ " return Collections.unmodifiableMap(ret);\n" +//
+ "}", nc, nc, nc);
+ }
+
+ private static String getEnumCode(EnumLeaf en) {
+ String enumValues = stream(en.getLegalValues()).map(e -> String.format(" public final static Enum %s = Enum.%s;", e, e)).collect(Collectors.joining("\n"));
+
+ String code = String.format("%s\n" +//
+ "public final static class %s extends EnumNode<%s> {\n" +//
+ "\n" +//
+ " public %s(){\n" +//
+ " this.value = null;\n" +//
+ " }\n" +//
+ "\n" +//
+ " public %s(Enum enumValue) {\n" +//
+ " super(enumValue != null);\n" +//
+ " this.value = enumValue;\n" +//
+ " }\n" +//
+ "\n" +//
+ " public enum Enum {%s}\n" +//
+ "%s\n" +//
+ "\n" +//
+ " @Override\n" +//
+ " protected boolean doSetValue(@NonNull String name) {\n" +//
+ " try {\n" +//
+ " value = Enum.valueOf(name);\n" +//
+ " return true;\n" +//
+ " } catch (IllegalArgumentException e) {\n" +//
+ " }\n" +//
+ " return false;\n" +//
+ " }\n" +//
+ "}", getClassDoc(en),
+ nodeClass(en),
+ nodeClass(en) + ".Enum",
+ nodeClass(en),
+ nodeClass(en),
+ String.join(", ", en.getLegalValues()),
+ enumValues);
+
+ return indentCode("", code);
+ }
+
+ private static String getClassDoc(CNode node) {
+ String header = "/**\n" + " * This class represents " + node.getFullName();
+ String nodeComment = node.getCommentBlock(" *");
+ if (nodeComment.isEmpty()) {
+ return header + "\n */";
+ } else {
+ if (nodeComment.endsWith("\n")) {
+ nodeComment = nodeComment.substring(0, nodeComment.length() - 1);
+ }
+ return header + "\n * \n" + nodeComment + "\n */";
+ }
+ }
+
+ static String indentCode(String indent, String code) {
+ List<String> indented = new LinkedList<>();
+ for (String line : code.split("\n", -1)) {
+ indented.add(line.length() > 0 ? indent + line : line);
+ }
+ return String.join("\n", indented);
+ }
+
+ /**
+ * @return the name of the class that is generated by this node.
+ */
+ static String nodeClass(CNode node) {
+ if (node.getName().length() == 0) {
+ throw new CodegenRuntimeException("Node with empty name, under parent " + node.getParent().getName());
+ } else if (node instanceof InnerCNode && node.getParent() == null) {
+ return ConfiggenUtil.createClassName(node.getName());
+ } else if (node instanceof BooleanLeaf) {
+ return "BooleanNode";
+ } else if (node instanceof DoubleLeaf) {
+ return "DoubleNode";
+ } else if (node instanceof FileLeaf) {
+ return "FileNode";
+ } else if (node instanceof PathLeaf) {
+ return "PathNode";
+ } else if (node instanceof IntegerLeaf) {
+ return "IntegerNode";
+ } else if (node instanceof LongLeaf) {
+ return "LongNode";
+ } else if (node instanceof ReferenceLeaf) {
+ return "ReferenceNode";
+ } else if (node instanceof StringLeaf) {
+ return "StringNode";
+ } else {
+ return ConfiggenUtil.capitalize(node.getName());
+ }
+ }
+
+ static String userDataType(CNode node) {
+ if (node instanceof InnerCNode) {
+ return nodeClass(node);
+ } else if (node instanceof EnumLeaf) {
+ return nodeClass(node) + ".Enum";
+ } else if (node instanceof BooleanLeaf) {
+ return "boolean";
+ } else if (node instanceof DoubleLeaf) {
+ return "double";
+ } else if (node instanceof FileLeaf) {
+ return "FileReference";
+ } else if (node instanceof PathLeaf) {
+ return "Path";
+ } else if (node instanceof IntegerLeaf) {
+ return "int";
+ } else if (node instanceof LongLeaf) {
+ return "long";
+ } else if (node instanceof StringLeaf) {
+ return "String";
+ } else {
+ throw new IllegalStateException("Cannot determine user data type for node"); // should not occur
+ }
+ }
+
+ /**
+ * @return the boxed java data type, e.g. Integer for int
+ */
+ static String boxedDataType(CNode node) {
+ String rawType = userDataType(node);
+
+ if ("int".equals(rawType)) {
+ return "Integer";
+ } else if (rawType.toLowerCase().equals(rawType)) {
+ return ConfiggenUtil.capitalize(rawType);
+ } else {
+ return rawType;
+ }
+ }
+}
diff --git a/configgen/src/main/java/com/yahoo/config/codegen/ConfiggenUtil.java b/configgen/src/main/java/com/yahoo/config/codegen/ConfiggenUtil.java
index 299a5540098..995ef419f30 100644
--- a/configgen/src/main/java/com/yahoo/config/codegen/ConfiggenUtil.java
+++ b/configgen/src/main/java/com/yahoo/config/codegen/ConfiggenUtil.java
@@ -26,7 +26,7 @@ public class ConfiggenUtil {
return className;
}
- private static String capitalize(String in) {
+ static String capitalize(String in) {
StringBuilder sb = new StringBuilder(in);
sb.setCharAt(0, Character.toTitleCase(in.charAt(0)));
return sb.toString();
diff --git a/configgen/src/main/java/com/yahoo/config/codegen/JavaClassBuilder.java b/configgen/src/main/java/com/yahoo/config/codegen/JavaClassBuilder.java
new file mode 100644
index 00000000000..00498094db5
--- /dev/null
+++ b/configgen/src/main/java/com/yahoo/config/codegen/JavaClassBuilder.java
@@ -0,0 +1,170 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.codegen;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.yahoo.config.codegen.ConfigGenerator.indentCode;
+import static com.yahoo.config.codegen.ConfiggenUtil.createClassName;
+import static com.yahoo.config.codegen.DefParser.DEFAULT_PACKAGE_PREFIX;
+
+/**
+ * Builds one Java class based on the given CNode tree.
+ *
+ * @author gjoranv
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class JavaClassBuilder implements ClassBuilder {
+ public static final String INDENTATION = " ";
+
+ private final InnerCNode root;
+ private final NormalizedDefinition nd;
+ private final String packagePrefix;
+ private final String javaPackage;
+ private final String className;
+ private final File destDir;
+
+ public JavaClassBuilder(InnerCNode root, NormalizedDefinition nd, File destDir, String rawPackagePrefix) {
+ this.root = root;
+ this.nd = nd;
+ this.packagePrefix = (rawPackagePrefix != null) ? rawPackagePrefix : DEFAULT_PACKAGE_PREFIX;
+ this.javaPackage = (root.getPackage() != null) ? root.getPackage() : packagePrefix + root.getNamespace();
+ this.className = createClassName(root.getName());
+ this.destDir = destDir;
+ }
+
+ @Override
+ public void createConfigClasses() {
+ try {
+ File outFile = new File(getDestPath(destDir, javaPackage), className + ".java");
+ try (PrintStream out = new PrintStream(new FileOutputStream(outFile))) {
+ out.print(getConfigClass(className));
+ }
+ System.err.println(outFile.getPath() + " successfully written.");
+ } catch (FileNotFoundException e) {
+ throw new CodegenRuntimeException(e);
+ }
+ }
+
+ public String getConfigClass(String className) {
+ return getHeader() + "\n\n" + //
+ getRootClassDeclaration(root, className) + "\n\n" + //
+ indentCode(INDENTATION, getFrameworkCode()) + "\n\n" + //
+ ConfigGenerator.generateContent(INDENTATION, root, true) + "\n" + //
+ "}\n";
+ }
+
+ private String getHeader() {
+ return "/**\n" + //
+ " * This file is generated from a config definition file.\n" + //
+ " * ------------ D O N O T E D I T ! ------------\n" + //
+ " */\n" + //
+ "\n" + //
+ "package " + javaPackage + ";\n" + //
+ "\n" + //
+ "import java.util.*;\n" + //
+ "import java.nio.file.Path;\n" + //
+ "import edu.umd.cs.findbugs.annotations.NonNull;\n" + //
+ getImportFrameworkClasses(root.getNamespace());
+ }
+
+ private String getImportFrameworkClasses(String namespace) {
+ if (CNode.DEFAULT_NAMESPACE.equals(namespace) == false) {
+ return "import " + packagePrefix + CNode.DEFAULT_NAMESPACE + ".*;";
+ } else {
+ return "";
+ }
+ }
+
+ // TODO: remove the extra comment line " *" if root.getCommentBlock is empty
+ private String getRootClassDeclaration(InnerCNode root, String className) {
+ return "/**\n" + //
+ " * This class represents the root node of " + root.getFullName() + "\n" + //
+ " *\n" + //
+ "" + root.getCommentBlock(" *") + " */\n" + //
+ "public final class " + className + " extends ConfigInstance {\n" + //
+ "\n" + //
+ " public final static String CONFIG_DEF_MD5 = \"" + root.getMd5() + "\";\n" + //
+ " public final static String CONFIG_DEF_NAME = \"" + root.getName() + "\";\n" + //
+ " public final static String CONFIG_DEF_NAMESPACE = \"" + root.getNamespace() + "\";\n" + //
+ " public final static String CONFIG_DEF_VERSION = \"" + root.getVersion() + "\";\n" + //
+ " public final static String[] CONFIG_DEF_SCHEMA = {\n" + //
+ "" + indentCode(INDENTATION + INDENTATION, getDefSchema()) + "\n" + //
+ " };\n" + //
+ "\n" + //
+ " public static String getDefMd5() { return CONFIG_DEF_MD5; }\n" + //
+ " public static String getDefName() { return CONFIG_DEF_NAME; }\n" + //
+ " public static String getDefNamespace() { return CONFIG_DEF_NAMESPACE; }\n" + //
+ " public static String getDefVersion() { return CONFIG_DEF_VERSION; }";
+ }
+
+ private String getDefSchema() {
+ return nd.getNormalizedContent().stream().map(l -> "\"" + l.replace("\"", "\\\"") + "\"").collect(Collectors.joining(",\n"));
+ }
+
+ private String getFrameworkCode() {
+ return "public interface Producer extends ConfigInstance.Producer {\n" + //
+ " void getConfig(Builder builder);\n" + //
+ "}";
+ }
+
+ /**
+ * @param rootDir
+ * The root directory for the destination path.
+ * @param javaPackage
+ * The java package
+ * @return the destination path for the generated config file, including the
+ * given rootDir.
+ */
+ private File getDestPath(File rootDir, String javaPackage) {
+ File dir = rootDir;
+ for (String subDir : javaPackage.split("\\.")) {
+ dir = new File(dir, subDir);
+ synchronized (this) {
+ if (!dir.isDirectory() && !dir.mkdir()) {
+ throw new CodegenRuntimeException("Could not create " + dir.getPath());
+ }
+ }
+ }
+ return dir;
+ }
+
+ /**
+ * Returns a name that can be safely used as a local variable in the generated
+ * config class for the given node. The name will be based on the given basis
+ * string, but the basis itself is not a possible return value.
+ *
+ * @param node
+ * The node to find a unused symbol name for.
+ * @param basis
+ * The basis for the generated symbol name.
+ * @return A name that is not used in the given config node.
+ */
+ static String createUniqueSymbol(CNode node, String basis) {
+ Set<String> usedSymbols = Arrays.stream(node.getChildren()).map(CNode::getName).collect(Collectors.toSet());
+ Random rng = new Random();
+
+ for (int i = 1;; i++) {
+ String candidate = (i < basis.length()) ? basis.substring(0, i)
+ : ReservedWords.INTERNAL_PREFIX + basis + rng.nextInt(Integer.MAX_VALUE);
+ if (usedSymbols.contains(candidate) == false) {
+ return candidate;
+ }
+ }
+ }
+
+ public String className() {
+ return className;
+ }
+
+ public String javaPackage() {
+ return javaPackage;
+ }
+}