diff options
author | Harald Musum <musum@yahooinc.com> | 2023-09-07 12:25:37 +0200 |
---|---|---|
committer | Harald Musum <musum@yahooinc.com> | 2023-09-07 12:25:37 +0200 |
commit | cac205f35b56d0bd584013b79b88a6635dee5ab4 (patch) | |
tree | bcf8461b3091c44ae78e5d9355a2109ba7d6a77a | |
parent | daba552c567f1fcb9e300ae65825c1d97cedbb5e (diff) |
Support optional 'path' config type
Initial work needed for this to work (config definition and config library)
21 files changed, 285 insertions, 17 deletions
diff --git a/config-lib/abi-spec.json b/config-lib/abi-spec.json index caae2ebf1cf..07e61c63237 100644 --- a/config-lib/abi-spec.json +++ b/config-lib/abi-spec.json @@ -273,6 +273,7 @@ "public static java.util.Map asNodeMap(java.util.Map, com.yahoo.config.LeafNode)", "public static java.util.Map asFileNodeMap(java.util.Map)", "public static java.util.Map asPathNodeMap(java.util.Map)", + "public static java.util.Map asOptionalPathNodeMap(java.util.Map)", "public static java.util.Map asUrlNodeMap(java.util.Map)", "public static java.util.Map asModelNodeMap(java.util.Map)" ], @@ -289,6 +290,7 @@ "public java.util.List asList()", "public static com.yahoo.config.LeafNodeVector createFileNodeVector(java.util.Collection)", "public static com.yahoo.config.LeafNodeVector createPathNodeVector(java.util.Collection)", + "public static com.yahoo.config.LeafNodeVector createOptionalPathNodeVector(java.util.Collection)", "public static com.yahoo.config.LeafNodeVector createUrlNodeVector(java.util.Collection)", "public static com.yahoo.config.LeafNodeVector createModelNodeVector(java.util.Collection)" ], @@ -420,6 +422,27 @@ "protected final java.util.ArrayList vector" ] }, + "com.yahoo.config.OptionalPathNode" : { + "superClass" : "com.yahoo.config.LeafNode", + "interfaces" : [ ], + "attributes" : [ + "public" + ], + "methods" : [ + "public void <init>()", + "public void <init>(com.yahoo.config.FileReference)", + "public void <init>(java.util.Optional)", + "public java.util.Optional value()", + "public java.lang.String getValue()", + "public java.lang.String toString()", + "protected boolean doSetValue(java.lang.String)", + "public java.util.Optional getFileReference()", + "public static java.util.List toFileReferences(java.util.List)", + "public static java.util.Map toFileReferenceMap(java.util.Map)", + "public bridge synthetic java.lang.Object value()" + ], + "fields" : [ ] + }, "com.yahoo.config.PathNode" : { "superClass" : "com.yahoo.config.LeafNode", "interfaces" : [ ], diff --git a/config-lib/src/main/java/com/yahoo/config/LeafNodeMaps.java b/config-lib/src/main/java/com/yahoo/config/LeafNodeMaps.java index 82663fa8bfd..214d8c52caa 100644 --- a/config-lib/src/main/java/com/yahoo/config/LeafNodeMaps.java +++ b/config-lib/src/main/java/com/yahoo/config/LeafNodeMaps.java @@ -4,6 +4,7 @@ package com.yahoo.config; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; /** @@ -60,6 +61,14 @@ public class LeafNodeMaps { return Collections.unmodifiableMap(pathNodeMap); } + public static Map<String, OptionalPathNode> asOptionalPathNodeMap(Map<String, Optional<FileReference>> fileReferenceMap) { + Map<String, OptionalPathNode> pathNodeMap = new LinkedHashMap<>(); + for (Map.Entry<String, Optional<FileReference>> e : fileReferenceMap.entrySet()) { + pathNodeMap.put(e.getKey(), new OptionalPathNode(e.getValue())); + } + return Collections.unmodifiableMap(pathNodeMap); + } + public static Map<String, UrlNode> asUrlNodeMap(Map<String, UrlReference> urlReferenceMap) { return Collections.unmodifiableMap( urlReferenceMap.entrySet().stream().collect( diff --git a/config-lib/src/main/java/com/yahoo/config/LeafNodeVector.java b/config-lib/src/main/java/com/yahoo/config/LeafNodeVector.java index a4fea95088d..cfb8cd4eebd 100644 --- a/config-lib/src/main/java/com/yahoo/config/LeafNodeVector.java +++ b/config-lib/src/main/java/com/yahoo/config/LeafNodeVector.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; /** * A vector of leaf nodes. @@ -71,6 +72,13 @@ public class LeafNodeVector<REAL, NODE extends LeafNode<REAL>> extends NodeVecto return new LeafNodeVector<>(paths, new PathNode()); } + public static LeafNodeVector<Optional<Path>, OptionalPathNode> createOptionalPathNodeVector(Collection<Optional<FileReference>> values) { + List<Optional<Path>> paths = new ArrayList<>(); + for (Optional<FileReference> fileReference : values) + paths.add(fileReference.map(reference -> Paths.get(reference.value()))); + return new LeafNodeVector<>(paths, new OptionalPathNode()); + } + public static LeafNodeVector<File, UrlNode> createUrlNodeVector(Collection<UrlReference> values) { List<File> files = new ArrayList<>(); for (UrlReference urlReference : values) diff --git a/config-lib/src/main/java/com/yahoo/config/OptionalPathNode.java b/config-lib/src/main/java/com/yahoo/config/OptionalPathNode.java new file mode 100644 index 00000000000..8a6414d798f --- /dev/null +++ b/config-lib/src/main/java/com/yahoo/config/OptionalPathNode.java @@ -0,0 +1,84 @@ +// Copyright Yahoo. 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.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Represents a 'path' in a {@link ConfigInstance}, usually a filename, can be optional + * + * @author hmusum + */ +public class OptionalPathNode extends LeafNode<Optional<Path>> { + + private final Optional<FileReference> fileReference; + + public OptionalPathNode() { + fileReference = Optional.empty(); + } + + public OptionalPathNode(FileReference fileReference) { + super(true); + this.value = Optional.of(Path.of(fileReference.value())); + this.fileReference = Optional.of(fileReference); + } + + public OptionalPathNode(Optional<FileReference> fileReference) { + super(true); + this.value = fileReference.map(reference -> Path.of(reference.value())); + this.fileReference = fileReference; + } + + public Optional<Path> value() { + return value; + } + + @Override + public String getValue() { + return value.toString(); + } + + @Override + public String toString() { + return (value.isEmpty()) ? "(empty)" : '"' + value.get().toString() + '"'; + } + + @Override + protected boolean doSetValue(String stringVal) { + throw new UnsupportedOperationException("doSetValue should not be necessary anymore!"); + } + + @Override + void serialize(String name, Serializer serializer) { + value.ifPresent(path -> serializer.serialize(name, path.toString())); + } + + @Override + void serialize(Serializer serializer) { + value.ifPresent(path -> serializer.serialize(path.toString())); + } + + public Optional<FileReference> getFileReference() { + return fileReference; + } + + public static List<Optional<FileReference>> toFileReferences(List<OptionalPathNode> pathNodes) { + List<Optional<FileReference>> fileReferences = new ArrayList<>(); + for (OptionalPathNode pathNode : pathNodes) + fileReferences.add(pathNode.getFileReference()); + return fileReferences; + } + + public static Map<String, Optional<FileReference>> toFileReferenceMap(Map<String, OptionalPathNode> map) { + Map<String, Optional<FileReference>> ret = new LinkedHashMap<>(); + for (Map.Entry<String, OptionalPathNode> e : map.entrySet()) { + ret.put(e.getKey(), e.getValue().getFileReference()); + } + return ret; + } + +} diff --git a/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java b/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java index e86b13b2c98..0dbc40a246c 100644 --- a/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java +++ b/config/src/main/java/com/yahoo/vespa/config/ConfigPayloadApplier.java @@ -21,6 +21,7 @@ import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.logging.Logger; @@ -213,7 +214,10 @@ public class ConfigPayloadApplier<T extends ConfigInstance.Builder> { Inspector value = (Inspector)rawValue; if (isPathField(builder, methodName)) return resolvePath(value.asString()); - else if (isUrlField(builder, methodName)) + if (isOptionalPathField(builder, methodName)) { + String v = value.asString(); + return resolvePath(v.isEmpty() ? Optional.empty() : Optional.of(v)); + } else if (isUrlField(builder, methodName)) return value.asString().isEmpty() ? "" : resolveUrl(value.asString()); else if (isModelField(builder, methodName)) return value.asString().isEmpty() ? "" : resolveModel(value.asString()); @@ -234,6 +238,10 @@ public class ConfigPayloadApplier<T extends ConfigInstance.Builder> { return new FileReference(path.toString()); } + private Optional<FileReference> resolvePath(Optional<String> value) { + return value.isEmpty() ? Optional.empty() : Optional.of(resolvePath(value.get())); + } + private UrlReference resolveUrl(String url) { if ( ! isClientside()) return new UrlReference(url); File file = urlDownloader.waitFor(new UrlReference(url), 60 * 60); @@ -319,6 +327,16 @@ public class ConfigPayloadApplier<T extends ConfigInstance.Builder> { return isFieldType(pathFieldSet, builder, methodName, FileReference.class); } + /** + * Checks if this field is of type 'path', in which + * case some special handling might be needed. Caches the result. + */ + private final Set<String> optionalPathFieldSet = new HashSet<>(); + private boolean isOptionalPathField(Object builder, String methodName) { + // Paths are stored as Optional<FileReference> in Builder. + return isFieldType(optionalPathFieldSet, builder, methodName, Optional.class); + } + private final Set<String> urlFieldSet = new HashSet<>(); private boolean isUrlField(Object builder, String methodName) { // Urls are stored as UrlReference in Builder. diff --git a/config/src/test/java/com/yahoo/config/subscription/CfgConfigPayloadBuilderTest.java b/config/src/test/java/com/yahoo/config/subscription/CfgConfigPayloadBuilderTest.java index a6273ad5ccb..0e50be83e7a 100644 --- a/config/src/test/java/com/yahoo/config/subscription/CfgConfigPayloadBuilderTest.java +++ b/config/src/test/java/com/yahoo/config/subscription/CfgConfigPayloadBuilderTest.java @@ -34,6 +34,7 @@ public class CfgConfigPayloadBuilderTest { " 'parent:'", " ],", " 'pathVal': 'src/test/resources/configs/def-files/function-test.def',", + " 'optionalPathVal': 'src/test/resources/configs/def-files/function-test.def',", " 'string_val': 'foo',", " 'myStructMap': {", " 'one': {", diff --git a/config/src/test/java/com/yahoo/config/subscription/ConfigInstancePayloadTest.java b/config/src/test/java/com/yahoo/config/subscription/ConfigInstancePayloadTest.java index c656bfe1a60..f09462eb634 100644 --- a/config/src/test/java/com/yahoo/config/subscription/ConfigInstancePayloadTest.java +++ b/config/src/test/java/com/yahoo/config/subscription/ConfigInstancePayloadTest.java @@ -13,6 +13,7 @@ import org.junit.Test; import java.io.File; import java.util.Arrays; import java.util.List; +import java.util.Optional; import static com.yahoo.foo.FunctionTestConfig.*; import static org.junit.Assert.assertNotNull; @@ -46,6 +47,7 @@ public class ConfigInstancePayloadTest { refwithdef(":parent:"). fileVal("etc"). pathVal(FileReference.mockFileReferenceForUnitTesting(new File("src/test/resources/configs/def-files/function-test.def"))). + optionalPathVal(Optional.of(FileReference.mockFileReferenceForUnitTesting(new File("src/test/resources/configs/def-files/function-test.def")))). boolarr(false). longarr(9223372036854775807L). longarr(-9223372036854775808L). diff --git a/config/src/test/java/com/yahoo/config/subscription/FunctionTest.java b/config/src/test/java/com/yahoo/config/subscription/FunctionTest.java index 8656c0e945f..7a3b0e437f2 100644 --- a/config/src/test/java/com/yahoo/config/subscription/FunctionTest.java +++ b/config/src/test/java/com/yahoo/config/subscription/FunctionTest.java @@ -30,7 +30,7 @@ public class FunctionTest { public static final String PATH = "src/test/resources/configs/function-test/"; private FunctionTestConfig config; - private ConfigSourceSet sourceSet = new ConfigSourceSet("function-test"); + private final ConfigSourceSet sourceSet = new ConfigSourceSet("function-test"); public void configure(FunctionTestConfig config, ConfigSourceSet sourceSet) { this.config = config; @@ -222,6 +222,8 @@ public class FunctionTest { assertEquals(":parent", config.refarr(1)); assertEquals("parent:", config.refarr(2)); assertEquals("bin", config.fileArr(0).value()); + assertEquals("function-test.def", config.pathVal().toFile().getName()); + assertEquals("function-test.def", config.optionalPathVal().get().toFile().getName()); // TODO assertEquals("pom.xml", config.pathArr(0).toString()); assertEquals("pom.xml", config.pathMap("one").toString()); diff --git a/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionBuilderTest.java b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionBuilderTest.java index a9f09951d7e..fa1ec815047 100644 --- a/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionBuilderTest.java +++ b/config/src/test/java/com/yahoo/vespa/config/ConfigDefinitionBuilderTest.java @@ -77,10 +77,14 @@ public class ConfigDefinitionBuilderTest { assertThat(def.getFileDefs().size(), is(1)); assertNotNull(def.getFileDefs().get("fileVal")); - assertThat(def.getArrayDefs().size(), is(9)); + // An array does not have to have any elements set + assertThat(def.getArrayDefs().size(), is(10)); assertNotNull(def.getArrayDefs().get("boolarr")); assertThat(def.getArrayDefs().get("boolarr").getTypeSpec().getType(), is("bool")); + assertNotNull(def.getArrayDefs().get("boolarrEmpty")); + assertThat(def.getArrayDefs().get("boolarrEmpty").getTypeSpec().getType(), is("bool")); + assertNotNull(def.getArrayDefs().get("enumarr")); assertThat(def.getArrayDefs().get("enumarr").getTypeSpec().getType(), is("enum")); assertThat(def.getArrayDefs().get("enumarr").getTypeSpec().getEnumVals().toString(), is("[ARRAY, VALUES]")); diff --git a/config/src/test/resources/configs/def-files/function-test.def b/config/src/test/resources/configs/def-files/function-test.def index 4c4cb6bf08b..b97713b18f3 100644 --- a/config/src/test/resources/configs/def-files/function-test.def +++ b/config/src/test/resources/configs/def-files/function-test.def @@ -42,8 +42,10 @@ refval reference refwithdef reference default=":parent:" fileVal file pathVal path +optionalPathVal path optional boolarr[] bool +boolarrEmpty[] bool intarr[] int longarr[] long doublearr[] double diff --git a/config/src/test/resources/configs/function-test/variableaccess.txt b/config/src/test/resources/configs/function-test/variableaccess.txt index 997de21750d..8c2cadcdbbc 100644 --- a/config/src/test/resources/configs/function-test/variableaccess.txt +++ b/config/src/test/resources/configs/function-test/variableaccess.txt @@ -14,7 +14,8 @@ enumwithdef BAR2 refval :parent: refwithdef ":parent:" fileVal "etc" -pathVal "pom.xml" +pathVal "function-test.def" +optionalPathVal "function-test.def" boolarr[1] boolarr[0] false intarr[0] diff --git a/configgen/src/main/java/com/yahoo/config/codegen/BuilderGenerator.java b/configgen/src/main/java/com/yahoo/config/codegen/BuilderGenerator.java index 6cd344466e4..12469d7a3ef 100644 --- a/configgen/src/main/java/com/yahoo/config/codegen/BuilderGenerator.java +++ b/configgen/src/main/java/com/yahoo/config/codegen/BuilderGenerator.java @@ -4,10 +4,12 @@ package com.yahoo.config.codegen; import com.yahoo.config.codegen.LeafCNode.FileLeaf; import com.yahoo.config.codegen.LeafCNode.ModelLeaf; import com.yahoo.config.codegen.LeafCNode.PathLeaf; +import com.yahoo.config.codegen.LeafCNode.OptionalPathLeaf; import com.yahoo.config.codegen.LeafCNode.UrlLeaf; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import static com.yahoo.config.codegen.ConfigGenerator.boxedDataType; @@ -89,7 +91,9 @@ public class BuilderGenerator { 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)) { + if (child instanceof LeafCNode + && (!child.isArray && !child.isMap && ((LeafCNode) child).getDefaultValue() == null) + && (! (child instanceof OptionalPathLeaf))) { scalarsWithoutDefault.add("\"" + child.getName() + "\""); } } @@ -109,7 +113,11 @@ public class BuilderGenerator { } 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()); + String boxedBuilderType = boxedBuilderType((LeafCNode) node); + if (boxedBuilderType.startsWith("Optional<")) + return String.format("private %s %s = Optional.empty();", boxedBuilderType, node.getName()); + else + return String.format("private %s %s = null;", boxedBuilderType, node.getName()); } else { throw new IllegalStateException("Cannot produce builder field definition for node"); // Should not happen } @@ -207,6 +215,11 @@ public class BuilderGenerator { private static String privateLeafNodeSetter(LeafCNode n) { if ("String".equals(builderType(n)) || "FileReference".equals(builderType(n))) { return ""; + } else if ("Optional<FileReference>".equals(builderType(n))) { + return "\n\n" + // + "private Builder " + n.getName() + "(String " + INTERNAL_PREFIX + "value) {\n" + // + " return " + n.getName() + "(" + builderType(n) + ".of(" + INTERNAL_PREFIX + "value));\n" + // + "}"; } else { return "\n\n" + // "private Builder " + n.getName() + "(String " + INTERNAL_PREFIX + "value) {\n" + // @@ -270,14 +283,24 @@ public class BuilderGenerator { : ""; String bType = builderType(n); - String stringSetter = ""; - if ( ! "String".equals(bType) && ! "FileReference".equals(bType) && ! "ModelReference".equals(bType)) { + String privateSetter = ""; + if ( ! Set.of("String", "FileReference", "ModelReference", "Optional<FileReference>").contains(bType)) { String type = boxedDataType(n); if ("UrlReference".equals(bType)) type = bType; - stringSetter = String.format("\nprivate Builder %s(String %svalue) {\n" + - " return %s(%s.valueOf(%svalue));\n" + // - "}", name, INTERNAL_PREFIX, name, type, INTERNAL_PREFIX); + // + privateSetter = String.format(""" + + private Builder %s(String %svalue) { + return %s(%s.valueOf(%svalue)); + }""", name, INTERNAL_PREFIX, name, type, INTERNAL_PREFIX); + } else if ("Optional<FileReference>".equals(bType)) { + // + privateSetter = String.format(""" + + private Builder %s(FileReference %svalue) { + return %s(Optional.of(%svalue)); + }""", name, INTERNAL_PREFIX, name, INTERNAL_PREFIX); } String getNullGuard = bType.equals(boxedBuilderType(n)) ? String.format( @@ -286,7 +309,7 @@ public class BuilderGenerator { 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; + " return this;" + "\n}\n" + privateSetter; } } @@ -307,6 +330,8 @@ public class BuilderGenerator { return name + "(" + nodeClass(child) + ".toFileReferenceMap(config." + name + "));"; } else if (child instanceof PathLeaf) { return name + "(config." + name + ".getFileReference());"; + } else if (child instanceof OptionalPathLeaf) { + return name + "(config." + name + ".getFileReference());"; } else if (child instanceof UrlLeaf && isArray) { return name + "(" + nodeClass(child) + ".toUrlReferences(config." + name + "));"; } else if (child instanceof UrlLeaf && isMap) { @@ -408,6 +433,8 @@ public class BuilderGenerator { return "String"; } else if (node instanceof PathLeaf) { return "FileReference"; + } else if (node instanceof OptionalPathLeaf) { + return "Optional<FileReference>"; } else if (node instanceof UrlLeaf) { return "UrlReference"; } else if (node instanceof ModelLeaf) { @@ -424,6 +451,8 @@ public class BuilderGenerator { return "String"; } else if (node instanceof PathLeaf) { return "FileReference"; + } else if (node instanceof OptionalPathLeaf) { + return "Optional<FileReference>"; } else if (node instanceof UrlLeaf) { return "UrlReference"; } else if (node instanceof ModelLeaf) { diff --git a/configgen/src/main/java/com/yahoo/config/codegen/ConfigGenerator.java b/configgen/src/main/java/com/yahoo/config/codegen/ConfigGenerator.java index cb10ffdc2be..903d8dc0865 100644 --- a/configgen/src/main/java/com/yahoo/config/codegen/ConfigGenerator.java +++ b/configgen/src/main/java/com/yahoo/config/codegen/ConfigGenerator.java @@ -7,6 +7,7 @@ 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.OptionalPathLeaf; import com.yahoo.config.codegen.LeafCNode.PathLeaf; import com.yahoo.config.codegen.LeafCNode.ReferenceLeaf; import com.yahoo.config.codegen.LeafCNode.StringLeaf; @@ -165,6 +166,8 @@ public class ConfigGenerator { return name + " = LeafNodeVector.createFileNodeVector(builder." + name + ");"; } else if (child instanceof PathLeaf && isArray) { return name + " = LeafNodeVector.createPathNodeVector(builder." + name + ");"; + } else if (child instanceof OptionalPathLeaf && isArray) { + return name + " = LeafNodeVector.createOptionalPathNodeVector(builder." + name + ");"; } else if (child instanceof UrlLeaf && isArray) { return name + " = LeafNodeVector.createUrlNodeVector(builder." + name + ");"; } else if (child instanceof ModelLeaf && isArray) { @@ -175,6 +178,8 @@ public class ConfigGenerator { return name + " = LeafNodeMaps.asFileNodeMap(builder." + name + ");"; } else if (child instanceof PathLeaf && isMap) { return name + " = LeafNodeMaps.asPathNodeMap(builder." + name + ");"; + } else if (child instanceof OptionalPathLeaf && isMap) { + return name + " = LeafNodeMaps.asOptionalPathNodeMap(builder." + name + ");"; } else if (child instanceof UrlLeaf && isMap) { return name + " = LeafNodeMaps.asUrlNodeMap(builder." + name + ");"; } else if (child instanceof ModelLeaf && isMap) { @@ -401,6 +406,8 @@ public class ConfigGenerator { return "FileNode"; } else if (node instanceof PathLeaf) { return "PathNode"; + } else if (node instanceof OptionalPathLeaf) { + return "OptionalPathNode"; } else if (node instanceof UrlLeaf) { return "UrlNode"; } else if (node instanceof ModelLeaf) { @@ -431,6 +438,8 @@ public class ConfigGenerator { return "FileReference"; } else if (node instanceof PathLeaf) { return "Path"; + } else if (node instanceof OptionalPathLeaf) { + return "Optional<Path>"; } else if (node instanceof UrlLeaf) { return "File"; } else if (node instanceof ModelLeaf) { @@ -456,6 +465,8 @@ public class ConfigGenerator { return "Integer"; } else if (rawType.toLowerCase().equals(rawType)) { return ConfiggenUtil.capitalize(rawType); + } else if (rawType.startsWith("Optional<")) { + return "Optional"; } else { return rawType; } diff --git a/configgen/src/main/java/com/yahoo/config/codegen/DefLine.java b/configgen/src/main/java/com/yahoo/config/codegen/DefLine.java index 385c7f1979e..d6bffe349a8 100644 --- a/configgen/src/main/java/com/yahoo/config/codegen/DefLine.java +++ b/configgen/src/main/java/com/yahoo/config/codegen/DefLine.java @@ -7,6 +7,7 @@ import java.util.regex.Pattern; public class DefLine { private final static Pattern defaultPattern = Pattern.compile("^\\s*default\\s*=\\s*(\\S+)"); + private final static Pattern optionalPattern = Pattern.compile("^\\s*optional\\s*"); private final static Pattern rangePattern = Pattern.compile("^\\s*range\\s*=\\s*([\\(\\[].*?[\\)\\]])"); private final static Pattern restartPattern = Pattern.compile("^\\s*restart\\s*"); private final static Pattern wordPattern = Pattern.compile("\\S+"); @@ -21,6 +22,7 @@ public class DefLine { private final Type type = new Type(); private DefaultValue defaultValue = null; + private boolean optional = false; private String range = null; private boolean restart = false; @@ -74,6 +76,9 @@ public class DefLine { } public Type getType() { + if (optional && type.name.equals("path")) + type.name = "optionalPath"; + return type; } @@ -89,6 +94,8 @@ public class DefLine { return enumArray; } + public boolean isOptional() { return optional; } + /** * Special function that searches through s and returns the index * of the first occurrence of " that is not escaped. @@ -114,6 +121,7 @@ public class DefLine { private int parseOptions(CharSequence string) { Matcher defaultNullMatcher = defaultNullPattern.matcher(string); Matcher defaultMatcher = defaultPattern.matcher(string); + Matcher optionalMatcher = optionalPattern.matcher(string); Matcher rangeMatcher = rangePattern.matcher(string); Matcher restartMatcher = restartPattern.matcher(string); @@ -133,6 +141,12 @@ public class DefLine { defaultValue = new DefaultValue(deflt, type); } return defaultMatcher.end(); + } else if (optionalMatcher.find()) { + if ( ! type.name.equals("path")) + throw new IllegalArgumentException("optional can only be used for 'path'"); + optional = true; + type.name = "optionalPath"; + return optionalMatcher.end(); } else if (rangeMatcher.find()) { range = rangeMatcher.group(1); return rangeMatcher.end(); diff --git a/configgen/src/main/java/com/yahoo/config/codegen/LeafCNode.java b/configgen/src/main/java/com/yahoo/config/codegen/LeafCNode.java index afd6acfbabf..c2470b0c703 100644 --- a/configgen/src/main/java/com/yahoo/config/codegen/LeafCNode.java +++ b/configgen/src/main/java/com/yahoo/config/codegen/LeafCNode.java @@ -26,6 +26,7 @@ public abstract class LeafCNode extends CNode { case "reference" -> new ReferenceLeaf(parent, name); case "file" -> new FileLeaf(parent, name); case "path" -> new PathLeaf(parent, name); + case "optionalPath" -> new OptionalPathLeaf(parent, name); case "enum" -> new EnumLeaf(parent, name, type.enumArray); case "url" -> new UrlLeaf(parent, name); case "model" -> new ModelLeaf(parent, name); @@ -217,6 +218,17 @@ public abstract class LeafCNode extends CNode { } } + public static class OptionalPathLeaf extends NoClassLeafCNode { + OptionalPathLeaf(InnerCNode parent, String name) { + super(parent, name); + } + + @Override + public String getType() { + return "optionalPath"; + } + } + public static class UrlLeaf extends NoClassLeafCNode { UrlLeaf(InnerCNode parent, String name) { super(parent, name); diff --git a/configgen/src/test/java/com/yahoo/config/codegen/DefLineParsingTest.java b/configgen/src/test/java/com/yahoo/config/codegen/DefLineParsingTest.java index 0e2f6cc4d05..bb6b8eb64b4 100644 --- a/configgen/src/test/java/com/yahoo/config/codegen/DefLineParsingTest.java +++ b/configgen/src/test/java/com/yahoo/config/codegen/DefLineParsingTest.java @@ -1,6 +1,7 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.config.codegen; +import java.util.Optional; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -246,4 +247,24 @@ public class DefLineParsingTest { assertTrue(r1.getRestart()); } + @Test + void testParseOptionalPathWithDefault() { + DefLine l = new DefLine("pathWithDef path optional"); + + assertEquals("pathWithDef", l.getName()); + assertNull(l.getDefault()); + assertTrue(l.isOptional()); + assertEquals("optionalPath", l.getType().getName()); + } + + @Test + void testParsPathWithDefault() { + DefLine l = new DefLine("pathWithDef path"); + + assertEquals("pathWithDef", l.getName()); + assertNull(l.getDefault()); + assertFalse(l.isOptional()); + assertEquals("path", l.getType().getName()); + } + } diff --git a/configgen/src/test/java/com/yahoo/config/codegen/DefParserTest.java b/configgen/src/test/java/com/yahoo/config/codegen/DefParserTest.java index 45d1f21763c..e5227282c05 100644 --- a/configgen/src/test/java/com/yahoo/config/codegen/DefParserTest.java +++ b/configgen/src/test/java/com/yahoo/config/codegen/DefParserTest.java @@ -28,7 +28,7 @@ public class DefParserTest { CNode root = new DefParser("test", new FileReader(defFile)).getTree(); assertNotNull(root); CNode[] children = root.getChildren(); - assertEquals(37, children.length); + assertEquals(38, children.length); int numGrandChildren = 0; int numGreatGrandChildren = 0; @@ -70,7 +70,7 @@ public class DefParserTest { void testMd5Sum() throws IOException { File defFile = new File(DEF_NAME); CNode root = new DefParser("test", new FileReader(defFile)).getTree(); - assertEquals("0501f9e2c4ecc8c283e100e0b1178ca4", root.defMd5); + assertEquals("ee37973499305fde315da46256e64b2e", root.defMd5); } @Test diff --git a/configgen/src/test/java/com/yahoo/config/codegen/JavaClassBuilderTest.java b/configgen/src/test/java/com/yahoo/config/codegen/JavaClassBuilderTest.java index 428576e340f..c3145c03fff 100644 --- a/configgen/src/test/java/com/yahoo/config/codegen/JavaClassBuilderTest.java +++ b/configgen/src/test/java/com/yahoo/config/codegen/JavaClassBuilderTest.java @@ -120,7 +120,7 @@ public class JavaClassBuilderTest { } for (int i = 0; i < referenceClassLines.size(); i++) { if (configClassLines.length <= i) - fail("Missing lines i generated config class. First missing line:\n" + referenceClassLines.get(i)); + fail("Missing lines in generated config class. First missing line:\n" + referenceClassLines.get(i)); assertEquals(referenceClassLines.get(i), configClassLines[i], "Line " + i); } } diff --git a/configgen/src/test/java/com/yahoo/config/codegen/NormalizedDefinitionTest.java b/configgen/src/test/java/com/yahoo/config/codegen/NormalizedDefinitionTest.java index 57b3ed962eb..18608102ffa 100644 --- a/configgen/src/test/java/com/yahoo/config/codegen/NormalizedDefinitionTest.java +++ b/configgen/src/test/java/com/yahoo/config/codegen/NormalizedDefinitionTest.java @@ -70,7 +70,7 @@ public class NormalizedDefinitionTest { } assertNotNull(out); - assertEquals(75, out.size()); + assertEquals(76, out.size()); assertNotNull(fileReader); fileReader.close(); diff --git a/configgen/src/test/resources/allfeatures.reference b/configgen/src/test/resources/allfeatures.reference index b7a79f663e7..79508b3a25f 100644 --- a/configgen/src/test/resources/allfeatures.reference +++ b/configgen/src/test/resources/allfeatures.reference @@ -35,7 +35,7 @@ import com.yahoo.config.*; */ public final class AllfeaturesConfig extends ConfigInstance { - public final static String CONFIG_DEF_MD5 = "0501f9e2c4ecc8c283e100e0b1178ca4"; + public final static String CONFIG_DEF_MD5 = "ee37973499305fde315da46256e64b2e"; public final static String CONFIG_DEF_NAME = "allfeatures"; public final static String CONFIG_DEF_NAMESPACE = "configgen"; public final static String[] CONFIG_DEF_SCHEMA = { @@ -56,6 +56,7 @@ public final class AllfeaturesConfig extends ConfigInstance { "refwithdef reference default=\":parent:\"", "fileVal file", "pathVal path", + "optionalPathVal path optional", "urlVal url", "modelVal model", "boolarr[] bool", @@ -130,6 +131,7 @@ public final class AllfeaturesConfig extends ConfigInstance { private String refwithdef = null; private String fileVal = null; private FileReference pathVal = null; + private Optional<FileReference> optionalPathVal = Optional.empty(); private UrlReference urlVal = null; private ModelReference modelVal = null; public List<Boolean> boolarr = new ArrayList<>(); @@ -171,6 +173,7 @@ public final class AllfeaturesConfig extends ConfigInstance { refwithdef(config.refwithdef()); fileVal(config.fileVal().value()); pathVal(config.pathVal.getFileReference()); + optionalPathVal(config.optionalPathVal.getFileReference()); urlVal(config.urlVal.getUrlReference()); modelVal(config.modelVal.getModelReference()); boolarr(config.boolarr()); @@ -231,6 +234,8 @@ public final class AllfeaturesConfig extends ConfigInstance { fileVal(__superior.fileVal); if (__superior.pathVal != null) pathVal(__superior.pathVal); + if (__superior.optionalPathVal != null) + optionalPathVal(__superior.optionalPathVal); if (__superior.urlVal != null) urlVal(__superior.urlVal); if (__superior.modelVal != null) @@ -412,6 +417,17 @@ public final class AllfeaturesConfig extends ConfigInstance { } + public Builder optionalPathVal(Optional<FileReference> __value) { + if (__value == null) throw new IllegalArgumentException("Null value is not allowed."); + optionalPathVal = __value; + __uninitialized.remove("optionalPathVal"); + return this; + } + + private Builder optionalPathVal(FileReference __value) { + return optionalPathVal(Optional.of(__value)); + } + public Builder urlVal(UrlReference __value) { if (__value == null) throw new IllegalArgumentException("Null value is not allowed."); urlVal = __value; @@ -759,6 +775,7 @@ public final class AllfeaturesConfig extends ConfigInstance { private final ReferenceNode refwithdef; private final FileNode fileVal; private final PathNode pathVal; + private final OptionalPathNode optionalPathVal; private final UrlNode urlVal; private final ModelNode modelVal; private final LeafNodeVector<Boolean, BooleanNode> boolarr; @@ -822,6 +839,8 @@ public final class AllfeaturesConfig extends ConfigInstance { new FileNode() : new FileNode(builder.fileVal); pathVal = (builder.pathVal == null) ? new PathNode() : new PathNode(builder.pathVal); + optionalPathVal = (builder.optionalPathVal == null) ? + new OptionalPathNode() : new OptionalPathNode(builder.optionalPathVal); urlVal = (builder.urlVal == null) ? new UrlNode() : new UrlNode(builder.urlVal); modelVal = (builder.modelVal == null) ? @@ -960,6 +979,13 @@ public final class AllfeaturesConfig extends ConfigInstance { } /** + * @return allfeatures.optionalPathVal + */ + public Optional<Path> optionalPathVal() { + return optionalPathVal.value(); + } + + /** * @return allfeatures.urlVal */ public File urlVal() { diff --git a/configgen/src/test/resources/configgen.allfeatures.def b/configgen/src/test/resources/configgen.allfeatures.def index 1f93e29b73b..eee39dc18f3 100644 --- a/configgen/src/test/resources/configgen.allfeatures.def +++ b/configgen/src/test/resources/configgen.allfeatures.def @@ -39,6 +39,7 @@ refVal reference refwithdef reference default=":parent:" fileVal file pathVal path +optionalPathVal path optional urlVal url modelVal model |