add ConfigUtil.joinPath, .splitPath, .quoteString

To help people get their escaping and quoting right.
This commit is contained in:
Havoc Pennington 2011-12-09 10:20:02 -05:00
parent aa8a258b59
commit 6991862def
5 changed files with 154 additions and 9 deletions

View File

@ -34,6 +34,10 @@ import java.util.Set;
* {@code ConfigObject} is a tree of nested maps from <em>keys</em> to values.
*
* <p>
* Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert
* between path expressions and individual path elements (keys).
*
* <p>
* Another difference between {@code Config} and {@code ConfigObject} is that
* conceptually, {@code ConfigValue}s with a {@link ConfigValue#valueType()
* valueType()} of {@link ConfigValueType#NULL NULL} exist in a
@ -264,7 +268,7 @@ public interface Config extends ConfigMergeable {
* recursing {@link #root() the root object}. Note that this is very
* different from <code>root().entrySet()</code> which returns the set of
* immediate-child keys in the root object and includes null values.
*
*
* @return set of paths with non-null values, built up by recursing the
* entire tree of {@link ConfigObject}
*/

View File

@ -8,34 +8,38 @@ import java.util.Map;
/**
* Subtype of {@link ConfigValue} representing an object (dictionary, map)
* value, as in JSON's <code>{ "a" : 42 }</code> syntax.
*
*
* <p>
* {@code ConfigObject} implements {@code java.util.Map<String, ConfigValue>} so
* you can use it like a regular Java map. Or call {@link #unwrapped()} to
* unwrap the map to a map with plain Java values rather than
* {@code ConfigValue}.
*
*
* <p>
* Like all {@link ConfigValue} subtypes, {@code ConfigObject} is immutable.
* This makes it threadsafe and you never have to create "defensive copies." The
* mutator methods from {@link java.util.Map} all throw
* {@link java.lang.UnsupportedOperationException}.
*
*
* <p>
* The {@link ConfigValue#valueType} method on an object returns
* {@link ConfigValueType#OBJECT}.
*
*
* <p>
* In most cases you want to use the {@link Config} interface rather than this
* one. Call {@link #toConfig()} to convert a {@code ConfigObject} to a
* {@code Config}.
*
*
* <p>
* The API for a {@code ConfigObject} is in terms of keys, while the API for a
* {@link Config} is in terms of path expressions. Conceptually,
* {@code ConfigObject} is a tree of maps from keys to values, while a
* {@code ConfigObject} is a one-level map from paths to values.
*
*
* <p>
* Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert
* between path expressions and individual path elements (keys).
*
* <p>
* A {@code ConfigObject} may contain null values, which will have
* {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If
@ -43,7 +47,7 @@ import java.util.Map;
* file (or wherever this value tree came from). If {@code get()} returns a
* {@link ConfigValue} with type {@code ConfigValueType#NULL} then the key was
* set to null explicitly in the config file.
*
*
* <p>
* <em>Do not implement {@code ConfigObject}</em>; it should only be implemented
* by the config library. Arbitrary implementations will not work because the

View File

@ -0,0 +1,70 @@
package com.typesafe.config;
import java.util.List;
import com.typesafe.config.impl.ConfigImplUtil;
public final class ConfigUtil {
private ConfigUtil() {
}
/**
* Quotes and escapes a string, as in the JSON specification.
*
* @param s
* a string
* @return the string quoted and escaped
*/
public static String quoteString(String s) {
return ConfigImplUtil.renderJsonString(s);
}
/**
* Converts a list of keys to a path expression, by quoting the path
* elements as needed and then joining them separated by a period. A path
* expression is usable with a {@link Config}, while individual path
* elements are usable with a {@link ConfigObject}.
*
* @param elements
* the keys in the path
* @return a path expression
* @throws ConfigException
* if there are no elements
*/
public static String joinPath(String... elements) {
return ConfigImplUtil.joinPath(elements);
}
/**
* Converts a list of strings to a path expression, by quoting the path
* elements as needed and then joining them separated by a period. A path
* expression is usable with a {@link Config}, while individual path
* elements are usable with a {@link ConfigObject}.
*
* @param elements
* the keys in the path
* @return a path expression
* @throws ConfigException
* if the list is empty
*/
public static String joinPath(List<String> elements) {
return ConfigImplUtil.joinPath(elements);
}
/**
* Converts a path expression into a list of keys, by splitting on period
* and unquoting the individual path elements. A path expression is usable
* with a {@link Config}, while individual path elements are usable with a
* {@link ConfigObject}.
*
* @param path
* a path expression
* @return the individual keys in the path
* @throws ConfigException
* if the path expression is invalid
*/
public static List<String> splitPath(String path) {
return ConfigImplUtil.splitPath(path);
}
}

View File

@ -6,6 +6,8 @@ package com.typesafe.config.impl;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import com.typesafe.config.ConfigException;
@ -23,7 +25,11 @@ final public class ConfigImplUtil {
return a.equals(b);
}
static String renderJsonString(String s) {
/**
* This is public ONLY for use by the "config" package, DO NOT USE this ABI
* may change.
*/
public static String renderJsonString(String s) {
StringBuilder sb = new StringBuilder();
sb.append('"');
for (int i = 0; i < s.length(); ++i) {
@ -146,4 +152,34 @@ final public class ConfigImplUtil {
return new File(url.getPath());
}
}
/**
* This is public ONLY for use by the "config" package, DO NOT USE this ABI
* may change. You can use the version in ConfigUtil instead.
*/
public static String joinPath(String... elements) {
return (new Path(elements)).render();
}
/**
* This is public ONLY for use by the "config" package, DO NOT USE this ABI
* may change. You can use the version in ConfigUtil instead.
*/
public static String joinPath(List<String> elements) {
return joinPath(elements.toArray(new String[0]));
}
/**
* This is public ONLY for use by the "config" package, DO NOT USE this ABI
* may change. You can use the version in ConfigUtil instead.
*/
public static List<String> splitPath(String path) {
Path p = Path.newPath(path);
List<String> elements = new ArrayList<String>();
while (p != null) {
elements.add(p.first());
p = p.remainder();
}
return elements;
}
}

View File

@ -433,4 +433,35 @@ class PublicApiTest extends TestUtils {
assertFalse("same urls in " + v1.origin + " " + v2.origin, v1.origin.url == v2.origin.url)
assertFalse(v1.origin.filename == v2.origin.filename)
}
@Test
def splitAndJoinPath() {
// the actual join-path logic should be tested OK in the non-public-API tests,
// this is just to test the public wrappers.
assertEquals("\"\".a.b.\"$\"", ConfigUtil.joinPath("", "a", "b", "$"))
assertEquals("\"\".a.b.\"$\"", ConfigUtil.joinPath(Seq("", "a", "b", "$").asJava))
assertEquals(Seq("", "a", "b", "$"), ConfigUtil.splitPath("\"\".a.b.\"$\"").asScala)
// invalid stuff throws
intercept[ConfigException] {
ConfigUtil.splitPath("$")
}
intercept[ConfigException] {
ConfigUtil.joinPath()
}
intercept[ConfigException] {
ConfigUtil.joinPath(Collections.emptyList[String]())
}
}
@Test
def quoteString() {
// the actual quote logic shoudl be tested OK in the non-public-API tests,
// this is just to test the public wrapper.
assertEquals("\"\"", ConfigUtil.quoteString(""))
assertEquals("\"a\"", ConfigUtil.quoteString("a"))
assertEquals("\"\\n\"", ConfigUtil.quoteString("\n"))
}
}