Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ConfigDocument API #280

Merged
merged 19 commits into from Mar 24, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 58 additions & 0 deletions config/src/main/java/com/typesafe/config/ConfigDocument.java
@@ -0,0 +1,58 @@
package com.typesafe.config;

/**
* An object parsed from the original input text, which can be used to
* replace individual values and exactly render the original text of the
* input.
*
* <p>
* Because this object is immutable, it is safe to use from multiple threads and
* there's no need for "defensive copies."
*
* <p>
* <em>Do not implement interface {@code ConfigNode}</em>; it should only be
* implemented by the config library. Arbitrary implementations will not work
* because the library internals assume a specific concrete implementation.
* Also, this interface is likely to grow new methods over time, so third-party
* implementations will break.
*/
public interface ConfigDocument {
/**
* Returns a new ConfigDocument that is a copy of the current ConfigDocument,
* but with the desired value set at the desired path. If the path exists, it will
* remove all duplicates before the final occurrence of the path, and replace the value
* at the final occurrence of the path. If the path does not exist, it will be added.
*
* @param path the path at which to set the desired value
* @param newValue the value to set at the desired path, represented as a string. This
* string will be parsed into a ConfigNode using the same options used to
* parse the entire document, and the text will be inserted
* as-is into the document. Leading and trailing comments, whitespace, or
* newlines are not allowed, and if present an exception will be thrown.
* If a concatenation is passed in for newValue but the document was parsed
* with JSON, the first value in the concatenation will be parsed and inserted
* into the ConfigDocument.
* @return a copy of the ConfigDocument with the desired value at the desired path
*/
ConfigDocument setValue(String path, String newValue);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it can go in a later PR but a natural method to add here would be one that takes a ConfigValue rather than a String (presumably it would just chain over to the string one by doing value.render() though)


/**
* Returns a new ConfigDocument that is a copy of the current ConfigDocument,
* but with the desired value set at the desired path as with {@link #setValue(String, String)},
* but takes a ConfigValue instead of a string.
*
* @param path the path at which to set the desired value
* @param newValue the value to set at the desired path, represented as a ConfigValue.
* The rendered text of the ConfigValue will be inserted into the
* ConfigDocument.
* @return a copy of the ConfigDocument with the desired value at the desired path
*/
ConfigDocument setValue(String path, ConfigValue newValue);

/**
* The original text of the input, modified if necessary with
* any replaced or added values.
* @return the modified original text
*/
String render();
}
@@ -0,0 +1,92 @@
package com.typesafe.config;

import com.typesafe.config.impl.ConfigImpl;
import com.typesafe.config.impl.Parseable;

import java.io.File;
import java.io.Reader;

/**
* Factory for automatically creating a ConfigDocument from a given input. Currently
* only supports files and strings.
*/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we might want Reader too I suppose. Then people can plug in other kinds of stream.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, I'll add support for a Reader as well.

public final class ConfigDocumentFactory {

/**
* Parses a Reader into a ConfigDocument instance.
*
* @param reader
* the reader to parse
* @param options
* parse options to control how the reader is interpreted
* @return the parsed configuration
* @throws ConfigException on IO or parse errors
*/
public static ConfigDocument parseReader(Reader reader, ConfigParseOptions options) {
return Parseable.newReader(reader, options).parseConfigDocument();
}

/**
* Parses a reader into a Config instance as with
* {@link #parseReader(Reader,ConfigParseOptions)} but always uses the
* default parse options.
*
* @param reader
* the reader to parse
* @return the parsed configuration
* @throws ConfigException on IO or parse errors
*/
public static ConfigDocument parseReader(Reader reader) {
return parseReader(reader, ConfigParseOptions.defaults());
}

/**
* Parses a file into a ConfigDocument instance.
*
* @param file
* the file to parse
* @param options
* parse options to control how the file is interpreted
* @return the parsed configuration
* @throws ConfigException on IO or parse errors
*/
public static ConfigDocument parseFile(File file, ConfigParseOptions options) {
return Parseable.newFile(file, options).parseConfigDocument();
}

/**
* Parses a file into a ConfigDocument instance as with
* {@link #parseFile(File,ConfigParseOptions)} but always uses the
* default parse options.
*
* @param file
* the file to parse
* @return the parsed configuration
* @throws ConfigException on IO or parse errors
*/
public static ConfigDocument parseFile(File file) {
return parseFile(file, ConfigParseOptions.defaults());
}

/**
* Parses a string which should be valid HOCON or JSON.
*
* @param s string to parse
* @param options parse options
* @return the parsed configuration
*/
public static ConfigDocument parseString(String s, ConfigParseOptions options) {
return Parseable.newString(s, options).parseConfigDocument();
}

/**
* Parses a string (which should be valid HOCON or JSON). Uses the
* default parse options.
*
* @param s string to parse
* @return the parsed configuration
*/
public static ConfigDocument parseString(String s) {
return parseString(s, ConfigParseOptions.defaults());
}
}
27 changes: 27 additions & 0 deletions config/src/main/java/com/typesafe/config/ConfigNode.java
@@ -0,0 +1,27 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com>
*/
package com.typesafe.config;

/**
* An immutable node that makes up the ConfigDocument AST, and which can be
* used to reproduce part or all of the original text of an input.
*
* <p>
* Because this object is immutable, it is safe to use from multiple threads and
* there's no need for "defensive copies."
*
* <p>
* <em>Do not implement interface {@code ConfigNode}</em>; it should only be
* implemented by the config library. Arbitrary implementations will not work
* because the library internals assume a specific concrete implementation.
* Also, this interface is likely to grow new methods over time, so third-party
* implementations will break.
*/
public interface ConfigNode {
/**
* The original text of the input which was used to form this particular node.
* @return the original text used to form this node as a String
*/
public String render();
}
@@ -0,0 +1,29 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com>
*/
package com.typesafe.config.impl;

import com.typesafe.config.ConfigNode;
import java.util.Collection;

abstract class AbstractConfigNode implements ConfigNode {
abstract Collection<Token> tokens();
final public String render() {
StringBuilder origText = new StringBuilder();
Iterable<Token> tokens = tokens();
for (Token t : tokens) {
origText.append(t.tokenText());
}
return origText.toString();
}

@Override
final public boolean equals(Object other) {
return other instanceof AbstractConfigNode && render().equals(((AbstractConfigNode)other).render());
}

@Override
final public int hashCode() {
return render().hashCode();
}
}
@@ -0,0 +1,11 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com>
*/
package com.typesafe.config.impl;

// This is required if we want
// to be referencing the AbstractConfigNode class in implementation rather than the
// ConfigNode interface, as we can't cast an AbstractConfigNode to an interface
abstract class AbstractConfigNodeValue extends AbstractConfigNode {

}