Modify duplicate removal

Modify the ConfigDocument duplicate removal so that multi-element
paths beginning with the desired path whose value should be set
are always removed as duplicates.
This commit is contained in:
Preben Ingvaldsen 2015-03-30 11:51:59 -07:00
parent 97bd1f60c0
commit d6c9f632ab
2 changed files with 38 additions and 31 deletions

View File

@ -1,11 +1,9 @@
package com.typesafe.config.impl; package com.typesafe.config.impl;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigSyntax; import com.typesafe.config.ConfigSyntax;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List;
final class ConfigNodeObject extends ConfigNodeComplexValue { final class ConfigNodeObject extends ConfigNodeComplexValue {
ConfigNodeObject(Collection<AbstractConfigNode> children) { ConfigNodeObject(Collection<AbstractConfigNode> children) {
@ -44,39 +42,38 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
childrenCopy.remove(i); childrenCopy.remove(i);
} }
continue; continue;
}else if (!(childrenCopy.get(i) instanceof ConfigNodeField)) { } else if (!(childrenCopy.get(i) instanceof ConfigNodeField)) {
continue; continue;
} }
ConfigNodeField node = (ConfigNodeField)childrenCopy.get(i); ConfigNodeField node = (ConfigNodeField) childrenCopy.get(i);
Path key = node.path().value(); Path key = node.path().value();
if (key.equals(desiredPath) || key.startsWith(desiredPath)) {
if (valueCopy == null) { // Delete all multi-element paths that start with the desired path, since technically they are duplicates
childrenCopy.remove(i); if ((valueCopy == null && key.equals(desiredPath))|| (key.startsWith(desiredPath) && !key.equals(desiredPath))) {
// Remove any whitespace or commas after the deleted setting childrenCopy.remove(i);
for (int j = i; j < childrenCopy.size(); j++) { // Remove any whitespace or commas after the deleted setting
if (childrenCopy.get(j) instanceof ConfigNodeSingleToken) { for (int j = i; j < childrenCopy.size(); j++) {
Token t = ((ConfigNodeSingleToken) childrenCopy.get(j)).token(); if (childrenCopy.get(j) instanceof ConfigNodeSingleToken) {
if (Tokens.isIgnoredWhitespace(t) || t == Tokens.COMMA) { Token t = ((ConfigNodeSingleToken) childrenCopy.get(j)).token();
childrenCopy.remove(j); if (Tokens.isIgnoredWhitespace(t) || t == Tokens.COMMA) {
j--; childrenCopy.remove(j);
} else { j--;
break;
}
} else { } else {
break; break;
} }
} else {
break;
} }
} }
else if (key.equals(desiredPath)){ } else if (key.equals(desiredPath)) {
seenNonMatching = true; seenNonMatching = true;
childrenCopy.set(i, node.replaceValue(value)); childrenCopy.set(i, node.replaceValue(value));
valueCopy = null; valueCopy = null;
}
} else if (desiredPath.startsWith(key)) { } else if (desiredPath.startsWith(key)) {
seenNonMatching = true; seenNonMatching = true;
if (node.value() instanceof ConfigNodeObject) { if (node.value() instanceof ConfigNodeObject) {
Path remainingPath = desiredPath.subPath(key.length()); Path remainingPath = desiredPath.subPath(key.length());
childrenCopy.set(i, node.replaceValue(((ConfigNodeObject)node.value()).changeValueOnPath(remainingPath, valueCopy, flavor))); childrenCopy.set(i, node.replaceValue(((ConfigNodeObject) node.value()).changeValueOnPath(remainingPath, valueCopy, flavor)));
if (valueCopy != null && !node.equals(super.children.get(i))) if (valueCopy != null && !node.equals(super.children.get(i)))
valueCopy = null; valueCopy = null;
} }
@ -84,11 +81,6 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
seenNonMatching = true; seenNonMatching = true;
} }
} }
// Since we've removed values and valid JSON does not allow trailing commas, remove a comma after the final setting
if (flavor == ConfigSyntax.JSON) {
}
return new ConfigNodeObject(childrenCopy); return new ConfigNodeObject(childrenCopy);
} }
@ -105,8 +97,8 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
ConfigNodeObject node = changeValueOnPath(desiredPath.value(), value, flavor); ConfigNodeObject node = changeValueOnPath(desiredPath.value(), value, flavor);
// If the desired Path did not exist, add it // If the desired Path did not exist, add it
if (node.equals(this)) { if (!node.hasValue(desiredPath.value())) {
return addValueOnPath(desiredPath, value, flavor); return node.addValueOnPath(desiredPath, value, flavor);
} }
return node; return node;
} }

View File

@ -27,7 +27,7 @@ class ConfigDocumentTest extends TestUtils {
} }
@Test @Test
def configDocumentReplace() { def configDocumentReplace {
// Can handle parsing/replacement with a very simple map // Can handle parsing/replacement with a very simple map
configDocumentReplaceConfTest("""{"a":1}""", """{"a":2}""", "2", "a") configDocumentReplaceConfTest("""{"a":1}""", """{"a":2}""", "2", "a")
configDocumentReplaceJsonTest("""{"a":1}""", """{"a":2}""", "2", "a") configDocumentReplaceJsonTest("""{"a":1}""", """{"a":2}""", "2", "a")
@ -136,6 +136,21 @@ class ConfigDocumentTest extends TestUtils {
"this is a concatenation 123 456 {a:b} [1,2,3] {a: this is another 123 concatenation null true}", "h.b.a") "this is a concatenation 123 456 {a:b} [1,2,3] {a: this is another 123 concatenation null true}", "h.b.a")
} }
@Test
def configDocumentMultiElementDuplicatesRemoved {
var origText = "{a: b, a.b.c: d, a: e}"
var configDoc = ConfigDocumentFactory.parseString(origText)
assertEquals("{a: 2}", configDoc.setValue("a", "2").render())
origText = "{a: b, a: e, a.b.c: d}"
configDoc = ConfigDocumentFactory.parseString(origText)
assertEquals("{a: 2, }", configDoc.setValue("a", "2").render())
origText = "{a.b.c: d}"
configDoc = ConfigDocumentFactory.parseString(origText)
assertEquals("{\na : 2\n}", configDoc.setValue("a", "2").render())
}
@Test @Test
def configDocumentSetNewValueBraceRoot { def configDocumentSetNewValueBraceRoot {
val origText = "{\n\t\"a\":\"b\",\n\t\"c\":\"d\"\n}" val origText = "{\n\t\"a\":\"b\",\n\t\"c\":\"d\"\n}"