pax_global_header00006660000000000000000000000064127714727460014533gustar00rootroot0000000000000052 comment=82e1b25f62cf6add4f64b5d909c56a653f449f4a config-1.3.1/000077500000000000000000000000001277147274600130025ustar00rootroot00000000000000config-1.3.1/.gitignore000066400000000000000000000001421277147274600147670ustar00rootroot00000000000000.classpath .project .cache .settings .idea .idea_modules /bin-test-lib target/ /bin /node_modules config-1.3.1/.travis.yml000066400000000000000000000012701277147274600151130ustar00rootroot00000000000000# use Docker-based container (instead of OpenVZ) sudo: false cache: directories: - $HOME/.ivy2/cache # Cache the sbt launcher, currently the Travis VM preinstalls 0.13.5 - $HOME/.sbt/launchers/0.13.7 # Cache scala, currently the Travis VM preinstalls 2.11.2 & 2.10.4 #- $HOME/.sbt/boot/scala-$TRAVIS_SCALA_VERSION # Updates regarding Travis VM preinstalls: # https://github.com/travis-ci/travis-cookbooks/blob/master/changes.md language: scala jdk: - oraclejdk8 script: - sbt ++$TRAVIS_SCALA_VERSION test doc # Remove to avoid unnecessary cache updates - find $HOME/.sbt -name "*.lock" -delete - find $HOME/.ivy2 -name "ivydata-*.properties" -delete config-1.3.1/CONTRIBUTING.md000066400000000000000000000046621277147274600152430ustar00rootroot00000000000000# Submitting pull requests Pull requests should go via GitHub; there are some nice [general guidelines for contributing on GitHub](https://guides.github.com/activities/contributing-to-open-source/) if you haven't done it before. Unless your fix is trivial, it's a good idea to look for related discussions and maybe open an issue to discuss, before making a pull request. Discussion in advance may keep you from wasting time on an approach that won't work out. However, if you communicate better in code than in words, starting with a patch is fine... just be willing to revise it! Before we can accept pull requests, you will need to agree to the Typesafe Contributor License Agreement online, using your GitHub account - it takes 30 seconds. You can do this at http://www.typesafe.com/contribute/cla Expect that most PRs will need revision before merge. If people suggest revisions, you can make them yourself or wait for a maintainer to do it on their own timeline. The larger the PR, the more revision will likely be needed. # Making a release To make a release you'll need to be a maintainer with GitHub permissions to push to the master and gh-pages branches, and Sonatype permissions to publish. Here are the steps, which should be automated but aren't (PR welcome!): 1. write release notes in NEWS.md following the format already in there. commit. 2. create a signed git tag "vX.Y.Z" 3. start sbt; `show version` should confirm that the version was taken from the tag 4. clean 5. test (double check that release works) 6. doc (double check that docs build, plus build docs to be copied to gh-pages later) 7. if test or doc fails, delete the tag, fix it, start over. 8. publishSigned 9. make a separate clone of the repo in another directory and check out the gh-pages branch 10. /bin/rm -rf latest/api on gh-pages checkout 11. copy config/target/api from master checkout to vX.Y.Z in gh-pages so you have vX.Y.Z/index.html 12. copy config/target/api from master checkout into latest/ so you have latest/api/index.html 13. commit all that to gh-pages, check the diff for sanity (latest/api should be mostly just changed timestamps) 14. push gh-pages 15. log into sonatype website and go through the usual hoops (search for com.typesafe, verify the artifacts in it, close, release) 16. push the "vX.Y.Z" tag 17. announce release, possibly wait for maven central to sync first config-1.3.1/HOCON.md000066400000000000000000001705011277147274600141760ustar00rootroot00000000000000 **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [HOCON (Human-Optimized Config Object Notation)](#hocon-human-optimized-config-object-notation) - [Goals / Background](#goals--background) - [Definitions](#definitions) - [Syntax](#syntax) - [Unchanged from JSON](#unchanged-from-json) - [Comments](#comments) - [Omit root braces](#omit-root-braces) - [Key-value separator](#key-value-separator) - [Commas](#commas) - [Whitespace](#whitespace) - [Duplicate keys and object merging](#duplicate-keys-and-object-merging) - [Unquoted strings](#unquoted-strings) - [Multi-line strings](#multi-line-strings) - [Value concatenation](#value-concatenation) - [String value concatenation](#string-value-concatenation) - [Array and object concatenation](#array-and-object-concatenation) - [Note: Concatenation with whitespace and substitutions](#note-concatenation-with-whitespace-and-substitutions) - [Note: Arrays without commas or newlines](#note-arrays-without-commas-or-newlines) - [Path expressions](#path-expressions) - [Paths as keys](#paths-as-keys) - [Substitutions](#substitutions) - [Self-Referential Substitutions](#self-referential-substitutions) - [The `+=` field separator](#the--field-separator) - [Examples of Self-Referential Substitutions](#examples-of-self-referential-substitutions) - [Includes](#includes) - [Include syntax](#include-syntax) - [Include semantics: merging](#include-semantics-merging) - [Include semantics: substitution](#include-semantics-substitution) - [Include semantics: missing files](#include-semantics-missing-files) - [Include semantics: file formats and extensions](#include-semantics-file-formats-and-extensions) - [Include semantics: locating resources](#include-semantics-locating-resources) - [Conversion of numerically-indexed objects to arrays](#conversion-of-numerically-indexed-objects-to-arrays) - [MIME Type](#mime-type) - [API Recommendations](#api-recommendations) - [Automatic type conversions](#automatic-type-conversions) - [Units format](#units-format) - [Duration format](#duration-format) - [Size in bytes format](#size-in-bytes-format) - [Config object merging and file merging](#config-object-merging-and-file-merging) - [Java properties mapping](#java-properties-mapping) - [Conventional configuration files for JVM apps](#conventional-configuration-files-for-jvm-apps) - [Conventional override by system properties](#conventional-override-by-system-properties) - [Substitution fallback to environment variables](#substitution-fallback-to-environment-variables) - [hyphen-separated vs. camelCase](#hyphen-separated-vs-camelcase) - [Note on Java properties similarity](#note-on-java-properties-similarity) # HOCON (Human-Optimized Config Object Notation) This is an informal spec, but hopefully it's clear. ## Goals / Background The primary goal is: keep the semantics (tree structure; set of types; encoding/escaping) from JSON, but make it more convenient as a human-editable config file format. The following features are desirable, to support human usage: - less noisy / less pedantic syntax - ability to refer to another part of the configuration (set a value to another value) - import/include another configuration file into the current file - a mapping to a flat properties list such as Java's system properties - ability to get values from environment variables - ability to write comments Implementation-wise, the format should have these properties: - a JSON superset, that is, all valid JSON should be valid and should result in the same in-memory data that a JSON parser would have produced. - be deterministic; the format is flexible, but it is not heuristic. It should be clear what's invalid and invalid files should generate errors. - require minimal look-ahead; should be able to tokenize the file by looking at only the next three characters. (right now, the only reason to look at three is to find "//" comments; otherwise you can parse looking at two.) HOCON is significantly harder to specify and to parse than JSON. Think of it as moving the work from the person maintaining the config file to the computer program. ## Definitions - a _key_ is a string JSON would have to the left of `:` and a _value_ is anything JSON would have to the right of `:`. i.e. the two halves of an object _field_. - a _value_ is any "value" as defined in the JSON spec, plus unquoted strings and substitutions as defined in this spec. - a _simple value_ is any value excluding an object or array value. - a _field_ is a key, any separator such as ':', and a value. - references to a _file_ ("the file being parsed") can be understood to mean any byte stream being parsed, not just literal files in a filesystem. ## Syntax Much of this is defined with reference to JSON; you can find the JSON spec at http://json.org/ of course. ### Unchanged from JSON - files must be valid UTF-8 - quoted strings are in the same format as JSON strings - values have possible types: string, number, object, array, boolean, null - allowed number formats matches JSON; as in JSON, some possible floating-point values are not represented, such as `NaN` ### Comments Anything between `//` or `#` and the next newline is considered a comment and ignored, unless the `//` or `#` is inside a quoted string. ### Omit root braces JSON documents must have an array or object at the root. Empty files are invalid documents, as are files containing only a non-array non-object value such as a string. In HOCON, if the file does not begin with a square bracket or curly brace, it is parsed as if it were enclosed with `{}` curly braces. A HOCON file is invalid if it omits the opening `{` but still has a closing `}`; the curly braces must be balanced. ### Key-value separator The `=` character can be used anywhere JSON allows `:`, i.e. to separate keys from values. If a key is followed by `{`, the `:` or `=` may be omitted. So `"foo" {}` means `"foo" : {}` ### Commas Values in arrays, and fields in objects, need not have a comma between them as long as they have at least one ASCII newline (`\n`, decimal value 10) between them. The last element in an array or last field in an object may be followed by a single comma. This extra comma is ignored. - `[1,2,3,]` and `[1,2,3]` are the same array. - `[1\n2\n3]` and `[1,2,3]` are the same array. - `[1,2,3,,]` is invalid because it has two trailing commas. - `[,1,2,3]` is invalid because it has an initial comma. - `[1,,2,3]` is invalid because it has two commas in a row. - these same comma rules apply to fields in objects. ### Whitespace The JSON spec simply says "whitespace"; in HOCON whitespace is defined as follows: - any Unicode space separator (Zs category), line separator (Zl category), or paragraph separator (Zp category), including nonbreaking spaces (such as 0x00A0, 0x2007, and 0x202F). The BOM (0xFEFF) must also be treated as whitespace. - tab (`\t` 0x0009), newline ('\n' 0x000A), vertical tab ('\v' 0x000B)`, form feed (`\f' 0x000C), carriage return ('\r' 0x000D), file separator (0x001C), group separator (0x001D), record separator (0x001E), unit separator (0x001F). In Java, the `isWhitespace()` method covers these characters with the exception of nonbreaking spaces and the BOM. While all Unicode separators should be treated as whitespace, in this spec "newline" refers only and specifically to ASCII newline 0x000A. ### Duplicate keys and object merging The JSON spec does not clarify how duplicate keys in the same object should be handled. In HOCON, duplicate keys that appear later override those that appear earlier, unless both values are objects. If both values are objects, then the objects are merged. Note: this would make HOCON a non-superset of JSON if you assume that JSON requires duplicate keys to have a behavior. The assumption here is that duplicate keys are invalid JSON. To merge objects: - add fields present in only one of the two objects to the merged object. - for non-object-valued fields present in both objects, the field found in the second object must be used. - for object-valued fields present in both objects, the object values should be recursively merged according to these same rules. Object merge can be prevented by setting the key to another value first. This is because merging is always done two values at a time; if you set a key to an object, a non-object, then an object, first the non-object falls back to the object (non-object always wins), and then the object falls back to the non-object (no merging, object is the new value). So the two objects never see each other. These two are equivalent: { "foo" : { "a" : 42 }, "foo" : { "b" : 43 } } { "foo" : { "a" : 42, "b" : 43 } } And these two are equivalent: { "foo" : { "a" : 42 }, "foo" : null, "foo" : { "b" : 43 } } { "foo" : { "b" : 43 } } The intermediate setting of `"foo"` to `null` prevents the object merge. ### Unquoted strings A sequence of characters outside of a quoted string is a string value if: - it does not contain "forbidden characters": '$', '"', '{', '}', '[', ']', ':', '=', ',', '+', '#', '`', '^', '?', '!', '@', '*', '&', '\' (backslash), or whitespace. - it does not contain the two-character string "//" (which starts a comment) - its initial characters do not parse as `true`, `false`, `null`, or a number. Unquoted strings are used literally, they do not support any kind of escaping. Quoted strings may always be used as an alternative when you need to write a character that is not permitted in an unquoted string. `truefoo` parses as the boolean token `true` followed by the unquoted string `foo`. However, `footrue` parses as the unquoted string `footrue`. Similarly, `10.0bar` is the number `10.0` then the unquoted string `bar` but `bar10.0` is the unquoted string `bar10.0`. (In practice, this distinction doesn't matter much because of value concatenation; see later section.) In general, once an unquoted string begins, it continues until a forbidden character or the two-character string "//" is encountered. Embedded (non-initial) booleans, nulls, and numbers are not recognized as such, they are part of the string. An unquoted string may not _begin_ with the digits 0-9 or with a hyphen (`-`, 0x002D) because those are valid characters to begin a JSON number. The initial number character, plus any valid-in-JSON number characters that follow it, must be parsed as a number value. Again, these characters are not special _inside_ an unquoted string; they only trigger number parsing if they appear initially. Note that quoted JSON strings may not contain control characters (control characters include some whitespace characters, such as newline). This rule is from the JSON spec. However, unquoted strings have no restriction on control characters, other than the ones listed as "forbidden characters" above. Some of the "forbidden characters" are forbidden because they already have meaning in JSON or HOCON, others are essentially reserved keywords to allow future extensions to this spec. ### Multi-line strings Multi-line strings are similar to Python or Scala, using triple quotes. If the three-character sequence `"""` appears, then all Unicode characters until a closing `"""` sequence are used unmodified to create a string value. Newlines and whitespace receive no special treatment. Unlike Scala, and unlike JSON quoted strings, Unicode escapes are not interpreted in triple-quoted strings. In Python, `"""foo""""` is a syntax error (a triple-quoted string followed by a dangling unbalanced quote). In Scala, it is a four-character string `foo"`. HOCON works like Scala; any sequence of at least three quotes ends the multi-line string, and any "extra" quotes are part of the string. ### Value concatenation The value of an object field or array element may consist of multiple values which are combined. There are three kinds of value concatenation: - if all the values are simple values (neither objects nor arrays), they are concatenated into a string. - if all the values are arrays, they are concatenated into one array. - if all the values are objects, they are merged (as with duplicate keys) into one object. String value concatenation is allowed in field keys, in addition to field values and array elements. Objects and arrays do not make sense as field keys. Note: Akka 2.0 (and thus Play 2.0) contains an embedded implementation of the config lib which does not support array and object value concatenation; it only supports string value concatenation. #### String value concatenation String value concatenation is the trick that makes unquoted strings work; it also supports substitutions (`${foo}` syntax) in strings. Only simple values participate in string value concatenation. Recall that a simple value is any value other than arrays and objects. As long as simple values are separated only by non-newline whitespace, the _whitespace between them is preserved_ and the values, along with the whitespace, are concatenated into a string. String value concatenations never span a newline, or a character that is not part of a simple value. A string value concatenation may appear in any place that a string may appear, including object keys, object values, and array elements. Whenever a value would appear in JSON, a HOCON parser instead collects multiple values (including the whitespace between them) and concatenates those values into a string. Whitespace before the first and after the last simple value must be discarded. Only whitespace _between_ simple values must be preserved. So for example ` foo bar baz ` parses as three unquoted strings, and the three are value-concatenated into one string. The inner whitespace is kept and the leading and trailing whitespace is trimmed. The equivalent string, written in quoted form, would be `"foo bar baz"`. Value concatenating `foo bar` (two unquoted strings with whitespace) and quoted string `"foo bar"` would result in the same in-memory representation, seven characters. For purposes of string value concatenation, non-string values are converted to strings as follows (strings shown as quoted strings): - `true` and `false` become the strings `"true"` and `"false"`. - `null` becomes the string `"null"`. - quoted and unquoted strings are themselves. - numbers should be kept as they were originally written in the file. For example, if you parse `1e5` then you might render it alternatively as `1E5` with capital `E`, or just `100000`. For purposes of value concatenation, it should be rendered as it was written in the file. - a substitution is replaced with its value which is then converted to a string as above. - it is invalid for arrays or objects to appear in a string value concatenation. A single value is never converted to a string. That is, it would be wrong to value concatenate `true` by itself; that should be parsed as a boolean-typed value. Only `true foo` (`true` with another simple value on the same line) should be parsed as a value concatenation and converted to a string. #### Array and object concatenation Arrays can be concatenated with arrays, and objects with objects, but it is an error if they are mixed. For purposes of concatenation, "array" also means "substitution that resolves to an array" and "object" also means "substitution that resolves to an object." Within an field value or array element, if only non-newline whitespace separates the end of a first array or object or substitution from the start of a second array or object or substitution, the two values are concatenated. Newlines may occur _within_ the array or object, but not _between_ them. Newlines _between_ prevent concatenation. For objects, "concatenation" means "merging", so the second object overrides the first. Arrays and objects cannot be field keys, whether concatenation is involved or not. Here are several ways to define `a` to the same object value: // one object a : { b : 1, c : 2 } // two objects that are merged via concatenation rules a : { b : 1 } { c : 2 } // two fields that are merged a : { b : 1 } a : { c : 2 } Here are several ways to define `a` to the same array value: // one array a : [ 1, 2, 3, 4 ] // two arrays that are concatenated a : [ 1, 2 ] [ 3, 4 ] // a later definition referring to an earlier // (see "self-referential substitutions" below) a : [ 1, 2 ] a : ${a} [ 3, 4 ] A common use of object concatenation is "inheritance": data-center-generic = { cluster-size = 6 } data-center-east = ${data-center-generic} { name = "east" } A common use of array concatenation is to add to paths: path = [ /bin ] path = ${path} [ /usr/bin ] #### Note: Concatenation with whitespace and substitutions When concatenating substitutions such as `${foo} ${bar}`, the substitutions may turn out to be strings (which makes the whitespace between them significant) or may turn out to be objects or lists (which makes it irrelevant). Unquoted whitespace must be ignored in between substitutions which resolve to objects or lists. Quoted whitespace should be an error. #### Note: Arrays without commas or newlines Arrays allow you to use newlines instead of commas, but not whitespace instead of commas. Non-newline whitespace will produce concatenation rather than separate elements. // this is an array with one element, the string "1 2 3 4" [ 1 2 3 4 ] // this is an array of four integers [ 1 2 3 4 ] // an array of one element, the array [ 1, 2, 3, 4 ] [ [ 1, 2 ] [ 3, 4 ] ] // an array of two arrays [ [ 1, 2 ] [ 3, 4 ] ] If this gets confusing, just use commas. The concatenation behavior is useful rather than surprising in cases like: [ This is an unquoted string my name is ${name}, Hello ${world} ] [ ${a} ${b}, ${x} ${y} ] Non-newline whitespace is never an element or field separator. ### Path expressions Path expressions are used to write out a path through the object graph. They appear in two places; in substitutions, like `${foo.bar}`, and as the keys in objects like `{ foo.bar : 42 }`. Path expressions are syntactically identical to a value concatenation, except that they may not contain substitutions. This means that you can't nest substitutions inside other substitutions, and you can't have substitutions in keys. When concatenating the path expression, any `.` characters outside quoted strings are understood as path separators, while inside quoted strings `.` has no special meaning. So `foo.bar."hello.world"` would be a path with three elements, looking up key `foo`, key `bar`, then key `hello.world`. The main tricky point is that `.` characters in numbers do count as a path separator. When dealing with a number as part of a path expression, it's essential to retain the _original_ string representation of the number as it appeared in the file (rather than converting it back to a string with a generic number-to-string library function). - `10.0foo` is a number then unquoted string `foo` and should be the two-element path with `10` and `0foo` as the elements. - `foo10.0` is an unquoted string with a `.` in it, so this would be a two-element path with `foo10` and `0` as the elements. - `foo"10.0"` is an unquoted then a quoted string which are concatenated, so this is a single-element path. - `1.2.3` is the three-element path with `1`,`2`,`3` Unlike value concatenations, path expressions are _always_ converted to a string, even if they are just a single value. If you have an array or element value consisting of the single value `true`, it's a value concatenation and retains its character as a boolean value. If you have a path expression (in a key or substitution) then it must always be converted to a string, so `true` becomes the string that would be quoted as `"true"`. If a path element is an empty string, it must always be quoted. That is, `a."".b` is a valid path with three elements, and the middle element is an empty string. But `a..b` is invalid and should generate an error. Following the same rule, a path that starts or ends with a `.` is invalid and should generate an error. ### Paths as keys If a key is a path expression with multiple elements, it is expanded to create an object for each path element other than the last. The last path element, combined with the value, becomes a field in the most-nested object. In other words: foo.bar : 42 is equivalent to: foo { bar : 42 } and: foo.bar.baz : 42 is equivalent to: foo { bar { baz : 42 } } and so on. These values are merged in the usual way; which implies that: a.x : 42, a.y : 43 is equivalent to: a { x : 42, y : 43 } Because path expressions work like value concatenations, you can have whitespace in keys: a b c : 42 is equivalent to: "a b c" : 42 Because path expressions are always converted to strings, even single values that would normally have another type become strings. - `true : 42` is `"true" : 42` - `3 : 42` is `"3" : 42` - `3.14 : 42` is `"3" : { "14" : 42 }` As a special rule, the unquoted string `include` may not begin a path expression in a key, because it has a special interpretation (see below). ### Substitutions Substitutions are a way of referring to other parts of the configuration tree. The syntax is `${pathexpression}` or `${?pathexpression}` where the `pathexpression` is a path expression as described above. This path expression has the same syntax that you could use for an object key. The `?` in `${?pathexpression}` must not have whitespace before it; the three characters `${?` must be exactly like that, grouped together. For substitutions which are not found in the configuration tree, implementations may try to resolve them by looking at system environment variables or other external sources of configuration. (More detail on environment variables in a later section.) Substitutions are not parsed inside quoted strings. To get a string containing a substitution, you must use value concatenation with the substitution in the unquoted portion: key : ${animal.favorite} is my favorite animal Or you could quote the non-substitution portion: key : ${animal.favorite}" is my favorite animal" Substitutions are resolved by looking up the path in the configuration. The path begins with the root configuration object, i.e. it is "absolute" rather than "relative." Substitution processing is performed as the last parsing step, so a substitution can look forward in the configuration. If a configuration consists of multiple files, it may even end up retrieving a value from another file. If a key has been specified more than once, the substitution will always evaluate to its latest-assigned value (that is, it will evaluate to the merged object, or the last non-object value that was set, in the entire document being parsed including all included files). If a configuration sets a value to `null` then it should not be looked up in the external source. Unfortunately there is no way to "undo" this in a later configuration file; if you have `{ "HOME" : null }` in a root object, then `${HOME}` will never look at the environment variable. There is no equivalent to JavaScript's `delete` operation in other words. If a substitution does not match any value present in the configuration and is not resolved by an external source, then it is undefined. An undefined substitution with the `${foo}` syntax is invalid and should generate an error. If a substitution with the `${?foo}` syntax is undefined: - if it is the value of an object field then the field should not be created. If the field would have overridden a previously-set value for the same field, then the previous value remains. - if it is an array element then the element should not be added. - if it is part of a value concatenation with another string then it should become an empty string; if part of a value concatenation with an object or array it should become an empty object or array. - `foo : ${?bar}` would avoid creating field `foo` if `bar` is undefined. `foo : ${?bar}${?baz}` would also avoid creating the field if _both_ `bar` and `baz` are undefined. Substitutions are only allowed in field values and array elements (value concatenations), they are not allowed in keys or nested inside other substitutions (path expressions). A substitution is replaced with any value type (number, object, string, array, true, false, null). If the substitution is the only part of a value, then the type is preserved. Otherwise, it is value-concatenated to form a string. #### Self-Referential Substitutions The big picture: - substitutions normally "look forward" and use the final value for their path expression - when this would create a cycle, when possible the cycle must be broken by looking backward only (thus removing one of the substitutions that's a link in the cycle) The idea is to allow a new value for a field to be based on the older value: path : "a:b:c" path : ${path}":d" A _self-referential field_ is one which: - has a substitution, or value concatenation containing a substitution, as its value - where this field value refers to the field being defined, either directly or by referring to one or more other substitutions which eventually point back to the field being defined Examples of self-referential fields: - `a : ${a}` - `a : ${a}bc` - `path : ${path} [ /usr/bin ]` Note that an object or array with a substitution inside it is _not_ considered self-referential for this purpose. The self-referential rules do _not_ apply to: - `a : { b : ${a} }` - `a : [${a}]` These cases are unbreakable cycles that generate an error. (If "looking backward" were allowed for these, something like `a={ x : 42, y : ${a.x} }` would look backward for a nonexistent `a` while resolving `${a.x}`.) A possible implementation is: - substitutions are resolved by looking up paths in a document. Cycles only arise when the lookup document is an ancestor node of the substitution node. - while resolving a potentially self-referential field (any substitution or value concatenation that contains a substitution), remove that field and all fields which override it from the lookup document. The simplest form of this implementation will report a circular reference as missing; in `a : ${a}` you would remove `a : ${a}` while resolving `${a}`, leaving an empty document to look up `${a}` in. You can give a more helpful error message if, rather than simply removing the field, you leave a marker value describing the cycle. Then generate an error if you return to that marker value during resolution. Cycles should be treated the same as a missing value when resolving an optional substitution (i.e. the `${?foo}` syntax). If `${?foo}` refers to itself then it's as if it referred to a nonexistent value. #### The `+=` field separator Fields may have `+=` as a separator rather than `:` or `=`. A field with `+=` transforms into a self-referential array concatenation, like this: a += b becomes: a = ${?a} [b] `+=` appends an element to a previous array. If the previous value was not an array, an error will result just as it would in the long form `a = ${?a} [b]`. Note that the previous value is optional (`${?a}` not `${a}`), which allows `a += b` to be the first mention of `a` in the file (it is not necessary to have `a = []` first). Note: Akka 2.0 (and thus Play 2.0) contains an embedded implementation of the config lib which does not support `+=`. #### Examples of Self-Referential Substitutions In isolation (with no merges involved), a self-referential field is an error because the substitution cannot be resolved: foo : ${foo} // an error When `foo : ${foo}` is merged with an earlier value for `foo`, however, the substitution can be resolved to that earlier value. When merging two objects, the self-reference in the overriding field refers to the overridden field. Say you have: foo : { a : 1 } and then: foo : ${foo} Then `${foo}` resolves to `{ a : 1 }`, the value of the overridden field. It would be an error if these two fields were reversed, so first: foo : ${foo} and then second: foo : { a : 1 } Here the `${foo}` self-reference comes before `foo` has a value, so it is undefined, exactly as if the substitution referenced a path not found in the document. Because `foo : ${foo}` conceptually looks to previous definitions of `foo` for a value, the error should be treated as "undefined" rather than "intractable cycle"; as a result, the optional substitution syntax `${?foo}` does not create a cycle: foo : ${?foo} // this field just disappears silently If a substitution is hidden by a value that could not be merged with it (by a non-object value) then it is never evaluated and no error will be reported. So for example: foo : ${does-not-exist} foo : 42 In this case, no matter what `${does-not-exist}` resolves to, we know `foo` is `42`, so `${does-not-exist}` is never evaluated and there is no error. The same is true for cycles like `foo : ${foo}, foo : 42`, where the initial self-reference must simply be ignored. A self-reference resolves to the value "below" even if it's part of a path expression. So for example: foo : { a : { c : 1 } } foo : ${foo.a} foo : { a : 2 } Here, `${foo.a}` would refer to `{ c : 1 }` rather than `2` and so the final merge would be `{ a : 2, c : 1 }`. Recall that for a field to be self-referential, it must have a substitution or value concatenation as its value. If a field has an object or array value, for example, then it is not self-referential even if there is a reference to the field itself inside that object or array. Implementations must be careful to allow objects to refer to paths within themselves, for example: bar : { foo : 42, baz : ${bar.foo} } Here, if an implementation resolved all substitutions in `bar` as part of resolving the substitution `${bar.foo}`, there would be a cycle. The implementation must only resolve the `foo` field in `bar`, rather than recursing the entire `bar` object. Because there is no inherent cycle here, the substitution must "look forward" (including looking at the field currently being defined). To make this clearer, `bar.baz` would be `43` in: bar : { foo : 42, baz : ${bar.foo} } bar : { foo : 43 } Mutually-referring objects should also work, and are not self-referential (so they look forward): // bar.a should end up as 4 bar : { a : ${foo.d}, b : 1 } bar.b = 3 // foo.c should end up as 3 foo : { c : ${bar.b}, d : 2 } foo.d = 4 Another tricky case is an optional self-reference in a value concatenation, in this example `a` should be `foo` not `foofoo` because the self reference has to "look back" to an undefined `a`: a = ${?a}foo In general, in resolving a substitution the implementation must: - lazy-evaluate the substitution target so there's no "circularity by side effect" - "look forward" and use the final value for the path specified in the substitution - if a cycle results, the implementation must "look back" in the merge stack to try to resolve the cycle - if neither lazy evaluation nor "looking only backward" resolves a cycle, the substitution is missing which is an error unless the `${?foo}` optional-substitution syntax was used. For example, this is not possible to resolve: bar : ${foo} foo : ${bar} A multi-step loop like this should also be detected as invalid: a : ${b} b : ${c} c : ${a} Some cases have undefined behavior because the behavior depends on the order in which two fields are resolved, and that order is not defined. For example: a : 1 b : 2 a : ${b} b : ${a} Implementations are allowed to handle this by setting both `a` and `b` to 1, setting both to `2`, or generating an error. Ideally this situation would generate an error, but that may be difficult to implement. Making the behavior defined would require always working with ordered maps rather than unordered maps, which is too constraining. Implementations only have to track order for duplicate instances of the same field (i.e. merges). Implementations must set both `a` and `b` to the same value in this case, however. In practice this means that all substitutions must be memoized (resolved once, with the result retained). Memoization should be keyed by the substitution "instance" (the specific occurrence of the `${}` expression) rather than by the path inside the `${}` expression, because substitutions may be resolved differently depending on their position in the file. ### Includes #### Include syntax An _include statement_ consists of the unquoted string `include` followed by whitespace and then either: - a single _quoted_ string which is interpreted heuristically as URL, filename, or classpath resource. - `url()`, `file()`, or `classpath()` surrounding a quoted string which is then interpreted as a URL, file, or classpath. The string must be quoted, unlike in CSS. - `required()` surrounding one of the above An include statement can appear in place of an object field. If the unquoted string `include` appears at the start of a path expression where an object key would be expected, then it is not interpreted as a path expression or a key. Instead, the next value must be a _quoted_ string or a quoted string surrounded by `url()`, `file()`, or `classpath()`. This value is the _resource name_. Together, the unquoted `include` and the resource name substitute for an object field syntactically, and are separated from the following object fields or includes by the usual comma (and as usual the comma may be omitted if there's a newline). If an unquoted `include` at the start of a key is followed by anything other than a single quoted string or the `url("")`/`file("")`/`classpath("")` syntax, it is invalid and an error should be generated. There can be any amount of whitespace, including newlines, between the unquoted `include` and the resource name. For `url()` etc., whitespace is allowed inside the parentheses `()` (outside of the quotes). Value concatenation is NOT performed on the "argument" to `include` or `url()` etc. The argument must be a single quoted string. No substitutions are allowed, and the argument may not be an unquoted string or any other kind of value. Unquoted `include` has no special meaning if it is not the start of a key's path expression. It may appear later in the key: # this is valid { foo include : 42 } # equivalent to { "foo include" : 42 } It may appear as an object or array value: { foo : include } # value is the string "include" [ include ] # array of one string "include" You can quote `"include"` if you want a key that starts with the word `"include"`, only unquoted `include` is special: { "include" : 42 } Note: Akka 2.0 (and thus Play 2.0) contains an embedded implementation of the config lib which does not support the `url()`/`file()`/`classpath()` syntax. Only the heuristic `include "foo"` syntax is supported in that version. #### Include semantics: merging An _including file_ contains the include statement and an _included file_ is the one specified in the include statement. (They need not be regular files on a filesystem, but assume they are for the moment.) An included file must contain an object, not an array. This is significant because both JSON and HOCON allow arrays as root values in a document. If an included file contains an array as the root value, it is invalid and an error should be generated. The included file should be parsed, producing a root object. The keys from the root object are conceptually substituted for the include statement in the including file. - If a key in the included object occurred prior to the include statement in the including object, the included key's value overrides or merges with the earlier value, exactly as with duplicate keys found in a single file. - If the including file repeats a key from an earlier-included object, the including file's value would override or merge with the one from the included file. #### Include semantics: substitution Substitutions in included files are looked up at two different paths; first, relative to the root of the included file; second, relative to the root of the including configuration. Recall that substitution happens as a final step, _after_ parsing. It should be done for the entire app's configuration, not for single files in isolation. Therefore, if an included file contains substitutions, they must be "fixed up" to be relative to the app's configuration root. Say for example that the root configuration is this: { a : { include "foo.conf" } } And "foo.conf" might look like this: { x : 10, y : ${x} } If you parsed "foo.conf" in isolation, then `${x}` would evaluate to 10, the value at the path `x`. If you include "foo.conf" in an object at key `a`, however, then it must be fixed up to be `${a.x}` rather than `${x}`. Say that the root configuration redefines `a.x`, like this: { a : { include "foo.conf" } a : { x : 42 } } Then the `${x}` in "foo.conf", which has been fixed up to `${a.x}`, would evaluate to `42` rather than to `10`. Substitution happens _after_ parsing the whole configuration. However, there are plenty of cases where the included file might intend to refer to the application's root config. For example, to get a value from a system property or from the reference configuration. So it's not enough to only look up the "fixed up" path, it's necessary to look up the original path as well. #### Include semantics: missing files and required files By default, if an included file does not exist then the include statement should be silently ignored (as if the included file contained only an empty object). If however an included resource is mandatory then the name of the included resource may be wrapped with `required()`, in which case file parsing will fail with an error if the resource cannot be resolved. The syntax for this is include required("foo.conf") include required(file("foo.conf")) include required(classpath("foo.conf")) include required(url("http://localhost/foo.conf")) Other IO errors probably should not be ignored but implementations will have to make a judgment which IO errors reflect an ignorable missing file, and which reflect a problem to bring to the user's attention. #### Include semantics: file formats and extensions Implementations may support including files in other formats. Those formats must be compatible with the JSON type system, or have some documented mapping to JSON's type system. If an implementation supports multiple formats, then the extension may be omitted from the name of included files: include "foo" If a filename has no extension, the implementation should treat it as a basename and try loading the file with all known extensions. If the file exists with multiple extensions, they should _all_ be loaded and merged together. Files in HOCON format should be parsed last. Files in JSON format should be parsed next-to-last. In short, `include "foo"` might be equivalent to: include "foo.properties" include "foo.json" include "foo.conf" This same extension-based behavior is applied to classpath resources and files. For URLs, a basename without extension is not allowed; only the exact URL specified is used. The format will be chosen based on the Content-Type if available, or by the extension of the path component of the URL if no Content-Type is set. This is true even for file: URLs. #### Include semantics: locating resources A quoted string not surrounded by `url()`, `file()`, `classpath()` must be interpreted heuristically. The heuristic is to treat the quoted string as: - a URL, if the quoted string is a valid URL with a known protocol. - otherwise, a file or other resource "adjacent to" the one being parsed and of the same type as the one being parsed. The meaning of "adjacent to", and the string itself, has to be specified separately for each kind of resource. - On the Java Virtual Machine, if an include statement does not identify a valid URL or an existing resource "adjacent to" the including resource, implementations may wish to fall back to a classpath resource. This allows configurations found in files or URLs to access classpath resources in a natural way. Implementations may vary in the kinds of resources they can include. For resources located on the Java classpath: - included resources are looked up by calling `getResource()` on the same class loader used to look up the including resource. - if the included resource name is absolute (starts with '/') then it should be passed to `getResource()` with the '/' removed. - if the included resource name does not start with '/' then it should have the "directory" of the including resource prepended to it, before passing it to `getResource()`. If the including resource is not absolute (no '/') and has no "parent directory" (is just a single path element), then the included relative resource name should be left as-is. - it would be wrong to use `getResource()` to get a URL and then locate the included name relative to that URL, because a class loader is not required to have a one-to-one mapping between paths in its URLs and the paths it handles in `getResource()`. In other words, the "adjacent to" computation should be done on the resource name not on the resource's URL. For plain files on the filesystem: - if the included file is an absolute path then it should be kept absolute and loaded as such. - if the included file is a relative path, then it should be located relative to the directory containing the including file. The current working directory of the process parsing a file must NOT be used when interpreting included paths. - if the file is not found, fall back to the classpath resource. The classpath resource should not have any package name added in front, it should be relative to the "root"; which means any leading "/" should just be removed (absolute is the same as relative since it's root-relative). The "/" is handled for consistency with including resources from inside other classpath resources, where the resource name may not be root-relative and "/" allows specifying relative to root. URLs: - for files loaded from a URL, "adjacent to" should be based on parsing the URL's path component, replacing the last path element with the included name. - file: URLs should behave in exactly the same way as a plain filename Implementations need not support files, Java resources, or URLs; and they need not support particular URL protocols. However, if they do support them they should do so as described above. Note that at present, if `url()`/`file()`/`classpath()` are specified, the included items are NOT interpreted relative to the including items. Relative-to-including-file paths only work with the heuristic `include "foo.conf"`. This may change in the future. ### Conversion of numerically-indexed objects to arrays In some file formats and contexts, such as Java properties files, there isn't a good way to define arrays. To provide some mechanism for this, implementations should support converting objects with numeric keys into arrays. For example, this object: { "0" : "a", "1" : "b" } could be treated as: [ "a", "b" ] This allows creating an array in a properties file like this: foo.0 = "a" foo.1 = "b" The details: - the conversion should be done lazily when required to avoid a type error, NOT eagerly anytime an object has numeric keys. - the conversion should be done when you would do an automatic type conversion (see the section "Automatic type conversions" below). - the conversion should be done in a concatenation when a list is expected and an object with numeric keys is found. - the conversion should not occur if the object is empty or has no keys which parse as positive integers. - the conversion should ignore any keys which do not parse as positive integers. - the conversion should sort by the integer value of each key and then build the array; if the integer keys are "0" and "2" then the resulting array would have indices "0" and "1", i.e. missing indices in the object are eliminated. ## MIME Type Use "application/hocon" for Content-Type. ## API Recommendations Implementations of HOCON ideally follow certain conventions and work in a predictable way. ### Automatic type conversions If an application asks for a value with a particular type, the implementation should attempt to convert types as follows: - number to string: convert the number into a string representation that would be a valid number in JSON. - boolean to string: should become the string "true" or "false" - string to number: parse the number with the JSON rules - string to boolean: the strings "true", "yes", "on", "false", "no", "off" should be converted to boolean values. It's tempting to support a long list of other ways to write a boolean, but for interoperability and keeping it simple, it's recommended to stick to these six. - string to null: the string `"null"` should be converted to a null value if the application specifically asks for a null value, though there's probably no reason an app would do this. - numerically-indexed object to array: see the section "Conversion of numerically-indexed objects to arrays" above The following type conversions should NOT be performed: - null to anything: If the application asks for a specific type and finds null instead, that should usually result in an error. - object to anything - array to anything - anything to object - anything to array, with the exception of numerically-indexed object to array Converting objects and arrays to and from strings is tempting, but in practical situations raises thorny issues of quoting and double-escaping. ### Units format Implementations may wish to support interpreting a value with some family of units, such as time units or memory size units: `10ms` or `512K`. HOCON does not have an extensible type system and there is no way to add a "duration" type. However, for example, if an application asks for milliseconds, the implementation can try to interpret a value as a milliseconds value. If an API supports this, for each family of units it should define a default unit in the family. For example, the family of duration units might default to milliseconds (see below for details on durations). The implementation should then interpret values as follows: - if the value is a number, it is taken to be a number in the default unit. - if the value is a string, it is taken to be this sequence: - optional whitespace - a number - optional whitespace - an optional unit name consisting only of letters (letters are the Unicode `L*` categories, Java `isLetter()`) - optional whitespace If a string value has no unit name, then it should be interpreted with the default unit, as if it were a number. If a string value has a unit name, that name of course specifies the value's interpretation. ### Duration format Implementations may wish to support a `getMilliseconds()` (and similar for other time units). This can use the general "units format" described above; bare numbers are taken to be in milliseconds already, while strings are parsed as a number plus an optional unit string. The supported unit strings for duration are case sensitive and must be lowercase. Exactly these strings are supported: - `ns`, `nano`, `nanos`, `nanosecond`, `nanoseconds` - `us`, `micro`, `micros`, `microsecond`, `microseconds` - `ms`, `milli`, `millis`, `millisecond`, `milliseconds` - `s`, `second`, `seconds` - `m`, `minute`, `minutes` - `h`, `hour`, `hours` - `d`, `day`, `days` ### Size in bytes format Implementations may wish to support a `getBytes()` returning a size in bytes. This can use the general "units format" described above; bare numbers are taken to be in bytes already, while strings are parsed as a number plus an optional unit string. The one-letter unit strings may be uppercase (note: duration units are always lowercase, so this convention is specific to size units). There is an unfortunate nightmare with size-in-bytes units, that they may be in powers or two or powers of ten. The approach defined by standards bodies appears to differ from common usage, such that following the standard leads to people being confused. Worse, common usage varies based on whether people are talking about RAM or disk sizes, and various existing operating systems and apps do all kinds of different things. See http://en.wikipedia.org/wiki/Binary_prefix#Deviation_between_powers_of_1024_and_powers_of_1000 for examples. It appears impossible to sort this out without causing confusion for someone sometime. For single bytes, exactly these strings are supported: - `B`, `b`, `byte`, `bytes` For powers of ten, exactly these strings are supported: - `kB`, `kilobyte`, `kilobytes` - `MB`, `megabyte`, `megabytes` - `GB`, `gigabyte`, `gigabytes` - `TB`, `terabyte`, `terabytes` - `PB`, `petabyte`, `petabytes` - `EB`, `exabyte`, `exabytes` - `ZB`, `zettabyte`, `zettabytes` - `YB`, `yottabyte`, `yottabytes` For powers of two, exactly these strings are supported: - `K`, `k`, `Ki`, `KiB`, `kibibyte`, `kibibytes` - `M`, `m`, `Mi`, `MiB`, `mebibyte`, `mebibytes` - `G`, `g`, `Gi`, `GiB`, `gibibyte`, `gibibytes` - `T`, `t`, `Ti`, `TiB`, `tebibyte`, `tebibytes` - `P`, `p`, `Pi`, `PiB`, `pebibyte`, `pebibytes` - `E`, `e`, `Ei`, `EiB`, `exbibyte`, `exbibytes` - `Z`, `z`, `Zi`, `ZiB`, `zebibyte`, `zebibytes` - `Y`, `y`, `Yi`, `YiB`, `yobibyte`, `yobibytes` It's very unclear which units the single-character abbreviations ("128K") should go with; some precedents such as `java -Xmx 2G` and the GNU tools such as `ls` map these to powers of two, so this spec copies that. You can certainly find examples of mapping these to powers of ten, though. If you don't like ambiguity, don't use the single-letter abbreviations. Note: any value in zetta/zebi or yotta/yobi will overflow a 64-bit integer, and of course large-enough values in any of the units may overflow. Most real-world APIs and apps will not support byte counts that overflow a 64-bit integer. The huge units are provided just to be complete but probably aren't useful in practice. At least not in 2014. ### Config object merging and file merging It may be useful to offer a method to merge two objects. If such a method is provided, it should work as if the two objects were duplicate values for the same key in the same file. (See the section earlier on duplicate key handling.) As with duplicate keys, an intermediate non-object value "hides" earlier object values. So say you merge three objects in this order: - `{ a : { x : 1 } }` (first priority) - `{ a : 42 }` (fallback) - `{ a : { y : 2 } }` (another fallback) The result would be `{ a : { x : 1 } }`. The two objects are not merged because they are not "adjacent"; the merging is done in pairs, and when `42` is paired with `{ y : 2 }`, `42` simply wins and loses all information about what it overrode. But if you re-ordered like this: - `{ a : { x : 1 } }` (first priority) - `{ a : { y : 2 } }` (fallback) - `{ a : 42 }` (another fallback) Now the result would be `{ a : { x : 1, y : 2 } }` because the two objects are adjacent. This rule for merging objects loaded from different files is _exactly_ the same behavior as for merging duplicate fields in the same file. All merging works the same way. Needless to say, normally it's well-defined whether a config setting is supposed to be a number or an object. This kind of weird pathology where the two are mixed should not be happening. The one place where it matters, though, is that it allows you to "clear" an object and start over by setting it to null and then setting it back to a new object. So this behavior gives people a way to get rid of default fallback values they don't want. ### Java properties mapping It may be useful to merge Java properties data with data loaded from JSON or HOCON. See the Java properties spec here: http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29 Java properties parse as a one-level map from string keys to string values. To convert to HOCON, first split each key on the `.` character, keeping any empty strings (including leading and trailing empty strings). Note that this is _very different_ from parsing a path expression. The key split on `.` is a series of path elements. So the properties key with just `.` is a path with two elements, both of them an empty string. `a.` is a path with two elements, `a` and empty string. (Java's `String.split()` does NOT do what you want for this.) It is impossible to represent a key with a `.` in it in a properties file. If a JSON/HOCON key has a `.` in it, which is possible if the key is quoted, then there is no way to refer to it as a Java property. It is not recommended to name HOCON keys with a `.` in them, since it would be confusing at best in any case. Once you have a path for each value, construct a tree of JSON-style objects with the string value of each property located at that value's path. Values from properties files are _always_ strings, even if they could be parsed as some other type. Implementations should do type conversion if an app asks for an integer, as described in an earlier section. When Java loads a properties file, unfortunately it does not preserve the order of the file. As a result, there is an intractable case where a single key needs to refer to both a parent object and a string value. For example, say the Java properties file has: a=hello a.b=world In this case, `a` needs to be both an object and a string value. The _object_ must always win in this case... the "object wins" rule throws out at most one value (the string) while "string wins" would throw out all values in the object. Unfortunately, when properties files are mapped to the JSON structure, there is no way to access these strings that conflict with objects. The usual rule in HOCON would be that the later assignment in the file wins, rather than "object wins"; but implementing that for Java properties would require implementing a custom Java properties parser, which is surely not worth it and wouldn't help with system properties anyway. ### Conventional configuration files for JVM apps By convention, JVM apps have two parts to their configuration: - the _reference_ config is made up of all resources named `reference.conf` on the classpath, merged in the order they are returned by `ClassLoader.getResources()`; also, system property overrides are applied. - the _application_ config can be loaded from anywhere an application likes, but by default if the application doesn't provide a config it would be loaded from files `application.{conf,json,properties}` on the classpath and then system property overrides are applied. - the reference config may be different for different class loaders, since each jar may provide a `reference.conf` to go with the code in that jar. - a single JVM may have multiple application configs if it has multiple modules or contexts of some kind. The reference config for a given class loader should be merged and resolved first, and may be shared among all application configs in that class loader. Substitutions in the reference config are not affected by any application configs, because the reference config should be resolved by itself. The application config should then be loaded, have the reference config added as a fallback, and have substitutions resolved. This means the application config can refer to the reference config in its substitutions. ### Conventional override by system properties For an application's config, Java system properties _override_ settings found in the configuration file. This supports specifying config options on the command line. ### Substitution fallback to environment variables Recall that if a substitution is not present (not even set to `null`) within a configuration tree, implementations may search for it from external sources. One such source could be environment variables. It's recommended that HOCON keys always use lowercase, because environment variables generally are capitalized. This avoids naming collisions between environment variables and configuration properties. (While on Windows getenv() is generally not case-sensitive, the lookup will be case sensitive all the way until the env variable fallback lookup is reached). See also the notes below on Windows and case sensitivity. An application can explicitly block looking up a substitution in the environment by setting a value in the configuration, with the same name as the environment variable. You could set `HOME : null` in your root object to avoid expanding `${HOME}` from the environment, for example. Environment variables are interpreted as follows: - env variables set to the empty string are kept as such (set to empty string, rather than undefined) - System.getenv throws SecurityException: treated as not present - encoding is handled by Java (System.getenv already returns a Unicode string) - environment variables always become a string value, though if an app asks for another type automatic type conversion would kick in ### hyphen-separated vs. camelCase Config keys are encouraged to be `hyphen-separated` rather than `camelCase`. ## Note on Java properties similarity You can write a HOCON file that looks much like a Java properties file, and many valid Java properties files will also parse as HOCON. However, HOCON is not a Java properties superset and the corner cases work like JSON, not like properties. Differences include but are probably not limited to: - certain characters that can be unquoted in properties files have to be placed in JSON-style double-quoted strings in HOCON - unquoted strings in HOCON do not support escape sequences - unquoted strings in HOCON do not preserve trailing whitespace - multi-line unquoted strings using backslash to continue the line are not allowed in HOCON - in properties files you can omit the value for a key and it's interpreted as an empty string, in HOCON you cannot omit the value - properties files support '!' as a comment character - HOCON allows comments on the same line as a key or value, while properties files only recognize comment characters if they occur as the first character on the line - HOCON interprets `${}` as a substitution ## Note on Windows and case sensitivity of environment variables HOCON's lookup of environment variable values is always case sensitive, but Linux and Windows differ in their handling of case. Linux allows one to define multiple environment variables with the same name but with different case; so both "PATH" and "Path" may be defined simultaneously. HOCON's access to these environment variables on Linux is straightforward; ie just make sure you define all your vars with the required case. Windows is more confusing. Windows environment variables names may contain a mix of upper and lowercase characters, eg "Path", however Windows does not allow one to define multiple instances of the same name but differing in case. Whilst accessing env vars in Windows is case insensitive, accessing env vars in HOCON is case sensitive. So if you know that you HOCON needs "PATH" then you must ensure that the variable is defined as "PATH" rather than some other name such as "Path" or "path". However, Windows does not allow us to change the case of an existing env var; we can't simply redefine the var with an upper case name. The only way to ensure that your environment variables have the desired case is to first undefine all the env vars that you will depend on then redefine them with the required case. For example, the the ambient environment might have this definition ... ``` set Path=A;B;C ``` .. we just don't know. But if the HOCON needs "PATH", then the start script must take a precautionary approach and enforce the necessary case as follows ... ``` set OLDPATH=%PATH% set PATH= set PATH=%OLDPATH% %JAVA_HOME%/bin/java .... ``` You cannot know what ambient environment variables might exist in the ambient environment when your program is invoked, nor what case those definitions might have. Therefore the only safe thing to do is redefine all the vars you rely on as shown above. config-1.3.1/LICENSE-2.0.txt000066400000000000000000000261361277147274600151320ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. config-1.3.1/NEWS.md000066400000000000000000000370201277147274600141020ustar00rootroot00000000000000# 1.3.1: September 24, 2016 - added `include required("foo")` syntax to specify includes that fail if not present - support for more kinds of property in ConfigBeanFactory: * enumerations * optional fields * lists of beans - numbers can now start with a decimal (".33" vs. "0.33"), these are still parsed as strings but Config.getDouble() etc. will convert them to numbers - a few other small bugfixes # 1.3.0: May 8, 2015 - no changes from 1.3.0-M3 - this is an ABI-guaranteed stable release. - 1.3.0 should be ABI-compatible with 1.2.x for most applications, though there are enough changes to corner cases and implementation that some obscure things could break. Please see the notes for the 1.3.0 milestones below for details, especially for 1.3.0-M1. # 1.3.0-M3: April 21, 2015 - this is an ABI-not-guaranteed beta release in advance of 1.3.0. Please see the notes for 1.3.0-M1 below for warnings, caveats, and the bulk of what's changed since 1.2.1. API changes (since 1.3.0-M2): - renamed some methods in the new ConfigDocument for consistency with Config (breaks ABI vs. 1.3.0-M2, but not vs. any stable release) Fixes: - couple of bugfixes in ConfigDocument Thank you to contributors with commits since v1.3.0-M2 tag: - Preben Ingvaldsen # 1.3.0-M2: April 1, 2015 - not in fact an April Fool's joke. Unless it's broken. Then it was. - this is an ABI-not-guaranteed beta release in advance of 1.3.0. Please see the notes for 1.3.0-M1 below for warnings, caveats, and the bulk of what's changed since 1.2.1. - this release churns the internals a good bit since 1.3.0-M1, so would benefit from your testing efforts. New API (since 1.3.0-M1): - added Config.hasPathOrNull - added Config.getIsNull - added parser.ConfigDocument which supports simple load/edit/save on a config file. For now, the only allowed edits are removing/replacing values. This was a major effort (redoing the whole parser), implemented by Preben Ingvaldsen. Fixes: - added missing @since tags to javadoc - fixed obscure bug in converting to camel case when instantiating beans Thank you to contributors with commits since v1.3.0-M1 tag: - Glen Ford - Jay McCure - Preben Ingvaldsen # 1.3.0-M1: March 6, 2015 - this is an ABI-not-guaranteed beta release in advance of 1.3.0. The public, documented ABI should not be broken vs. 1.2.0; however, there are enough changes that something certainly could break, and some obscure corner cases have changed semantics and that could bite some people. Changes that are most likely to break something: - now built with Java 8 and requires Java 8 - if you were relying on the order of key iteration in a config, note that Java 8 changed the iteration order for hashes and that includes `Config` and `ConfigObject` - several correctness fixes to resolving substitutions (the `${foo}` syntax). These should only matter in weird corner cases, but some people did encounter the problems such as in #177. - throw an exception if a size-in-bytes values are out of Long range #170 - two adjacent undefined concatenation elements (like `${?foo}${?bar}`) now become undefined instead of throwing an exception - when rendering a path that starts with a digit, don't put quotes around it - set the Accept header on http requests when loading config from a URL - when getting a 404 from a URL, treat it as a missing file (silently ignore) instead of throwing exception. Other error codes will still throw an exception. - ConfigParseOptions.prependIncluder/appendIncluder always throw when given a null includer, formerly they only threw sometimes New API: - `ConfigBeanFactory` will auto-fill a JavaBean from a `Config` - it is now possible to create a `ConfigOrigin` using `ConfigOriginFactory` and to modify origins on values using `ConfigValue.withOrigin` - `Config.getMemorySize` returns a `ConfigMemorySize` - `Config.getDuration` returns a `java.time.Duration` - the existing `ConfigValueFactory.fromAnyRef` and related methods now pass through a `ConfigValue` instead of throwing an exception - `ConfigFactory.defaultApplication()` returns the default `Config` used by `ConfigFactory.load()` in between `defaultReference()` and `defaultOverrides()`, leaving `ConfigFactory.load()` as a trivial convenience API that uses no internal magic. Improvements: - allow duration abbreviations "nanos", "millis", "micros" - Config.hasPath is now _much_ faster, so if you were caching to avoid this you may be able to stop - new debug option -Dconfig.trace=substitutions explains how `${foo}` references are being resolved - sort numeric keys in numeric order when rendering - allow whitespace in between two substitutions referring to objects or lists when concatenating them, so `${foo} ${bar}` and `${foo}${bar}` are now the same if foo and bar are objects or lists. - better error messages for problems loading resources from classpath, now we show the jar URL that failed - even more test coverage! - lots of minor javadoc fixes - method names in javadoc now link to github source Bug fixes: - fix "allow unresolved" behavior for unresolved list elements - class loaders are cached with a WeakReference to avoid leaks #171 - create valid output for values with multiline descriptions #239 - `-Dsun.io.serialization.extendedDebugInfo=true` no longer explodes due to calling toString on an internal object, #176 Thank you to contributors with commits since v1.2.1 tag: - Alex Wei - Andrey Zaytsev - Ben Jackman - Ben McCann - Chris Martin - Dale Wijnand - Francois Dang Ngoc - ian - KAWACHI Takashi - Kornel Kielczewski - Lunfu Zhong - Michel Daviot - Paul Phillips - Pavel Yakunin - Preben Ingvaldsen - verbeto - Wu Zhenwei # 1.2.1: May 2, 2014 - bugfix release, no API additions or changes - fix resolving substitutions in include statements nested inside objects - when rendering an object to a string, sort the fields - handle unresolved substitutions in value concatenations - make ConfigOrigin.comments unmodifiable - when using '+=' or 'include' inside a list, throw an exception instead of generating a wrong result - when context class loader is unset throw a more helpful exception than NullPointerException - ignore non-string values in a Properties object # 1.2.0: January 15, 2014 - new stable ABI release (binary compatible with 1.0.x; a few new APIs) - new API ConfigResolveOptions.setAllowUnresolved lets you partially-resolve a Config - new API Config.isResolved lets you check on resolution status - new API Config.resolveWith lets you source substitutions from somewhere other than the Config itself - new API Config.getDuration() replaces getMilliseconds and getNanoseconds - if -Dconfig.file, -Dconfig.resource, -Dconfig.url refer to a nonexistent file, resource, or url it is now an error rather than silently loading an empty configuration. - quite a few bugfixes # 1.1.0-4dd6c85cab1ef1a4415abb74704d60e57497b7b8: January 8, 2014 - remove junk in POM caused by broken local configuration - build jar using Java 1.6 (and enforce this in build) - change getDuration to return unboxed long instead of boxed - API documentation improvements http://typesafehub.github.io/config/latest/api/ # 1.1.0-9f31d6308e7ebbc3d7904b64ebb9f61f7e22a968: January 6, 2014 - this is a snapshot/preview with API/ABI additions. *New* API since 1.0.x is NOT guaranteed to remain compatible for now since the purpose of this release is to test it. This release is supposed to be ABI-compatible with 1.0.x however. - snapshots now use the git hash they are based on in the version, instead of SNAPSHOT, so they are a stable reference if you want to test them out. - if -Dconfig.file, -Dconfig.resource, -Dconfig.url refer to a nonexistent file, resource, or url it is now an error rather than silently loading an empty configuration. - new API Config.getDuration() replaces getMilliseconds and getNanoseconds. (should it return `long` instead of `Long` even though it's been in git for a while? weigh in at https://github.com/typesafehub/config/issues/119 ) - new API ConfigResolveOptions.setAllowUnresolved lets you partially-resolve a Config - new API Config.isResolved lets you check on resolution status - new API Config.resolveWith lets you source substitutions from somewhere other than the Config itself - compiled with debug symbols - add -Dconfig.trace=loads feature to trace loaded files and failures - improvements to ConfigObject render() formatting so you can print out a config in a prettier way - attempt to honor Content-Type when loading from a URL - a fair list of corner case bugfixes # 1.0.2: July 3, 2013 - ignore byte-order mark (BOM), treating it as whitespace - very minor docs/build improvements # 1.0.1: May 19, 2013 - when an array is requested and an object found, try to convert the object to an array if the object has numeric keys in it. This is intended to support `-Dfoo.0=bar, -Dfoo.1=baz` which would create `foo : { "0" : "bar", "1" : "baz" }`; which in turn could now be treated as if it were `foo : ["bar","baz"]`. This is useful for creating array values on the command line using Java system properties. - fix a ConcurrentModificationException if an app modified system properties while we were trying to parse them. - fix line numbering in error messages for newlines within triple quotes. # 1.0.0: October 15, 2012 - no changes from 0.6.0. ABI now guaranteed for 1.0.x series. # 0.6.0: October 10, 2012 - add ConfigRenderOptions.setJson which can be used to enable or disable the use of HOCON extensions (other than comments, which remain separately-controlled). Right now setJson(false) will result in a somewhat prettier rendering using extensions. - add ConfigFactory.invalidateCaches() to support reloading system properties (mostly this is intended for use in unit tests). - make ConfigException serializable, in case you have some logging system or similar that relies on that. Serialization of ConfigException is not guaranteed to be compatible across releases. # 0.5.2: September 6, 2012 - add versions of ConfigFactory.load() which let you specify ConfigParseOptions and ConfigResolveOptions. # 0.5.0: July 5, 2012 - triple-quoted strings as in Python or Scala - obscure backward incompatibilities: - `""""` previously parsed as two empty strings concatenated into a single empty string, now it parses as an unterminated triple-quoted string. - a triple-quoted string like `"""\n"""` previously parsed as an empty string, a string with one newline character, and another empty string, all concatenated into a single string. Now it parses as a string with two characters (backslash and lowercase "n"). - in short you could have two adjacent quoted strings before, where one was an empty string, and now you can't. As far as I know, the empty string was always worthless in this case and can just be removed. - added methods atPath() and atKey() to ConfigValue, to wrap the value into a Config - added method withValue() to Config and ConfigObject, to add a value at a given path or key # 0.4.1: May 22, 2012 - publish as OSGi bundle # 0.4.0: April 12, 2012 - this is **rolling toward 1.0** and should be pretty much feature-complete. - this version is published on **Maven central** so you need to update your dependency coordinates - the **serialization format has changed** to one that's extensible and lets the library evolve without breaking serialization all the time. The new format is also much more compact. However, this change is incompatible with old serializations, if you rely on that. The hope is to avoid serialization breakage in the future now that the format is not the default Java one (which was a direct dump of all the implementation details). - **serializing an unresolved Config** (one that hasn't had resolve() called on it) is no longer supported, you will get NotSerializableException if you try. - ConfigValue.render() now supports ConfigRenderOptions which means you can get a **no-whitespace no-comments plain JSON rendering** of a ConfigValue - supports **self-referential substitutions**, such as `path=${path}":/bin"`, by "looking backward" to the previous value of `path` - supports **concatenating arrays and merging objects within a single value**. So you can do `path=${path} [ "/bin" ]` for example. See README and spec for more details. - supports **array append** `+=` where `path+="/bin"` expands to `path=${?path} [ "/bin" ]` - supports **specifying type of include** `include url("http://example.com/")`, `include file("/my/file.conf")`, and `include classpath("whatever")`. This syntax forces treatment as URL, file, or classpath resource. - supports **including URLs** `include "http://example.com/whatever.conf"` (if an include is a valid URL, it's loaded as such). This is incompatible with prior versions, if you have a filename that is also a valid URL, it would have loaded previously but now it will not. Use the `include file("")` syntax to force treatment as a file. - **class loaders are now recursively inherited** through include statements; previously, even if you set a custom class loader when parsing a file, it would not be used for parsing a classpath resource included from the file. - parseString() and parseReader() now support include statements in the parsed string or reader - in -Dconfig.resource=name, name can start with a "/" or not, doesn't matter - if you implement ConfigIncluder, you should most likely also implement ConfigIncluderFile, ConfigIncluderURL, and ConfigIncluderClasspath. You should also use ConfigIncludeContext.parseOptions() if appropriate. - cycles in include statements (self-includes) are now detected and result in a nicer error instead of stack overflow - since 0.3.0, there is an obscure incompatible semantic change in that self-referential substitutions where the cycle could be broken by partially resolving the object now "look backward" and may fail to resolve. This is not incompatible with the version included in Play/Akka 2.0 because in that version this obscure case just threw an exception. But in 0.3.0 there were cases that worked that now work differently. You are very unlikely to be affected by this. - Play/Akka 2.0 do not and will not have the new stuff in this version due to the serialization break, they will update next time they bump their ABI. # 0.3.1: August 6, 2012 - 0.3.1 is a backport of the "-Dconfig.resource=name can start with a /" fix to 0.3.0 - 0.3.1 was published on Maven Central while 0.3.0 and earlier are only on Typesafe's repository - 0.3.1 is mostly intended for use by Akka 2.0.x (and therefore Play 2.0.x), everyone else should use a higher version number # 0.3.0: March 1, 2012 - ConfigFactory methods now use the thread's context class loader by default, and have overloads so you can specify a class loader. Because jars may come with "reference.conf" in the jar, a config must always be loaded with the same class loader as the jar using the config. - ConfigValue instances are now serializable - new methods ConfigObject.withoutKey, ConfigObject.withOnlyKey, Config.withoutPath, Config.withOnlyPath allow subsetting configs more easily. - better handle complex interdependent substitutions (the `${foo}` syntax) without getting confused; just about anything that makes conceptual sense should now work. Only inherently circular config files should fail. - some minor documentation fixes. config-1.3.1/README.md000066400000000000000000000775461277147274600143040ustar00rootroot00000000000000Configuration library for JVM languages. [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.typesafe/config/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.typesafe/config) [![Build Status](https://travis-ci.org/typesafehub/config.svg?branch=master)](https://travis-ci.org/typesafehub/config) If you have questions or are working on a pull request or just curious, please feel welcome to join the chat room: [![Join chat https://gitter.im/typesafehub/config](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/typesafehub/config?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Overview - implemented in plain Java with no dependencies - supports files in three formats: Java properties, JSON, and a human-friendly JSON superset - merges multiple files across all formats - can load from files, URLs, or classpath - good support for "nesting" (treat any subtree of the config the same as the whole config) - users can override the config with Java system properties, `java -Dmyapp.foo.bar=10` - supports configuring an app, with its framework and libraries, all from a single file such as `application.conf` - parses duration and size settings, "512k" or "10 seconds" - converts types, so if you ask for a boolean and the value is the string "yes", or you ask for a float and the value is an int, it will figure it out. - JSON superset features: - comments - includes - substitutions (`"foo" : ${bar}`, `"foo" : Hello ${who}`) - properties-like notation (`a.b=c`) - less noisy, more lenient syntax - substitute environment variables (`logdir=${HOME}/logs`) - API based on immutable `Config` instances, for thread safety and easy reasoning about config transformations - extensive test coverage This library limits itself to config files. If you want to load config from a database or something, you would need to write some custom code. The library has nice support for merging configurations so if you build one from a custom source it's easy to merge it in. **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [Essential Information](#essential-information) - [License](#license) - [Binary Releases](#binary-releases) - [Release Notes](#release-notes) - [API docs](#api-docs) - [Bugs and Patches](#bugs-and-patches) - [Build](#build) - [Using the Library](#using-the-library) - [API Example](#api-example) - [Longer Examples](#longer-examples) - [Immutability](#immutability) - [Schemas and Validation](#schemas-and-validation) - [Standard behavior](#standard-behavior) - [Note about resolving substitutions in `reference.conf` and `application.conf`](#note-about-resolving-substitutions-in-referenceconf-and-applicationconf) - [Merging config trees](#merging-config-trees) - [How to handle defaults](#how-to-handle-defaults) - [Understanding `Config` and `ConfigObject`](#understanding-config-and-configobject) - [ConfigBeanFactory](#configbeanfactory) - [Using HOCON, the JSON Superset](#using-hocon-the-json-superset) - [Features of HOCON](#features-of-hocon) - [Examples of HOCON](#examples-of-hocon) - [Uses of Substitutions](#uses-of-substitutions) - [Factor out common values](#factor-out-common-values) - [Inheritance](#inheritance) - [Optional system or env variable overrides](#optional-system-or-env-variable-overrides) - [Concatenation](#concatenation) - [`reference.conf` can't refer to `application.conf`](#referenceconf-cant-refer-to-applicationconf) - [Miscellaneous Notes](#miscellaneous-notes) - [Debugging Your Configuration](#debugging-your-configuration) - [Supports Java 8 and Later](#supports-java-8-and-later) - [Rationale for Supported File Formats](#rationale-for-supported-file-formats) - [Other APIs (Wrappers, Ports and Utilities)](#other-apis-wrappers-ports-and-utilities) - [Guice integration](#guice-integration) - [Java (yep!) wrappers for the Java library](#java-yep-wrappers-for-the-java-library) - [Scala wrappers for the Java library](#scala-wrappers-for-the-java-library) - [Clojure wrappers for the Java library](#clojure-wrappers-for-the-java-library) - [Scala port](#scala-port) - [Ruby port](#ruby-port) - [Puppet module](#puppet-module) - [Python port](#python-port) - [C++ port](#c-port) - [Linting tool](#linting-tool) ## Essential Information ### License The license is Apache 2.0, see LICENSE-2.0.txt. ### Binary Releases Version 1.2.1 and earlier were built for Java 6, while newer versions (1.3.0 and above) will be built for Java 8. You can find published releases on Maven Central. com.typesafe config 1.3.0 sbt dependency: libraryDependencies += "com.typesafe" % "config" % "1.3.0" Link for direct download if you don't use a dependency manager: - http://central.maven.org/maven2/com/typesafe/config/ ### Release Notes Please see NEWS.md in this directory, https://github.com/typesafehub/config/blob/master/NEWS.md ### API docs - Online: http://typesafehub.github.com/config/latest/api/ - also published in jar form - consider reading this README first for an intro - for questions about the `.conf` file format, read [HOCON.md](https://github.com/typesafehub/config/blob/master/HOCON.md) in this directory ### Bugs and Patches Report bugs to the GitHub issue tracker. Send patches as pull requests on GitHub. Before we can accept pull requests, you will need to agree to the Typesafe Contributor License Agreement online, using your GitHub account - it takes 30 seconds. You can do this at http://www.typesafe.com/contribute/cla Please see [CONTRIBUTING](https://github.com/typesafehub/config/blob/master/CONTRIBUTING.md) for more including how to make a release. ### Build The build uses sbt and the tests are written in Scala; however, the library itself is plain Java and the published jar has no Scala dependency. ## Using the Library ### API Example import com.typesafe.config.ConfigFactory Config conf = ConfigFactory.load(); int bar1 = conf.getInt("foo.bar"); Config foo = conf.getConfig("foo"); int bar2 = foo.getInt("bar"); ### Longer Examples See the examples in the `examples/` [directory](https://github.com/typesafehub/config/tree/master/examples). You can run these from the sbt console with the commands `project config-simple-app-java` and then `run`. In brief, as shown in the examples: - libraries should use a `Config` instance provided by the app, if any, and use `ConfigFactory.load()` if no special `Config` is provided. Libraries should put their defaults in a `reference.conf` on the classpath. - apps can create a `Config` however they want (`ConfigFactory.load()` is easiest and least-surprising), then provide it to their libraries. A `Config` can be created with the parser methods in `ConfigFactory` or built up from any file format or data source you like with the methods in `ConfigValueFactory`. ### Immutability Objects are immutable, so methods on `Config` which transform the configuration return a new `Config`. Other types such as `ConfigParseOptions`, `ConfigResolveOptions`, `ConfigObject`, etc. are also immutable. See the [API docs](http://typesafehub.github.com/config/latest/api/) for details of course. ### Schemas and Validation There isn't a schema language or anything like that. However, two suggested tools are: - use the [checkValid() method](http://typesafehub.github.com/config/latest/api/com/typesafe/config/Config.html#checkValid%28com.typesafe.config.Config,%20java.lang.String...%29) - access your config through a Settings class with a field for each setting, and instantiate it on startup (immediately throwing an exception if any settings are missing) In Scala, a Settings class might look like: class Settings(config: Config) { // validate vs. reference.conf config.checkValid(ConfigFactory.defaultReference(), "simple-lib") // non-lazy fields, we want all exceptions at construct time val foo = config.getString("simple-lib.foo") val bar = config.getInt("simple-lib.bar") } See the examples/ directory for a full compilable program using this pattern. ### Standard behavior The convenience method `ConfigFactory.load()` loads the following (first-listed are higher priority): - system properties - `application.conf` (all resources on classpath with this name) - `application.json` (all resources on classpath with this name) - `application.properties` (all resources on classpath with this name) - `reference.conf` (all resources on classpath with this name) The idea is that libraries and frameworks should ship with a `reference.conf` in their jar. Applications should provide an `application.conf`, or if they want to create multiple configurations in a single JVM, they could use `ConfigFactory.load("myapp")` to load their own `myapp.conf`. (Applications _can_ provide a `reference.conf` also if they want, but you may not find it necessary to separate it from `application.conf`.) Libraries and frameworks should default to `ConfigFactory.load()` if the application does not provide a custom `Config` object. This way, libraries will see configuration from `application.conf` and users can configure the whole app, with its libraries, in a single `application.conf` file. Libraries and frameworks should also allow the application to provide a custom `Config` object to be used instead of the default, in case the application needs multiple configurations in one JVM or wants to load extra config files from somewhere. The library examples in `examples/` show how to accept a custom config while defaulting to `ConfigFactory.load()`. For applications using `application.{conf,json,properties}`, system properties can be used to force a different config source: - `config.resource` specifies a resource name - not a basename, i.e. `application.conf` not `application` - `config.file` specifies a filesystem path, again it should include the extension, not be a basename - `config.url` specifies a URL These system properties specify a _replacement_ for `application.{conf,json,properties}`, not an addition. They only affect apps using the default `ConfigFactory.load()` configuration. In the replacement config file, you can use `include "application"` to include the original default config file; after the include statement you could go on to override certain settings. If you set `config.resource`, `config.file`, or `config.url` on-the-fly from inside your program (for example with `System.setProperty()`), be aware that `ConfigFactory` has some internal caches and may not see new values for system properties. Use `ConfigFactory.invalidateCaches()` to force-reload system properties. #### Note about resolving substitutions in `reference.conf` and `application.conf` The substitution syntax `${foo.bar}` will be resolved twice. First, all the `reference.conf` files are merged and then the result gets resolved. Second, all the `application.conf` are layered over the `reference.conf` and the result of that gets resolved again. The implication of this is that the `reference.conf` stack has to be self-contained; you can't leave an undefined value `${foo.bar}` to be provided by `application.conf`, or refer to `${foo.bar}` in a way that you want to allow `application.conf` to override. However, `application.conf` can refer to a `${foo.bar}` in `reference.conf`. This can be frustrating at times, but possible workarounds include: * putting an `application.conf` in a library jar, alongside the `reference.conf`, with values intended for later resolution. * putting some logic in code instead of building up values in the config itself. ### Merging config trees Any two Config objects can be merged with an associative operation called `withFallback`, like `merged = firstConfig.withFallback(secondConfig)`. The `withFallback` operation is used inside the library to merge duplicate keys in the same file and to merge multiple files. `ConfigFactory.load()` uses it to stack system properties over `application.conf` over `reference.conf`. You can also use `withFallback` to merge in some hardcoded values, or to "lift" a subtree up to the root of the configuration; say you have something like: foo=42 dev.foo=57 prod.foo=10 Then you could code something like: Config devConfig = originalConfig .getConfig("dev") .withFallback(originalConfig) There are lots of ways to use `withFallback`. ### How to handle defaults Many other configuration APIs allow you to provide a default to the getter methods, like this: boolean getBoolean(String path, boolean fallback) Here, if the path has no setting, the fallback would be returned. An API could also return `null` for unset values, so you would check for `null`: // returns null on unset, check for null and fall back Boolean getBoolean(String path) The methods on the `Config` interface do NOT do this, for two major reasons: 1. If you use a config setting in two places, the default fallback value gets cut-and-pasted and typically out of sync. This can result in Very Evil Bugs. 2. If the getter returns `null` (or `None`, in Scala) then every time you get a setting you have to write handling code for `null`/`None` and that code will almost always just throw an exception. Perhaps more commonly, people forget to check for `null` at all, so missing settings result in `NullPointerException`. For most situations, failure to have a setting is simply a bug to fix (in either code or the deployment environment). Therefore, if a setting is unset, by default the getters on the `Config` interface throw an exception. If you want to allow a setting to be missing from `application.conf` in a particular case, then here are some options: 1. Set it in a `reference.conf` included in your library or application jar, so there's a default value. 2. Use the `Config.hasPath()` method to check in advance whether the path exists (rather than checking for `null`/`None` after as you might in other APIs). 3. Catch and handle `ConfigException.Missing`. NOTE: using an exception for control flow like this is much slower than using `Config.hasPath()`; the JVM has to do a lot of work to throw an exception. 4. In your initialization code, generate a `Config` with your defaults in it (using something like `ConfigFactory.parseMap()`) then fold that default config into your loaded config using `withFallback()`, and use the combined config in your program. "Inlining" your reference config in the code like this is probably less convenient than using a `reference.conf` file, but there may be reasons to do it. 5. Use `Config.root()` to get the `ConfigObject` for the `Config`; `ConfigObject` implements `java.util.Map` and the `get()` method on `Map` returns null for missing keys. See the API docs for more detail on `Config` vs. `ConfigObject`. 6. Set the setting to `null` in `reference.conf`, then use `Config.getIsNull` and `Config.hasPathOrNull` to handle `null` in a special way while still throwing an exception if the setting is entirely absent. The *recommended* path (for most cases, in most apps) is that you require all settings to be present in either `reference.conf` or `application.conf` and allow `ConfigException.Missing` to be thrown if they are not. That's the design intent of the `Config` API design. Consider the "Settings class" pattern with `checkValid()` to verify that you have all settings when you initialize the app. See the [Schemas and Validation](#schemas-and-validation) section of this README for more details on this pattern. **If you do need a setting to be optional**: checking `hasPath()` in advance should be the same amount of code (in Java) as checking for `null` afterward, without the risk of `NullPointerException` when you forget. In Scala, you could write an enrichment class like this to use the idiomatic `Option` syntax: ```scala implicit class RichConfig(val underlying: Config) extends AnyVal { def getOptionalBoolean(path: String): Option[Boolean] = if (underlying.hasPath(path)) { Some(underlying.getBoolean(path)) } else { None } } ``` Since this library is a Java library it doesn't come with that out of the box, of course. It is understood that sometimes defaults in code make sense. For example, if your configuration lets users invent new sections, you may not have all paths up front and may be unable to set up defaults in `reference.conf` for dynamic paths. The design intent of `Config` isn't to *prohibit* inline defaults, but simply to recognize that it seems to be the 10% case (rather than the 90% case). Even in cases where dynamic defaults are needed, you may find that using `withFallback()` to build a complete nothing-missing `Config` in one central place in your code keeps things tidy. Whatever you do, please remember not to cut-and-paste default values into multiple places in your code. You have been warned! :-) ### Understanding `Config` and `ConfigObject` To read and modify configuration, you'll use the [Config](http://typesafehub.github.io/config/latest/api/com/typesafe/config/Config.html) interface. A `Config` looks at a JSON-equivalent data structure as a one-level map from paths to values. So if your JSON looks like this: ``` "foo" : { "bar" : 42 "baz" : 43 } ``` Using the `Config` interface, you could write `conf.getInt("foo.bar")`. The `foo.bar` string is called a _path expression_ ([HOCON.md](https://github.com/typesafehub/config/blob/master/HOCON.md) has the syntax details for these expressions). Iterating over this `Config`, you would get two entries; `"foo.bar" : 42` and `"foo.baz" : 43`. When iterating a `Config` you will not find nested `Config` (because everything gets flattened into one level). When looking at a JSON tree as a `Config`, `null` values are treated as if they were missing. Iterating over a `Config` will skip `null` values. You can also look at a `Config` in the way most JSON APIs would, through the [ConfigObject](http://typesafehub.github.io/config/latest/api/com/typesafe/config/ConfigObject.html) interface. This interface represents an object node in the JSON tree. `ConfigObject` instances come in multi-level trees, and the keys do not have any syntax (they are just strings, not path expressions). Iterating over the above example as a `ConfigObject`, you would get one entry `"foo" : { "bar" : 42, "baz" : 43 }`, where the value at `"foo"` is another nested `ConfigObject`. In `ConfigObject`, `null` values are visible (distinct from missing values), just as they are in JSON. `ConfigObject` is a subtype of [ConfigValue](http://typesafehub.github.io/config/latest/api/com/typesafe/config/ConfigValue.html), where the other subtypes are the other JSON types (list, string, number, boolean, null). `Config` and `ConfigObject` are two ways to look at the same internal data structure, and you can convert between them for free using [Config.root()](http://typesafehub.github.io/config/latest/api/com/typesafe/config/Config.html#root%28%29) and [ConfigObject.toConfig()](http://typesafehub.github.io/config/latest/api/com/typesafe/config/ConfigObject.html#toConfig%28%29). ### ConfigBeanFactory As of version 1.3.0, if you have a Java object that follows JavaBean conventions (zero-args constructor, getters and setters), you can automatically initialize it from a `Config`. Use `ConfigBeanFactory.create(config.getConfig("subtree-that-matches-bean"), MyBean.class)` to do this. Creating a bean from a `Config` automatically validates that the config matches the bean's implied schema. Bean fields can be primitive types, typed lists such as `List`, `java.time.Duration`, `ConfigMemorySize`, or even a raw `Config`, `ConfigObject`, or `ConfigValue` (if you'd like to deal with a particular value manually). ## Using HOCON, the JSON Superset The JSON superset is called "Human-Optimized Config Object Notation" or HOCON, and files use the suffix `.conf`. See [HOCON.md](https://github.com/typesafehub/config/blob/master/HOCON.md) in this directory for more detail. After processing a `.conf` file, the result is always just a JSON tree that you could have written (less conveniently) in JSON. ### Features of HOCON - Comments, with `#` or `//` - Allow omitting the `{}` around a root object - Allow `=` as a synonym for `:` - Allow omitting the `=` or `:` before a `{` so `foo { a : 42 }` - Allow omitting commas as long as there's a newline - Allow trailing commas after last element in objects and arrays - Allow unquoted strings for keys and values - Unquoted keys can use dot-notation for nested objects, `foo.bar=42` means `foo { bar : 42 }` - Duplicate keys are allowed; later values override earlier, except for object-valued keys where the two objects are merged recursively - `include` feature merges root object in another file into current object, so `foo { include "bar.json" }` merges keys in `bar.json` into the object `foo` - include with no file extension includes any of `.conf`, `.json`, `.properties` - you can include files, URLs, or classpath resources; use `include url("http://example.com")` or `file()` or `classpath()` syntax to force the type, or use just `include "whatever"` to have the library do what you probably mean (Note: `url()`/`file()`/`classpath()` syntax is not supported in Play/Akka 2.0, only in later releases.) - substitutions `foo : ${a.b}` sets key `foo` to the same value as the `b` field in the `a` object - substitutions concatenate into unquoted strings, `foo : the quick ${colors.fox} jumped` - substitutions fall back to environment variables if they don't resolve in the config itself, so `${HOME}` would work as you expect. Also, most configs have system properties merged in so you could use `${user.home}`. - substitutions normally cause an error if unresolved, but there is a syntax `${?a.b}` to permit them to be missing. - `+=` syntax to append elements to arrays, `path += "/bin"` - multi-line strings with triple quotes as in Python or Scala ### Examples of HOCON All of these are valid HOCON. Start with valid JSON: { "foo" : { "bar" : 10, "baz" : 12 } } Drop root braces: "foo" : { "bar" : 10, "baz" : 12 } Drop quotes: foo : { bar : 10, baz : 12 } Use `=` and omit it before `{`: foo { bar = 10, baz = 12 } Remove commas: foo { bar = 10 baz = 12 } Use dotted notation for unquoted keys: foo.bar=10 foo.baz=12 Put the dotted-notation fields on a single line: foo.bar=10, foo.baz=12 The syntax is well-defined (including handling of whitespace and escaping). But it handles many reasonable ways you might want to format the file. Note that while you can write HOCON that looks a lot like a Java properties file (and many properties files will parse as HOCON), the details of escaping, whitespace handling, comments, and so forth are more like JSON. The spec (see HOCON.md in this directory) has some more detailed notes on this topic. ### Uses of Substitutions The `${foo.bar}` substitution feature lets you avoid cut-and-paste in some nice ways. #### Factor out common values This is the obvious use, standard-timeout = 10ms foo.timeout = ${standard-timeout} bar.timeout = ${standard-timeout} #### Inheritance If you duplicate a field with an object value, then the objects are merged with last-one-wins. So: foo = { a : 42, c : 5 } foo = { b : 43, c : 6 } means the same as: foo = { a : 42, b : 43, c : 6 } You can take advantage of this for "inheritance": data-center-generic = { cluster-size = 6 } data-center-east = ${data-center-generic} data-center-east = { name = "east" } data-center-west = ${data-center-generic} data-center-west = { name = "west", cluster-size = 8 } Using `include` statements you could split this across multiple files, too. If you put two objects next to each other (close brace of the first on the same line with open brace of the second), they are merged, so a shorter way to write the above "inheritance" example would be: data-center-generic = { cluster-size = 6 } data-center-east = ${data-center-generic} { name = "east" } data-center-west = ${data-center-generic} { name = "west", cluster-size = 8 } #### Optional system or env variable overrides In default uses of the library, exact-match system properties already override the corresponding config properties. However, you can add your own overrides, or allow environment variables to override, using the `${?foo}` substitution syntax. basedir = "/whatever/whatever" basedir = ${?FORCED_BASEDIR} Here, the override field `basedir = ${?FORCED_BASEDIR}` simply vanishes if there's no value for `FORCED_BASEDIR`, but if you set an environment variable `FORCED_BASEDIR` for example, it would be used. A natural extension of this idea is to support several different environment variable names or system property names, if you aren't sure which one will exist in the target environment. Object fields and array elements with a `${?foo}` substitution value just disappear if the substitution is not found: // this array could have one or two elements path = [ "a", ${?OPTIONAL_A} ] ### Concatenation Values _on the same line_ are concatenated (for strings and arrays) or merged (for objects). This is why unquoted strings work, here the number `42` and the string `foo` are concatenated into a string `42 foo`: key : 42 foo When concatenating values into a string, leading and trailing whitespace is stripped but whitespace between values is kept. Unquoted strings also support substitutions of course: tasks-url : ${base-url}/tasks A concatenation can refer to earlier values of the same field: path : "/bin" path : ${path}":/usr/bin" Arrays can be concatenated as well: path : [ "/bin" ] path : ${path} [ "/usr/bin" ] There is a shorthand for appending to arrays: // equivalent to: path = ${?path} [ "/usr/bin" ] path += "/usr/bin" To prepend or insert into an array, there is no shorthand. When objects are "concatenated," they are merged, so object concatenation is just a shorthand for defining the same object twice. The long way (mentioned earlier) is: data-center-generic = { cluster-size = 6 } data-center-east = ${data-center-generic} data-center-east = { name = "east" } The concatenation-style shortcut is: data-center-generic = { cluster-size = 6 } data-center-east = ${data-center-generic} { name = "east" } When concatenating objects and arrays, newlines are allowed _inside_ each object or array, but not between them. Non-newline whitespace is never a field or element separator. So `[ 1 2 3 4 ]` is an array with one unquoted string element `"1 2 3 4"`. To get an array of four numbers you need either commas or newlines separating the numbers. See the spec for full details on concatenation. Note: Play/Akka 2.0 have an earlier version that supports string concatenation, but not object/array concatenation. `+=` does not work in Play/Akka 2.0 either. Post-2.0 versions support these features. ### `reference.conf` can't refer to `application.conf` Please see this earlier section; all `reference.conf` have substitutions resolved first, without `application.conf` in the stack, so the reference stack has to be self-contained. ## Miscellaneous Notes ### Debugging Your Configuration If you have trouble with your configuration, some useful tips. - Set the Java system property `-Dconfig.trace=loads` to get output on stderr describing each file that is loaded. Note: this feature is not included in the older version in Play/Akka 2.0. - Use `myConfig.root().render()` to get a `Config` printed out as a string with comments showing where each value came from. ### Supports Java 8 and Later Currently the library is maintained against Java 8, but version 1.2.1 and earlier will work with Java 6. Please use 1.2.1 if you need Java 6 support, though some people have expressed interest in a branch off of 1.3.0 supporting Java 7. If you want to work on that branch you might bring it up on [chat](https://gitter.im/typesafehub/config). We can release a jar for Java 7 if someone(s) steps up to maintain the branch. The master branch does not use Java 8 "gratuitously" but some APIs that use Java 8 types will need to be removed. ### Rationale for Supported File Formats (For the curious.) The three file formats each have advantages. - Java `.properties`: - Java standard, built in to JVM - Supported by many tools such as IDEs - JSON: - easy to generate programmatically - well-defined and standard - bad for human maintenance, with no way to write comments, and no mechanisms to avoid duplication of similar config sections - HOCON/`.conf`: - nice for humans to read, type, and maintain, with more lenient syntax - built-in tools to avoid cut-and-paste - ways to refer to the system environment, such as system properties and environment variables The idea would be to use JSON if you're writing a script to spit out config, and use HOCON if you're maintaining config by hand. If you're doing both, then mix the two. Two alternatives to HOCON syntax could be: - YAML is also a JSON superset and has a mechanism for adding custom types, so the include statements in HOCON could become a custom type tag like `!include`, and substitutions in HOCON could become a custom tag such as `!subst`, for example. The result is somewhat clunky to write, but would have the same in-memory representation as the HOCON approach. - Put a syntax inside JSON strings, so you might write something like `"$include" : "filename"` or allow `"foo" : "${bar}"`. This is a way to tunnel new syntax through a JSON parser, but other than the implementation benefit (using a standard JSON parser), it doesn't really work. It's a bad syntax for human maintenance, and it's not valid JSON anymore because properly interpreting it requires treating some valid JSON strings as something other than plain strings. A better approach is to allow mixing true JSON files into the config but also support a nicer format. ### Other APIs (Wrappers, Ports and Utilities) This may not be comprehensive - if you'd like to add mention of your wrapper, just send a pull request for this README. We would love to know what you're doing with this library or with the HOCON format. #### Guice integration * Typesafe Config Guice https://github.com/racc/typesafeconfig-guice #### Java (yep!) wrappers for the Java library * tscfg https://github.com/carueda/tscfg #### Scala wrappers for the Java library * Ficus https://github.com/ceedubs/ficus * configz https://github.com/arosien/configz * configs https://github.com/kxbmap/configs * config-annotation https://github.com/wacai/config-annotation * PureConfig https://github.com/melrief/pureconfig * Simple Scala Config https://github.com/ElderResearch/ssc * konfig https://github.com/vpon/konfig #### Clojure wrappers for the Java library * beamly-core.config https://github.com/beamly/beamly-core.config #### Scala port * SHocon https://github.com/unicredit/shocon (work with both Scala and Scala.Js) #### Ruby port * https://github.com/puppetlabs/ruby-hocon #### Puppet module * Manage your HOCON configuration files with Puppet!: https://forge.puppetlabs.com/puppetlabs/hocon #### Python port * pyhocon https://github.com/chimpler/pyhocon #### C++ port * https://github.com/puppetlabs/cpp-hocon #### Linting tool * A web based linting tool http://www.hoconlint.com/ config-1.3.1/appveyor.yml000066400000000000000000000005101277147274600153660ustar00rootroot00000000000000version: '{build}' os: Windows Server 2012 install: - cmd: choco install sbt -ia "INSTALLDIR=""C:\sbt""" - cmd: SET PATH=C:\sbt\bin;%JAVA_HOME%\bin;%PATH% - cmd: SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g build_script: - sbt clean compile test_script: - sbt test - sbt doc cache: - C:\sbt\ - C:\Users\appveyor\.ivy2 config-1.3.1/build.sbt000066400000000000000000000013611277147274600146140ustar00rootroot00000000000000// to release, bump major/minor/micro as appropriate, // update NEWS, update version in README.md, tag, then // publishSigned. // Release tags should follow: http://semver.org/ enablePlugins(GitVersioning) git.baseVersion := "1.3.0" organization in GlobalScope := "com.typesafe" scalacOptions in GlobalScope in Compile := Seq("-unchecked", "-deprecation", "-feature") scalacOptions in GlobalScope in Test := Seq("-unchecked", "-deprecation", "-feature") scalaVersion in ThisBuild := "2.10.4" useGpg := true aggregate in PgpKeys.publishSigned := false PgpKeys.publishSigned := (PgpKeys.publishSigned in configLib).value aggregate in PgpKeys.publishLocalSigned := false PgpKeys.publishLocalSigned := (PgpKeys.publishLocalSigned in configLib).value config-1.3.1/config/000077500000000000000000000000001277147274600142475ustar00rootroot00000000000000config-1.3.1/config/.gitignore000066400000000000000000000000051277147274600162320ustar00rootroot00000000000000/bin config-1.3.1/config/build.sbt000066400000000000000000000061251277147274600160640ustar00rootroot00000000000000import de.johoop.findbugs4sbt.FindBugs._ import de.johoop.findbugs4sbt.{ Effort, ReportType } import de.johoop.jacoco4sbt.JacocoPlugin.jacoco import com.typesafe.sbt.SbtScalariform import com.typesafe.sbt.SbtScalariform.ScalariformKeys import scalariform.formatter.preferences._ SbtScalariform.scalariformSettings val formatPrefs = FormattingPreferences() .setPreference(IndentSpaces, 4) ScalariformKeys.preferences in Compile := formatPrefs ScalariformKeys.preferences in Test := formatPrefs fork in test := true fork in run := true fork in run in Test := true autoScalaLibrary := false crossPaths := false libraryDependencies += "net.liftweb" %% "lift-json" % "2.5" % "test" libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" externalResolvers += "Scala Tools Snapshots" at "http://scala-tools.org/repo-snapshots/" checkstyleConfigLocation := CheckstyleConfigLocation.File((baseDirectory.value / "checkstyle-config.xml").toString) checkstyle in Compile := { val log = streams.value.log (checkstyle in Compile).value val resultFile = (checkstyleOutputFile in Compile).value val results = scala.xml.XML.loadFile(resultFile) val errorFiles = results \\ "checkstyle" \\ "file" def errorFromXml(node: scala.xml.NodeSeq): (String, String, String) = { val line: String = (node \ "@line" text) val msg: String = (node \ "@message" text) val source: String = (node \ "@source" text) (line, msg, source) } def errorsFromXml(fileNode: scala.xml.NodeSeq): Seq[(String, String, String, String)] = { val name: String = (fileNode \ "@name" text) val errors = (fileNode \\ "error") map { e => errorFromXml(e) } errors map { case (line, error, source) => (name, line, error, source) } } val errors = errorFiles flatMap { f => errorsFromXml(f) } if (errors.nonEmpty) { for (e <- errors) { log.error(s"${e._1}:${e._2}: ${e._3} (from ${e._4})") } throw new RuntimeException(s"Checkstyle failed with ${errors.size} errors") } log.info("No errors from checkstyle") } // add checkstyle as a dependency of doc doc in Compile <<= (doc in Compile).dependsOn(checkstyle in Compile) findbugsSettings findbugsReportType := Some(ReportType.Html) findbugsReportPath := Some(crossTarget.value / "findbugs.html") findbugsEffort := Effort.Maximum findbugsMaxMemory := 2000 jacoco.settings javacOptions in (Compile, compile) ++= Seq("-source", "1.6", "-target", "1.8", "-g", "-Xlint:unchecked") // because we test some global state such as singleton caches, // we have to run tests in serial. parallelExecution in Test := false javacOptions in (Compile, doc) ++= Seq("-group", s"Public API (version ${version.value})", "com.typesafe.config:com.typesafe.config.parser", "-group", "Internal Implementation - Not ABI Stable", "com.typesafe.config.impl") javadocSourceBaseUrl := { for (gitHead <- com.typesafe.sbt.SbtGit.GitKeys.gitHeadCommit.value) yield s"https://github.com/typesafehub/config/blob/$gitHead/config/src/main/java" } javaVersionPrefix in javaVersionCheck := Some("1.8") config-1.3.1/config/checkstyle-config.xml000066400000000000000000000012131277147274600203670ustar00rootroot00000000000000 config-1.3.1/config/checkstyle-suppressions.xml000066400000000000000000000011551277147274600217040ustar00rootroot00000000000000 config-1.3.1/config/src/000077500000000000000000000000001277147274600150365ustar00rootroot00000000000000config-1.3.1/config/src/main/000077500000000000000000000000001277147274600157625ustar00rootroot00000000000000config-1.3.1/config/src/main/java/000077500000000000000000000000001277147274600167035ustar00rootroot00000000000000config-1.3.1/config/src/main/java/com/000077500000000000000000000000001277147274600174615ustar00rootroot00000000000000config-1.3.1/config/src/main/java/com/typesafe/000077500000000000000000000000001277147274600213015ustar00rootroot00000000000000config-1.3.1/config/src/main/java/com/typesafe/config/000077500000000000000000000000001277147274600225465ustar00rootroot00000000000000config-1.3.1/config/src/main/java/com/typesafe/config/Config.java000066400000000000000000001254231277147274600246250ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; import java.time.Duration; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** * An immutable map from config paths to config values. Paths are dot-separated * expressions such as foo.bar.baz. Values are as in JSON * (booleans, strings, numbers, lists, or objects), represented by * {@link ConfigValue} instances. Values accessed through the * Config interface are never null. * *

* {@code Config} is an immutable object and thus safe to use from multiple * threads. There's never a need for "defensive copies." * *

* Fundamental operations on a {@code Config} include getting configuration * values, resolving substitutions with {@link Config#resolve()}, and * merging configs using {@link Config#withFallback(ConfigMergeable)}. * *

* All operations return a new immutable {@code Config} rather than modifying * the original instance. * *

* Examples * *

* You can find an example app and library on * GitHub. Also be sure to read the package overview which * describes the big picture as shown in those examples. * *

* Paths, keys, and Config vs. ConfigObject * *

* Config is a view onto a tree of {@link ConfigObject}; the * corresponding object tree can be found through {@link Config#root()}. * ConfigObject is a map from config keys, rather than * paths, to config values. Think of ConfigObject as a JSON object * and Config as a configuration API. * *

* The API tries to consistently use the terms "key" and "path." A key is a key * in a JSON object; it's just a string that's the key in a map. A "path" is a * parseable expression with a syntax and it refers to a series of keys. Path * expressions are described in the spec for * Human-Optimized Config Object Notation. In brief, a path is * period-separated so "a.b.c" looks for key c in object b in object a in the * root object. Sometimes double quotes are needed around special characters in * path expressions. * *

* The API for a {@code Config} is in terms of path expressions, while the API * for a {@code ConfigObject} is in terms of keys. Conceptually, {@code Config} * is a one-level map from paths to values, while a * {@code ConfigObject} is a tree of nested maps from keys to values. * *

* Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert * between path expressions and individual path elements (keys). * *

* 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 * {@code ConfigObject}, while a {@code Config} treats null values as if they * were missing. (With the exception of two methods: {@link Config#hasPathOrNull} * and {@link Config#getIsNull} let you detect null values.) * *

* Getting configuration values * *

* The "getters" on a {@code Config} all work in the same way. They never return * null, nor do they return a {@code ConfigValue} with * {@link ConfigValue#valueType() valueType()} of {@link ConfigValueType#NULL * NULL}. Instead, they throw {@link ConfigException.Missing} if the value is * completely absent or set to null. If the value is set to null, a subtype of * {@code ConfigException.Missing} called {@link ConfigException.Null} will be * thrown. {@link ConfigException.WrongType} will be thrown anytime you ask for * a type and the value has an incompatible type. Reasonable type conversions * are performed for you though. * *

* Iteration * *

* If you want to iterate over the contents of a {@code Config}, you can get its * {@code ConfigObject} with {@link #root()}, and then iterate over the * {@code ConfigObject} (which implements java.util.Map). Or, you * can use {@link #entrySet()} which recurses the object tree for you and builds * up a Set of all path-value pairs where the value is not null. * *

* Resolving substitutions * *

* Substitutions are the ${foo.bar} syntax in config * files, described in the specification. Resolving substitutions replaces these references with real * values. * *

* Before using a {@code Config} it's necessary to call {@link Config#resolve()} * to handle substitutions (though {@link ConfigFactory#load()} and similar * methods will do the resolve for you already). * *

* Merging * *

* The full Config for your application can be constructed using * the associative operation {@link Config#withFallback(ConfigMergeable)}. If * you use {@link ConfigFactory#load()} (recommended), it merges system * properties over the top of application.conf over the top of * reference.conf, using withFallback. You can add in * additional sources of configuration in the same way (usually, custom layers * should go either just above or just below application.conf, * keeping reference.conf at the bottom and system properties at * the top). * *

* Serialization * *

* Convert a Config to a JSON or HOCON string by calling * {@link ConfigObject#render()} on the root object, * myConfig.root().render(). There's also a variant * {@link ConfigObject#render(ConfigRenderOptions)} which allows you to control * the format of the rendered string. (See {@link ConfigRenderOptions}.) Note * that Config does not remember the formatting of the original * file, so if you load, modify, and re-save a config file, it will be * substantially reformatted. * *

* As an alternative to {@link ConfigObject#render()}, the * toString() method produces a debug-output-oriented * representation (which is not valid JSON). * *

* Java serialization is supported as well for Config and all * subtypes of ConfigValue. * *

* This is an interface but don't implement it yourself * *

* Do not implement {@code Config}; 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 Config extends ConfigMergeable { /** * Gets the {@code Config} as a tree of {@link ConfigObject}. This is a * constant-time operation (it is not proportional to the number of values * in the {@code Config}). * * @return the root object in the configuration */ ConfigObject root(); /** * Gets the origin of the {@code Config}, which may be a file, or a file * with a line number, or just a descriptive phrase. * * @return the origin of the {@code Config} for use in error messages */ ConfigOrigin origin(); @Override Config withFallback(ConfigMergeable other); /** * Returns a replacement config with all substitutions (the * ${foo.bar} syntax, see the * spec) resolved. Substitutions are looked up using this * Config as the root object, that is, a substitution * ${foo.bar} will be replaced with the result of * getValue("foo.bar"). * *

* This method uses {@link ConfigResolveOptions#defaults()}, there is * another variant {@link Config#resolve(ConfigResolveOptions)} which lets * you specify non-default options. * *

* A given {@link Config} must be resolved before using it to retrieve * config values, but ideally should be resolved one time for your entire * stack of fallbacks (see {@link Config#withFallback}). Otherwise, some * substitutions that could have resolved with all fallbacks available may * not resolve, which will be potentially confusing for your application's * users. * *

* resolve() should be invoked on root config objects, rather * than on a subtree (a subtree is the result of something like * config.getConfig("foo")). The problem with * resolve() on a subtree is that substitutions are relative to * the root of the config and the subtree will have no way to get values * from the root. For example, if you did * config.getConfig("foo").resolve() on the below config file, * it would not work: * *

     *   common-value = 10
     *   foo {
     *      whatever = ${common-value}
     *   }
     * 
* *

* Many methods on {@link ConfigFactory} such as * {@link ConfigFactory#load()} automatically resolve the loaded * Config on the loaded stack of config files. * *

* Resolving an already-resolved config is a harmless no-op, but again, it * is best to resolve an entire stack of fallbacks (such as all your config * files combined) rather than resolving each one individually. * * @return an immutable object with substitutions resolved * @throws ConfigException.UnresolvedSubstitution * if any substitutions refer to nonexistent paths * @throws ConfigException * some other config exception if there are other problems */ Config resolve(); /** * Like {@link Config#resolve()} but allows you to specify non-default * options. * * @param options * resolve options * @return the resolved Config (may be only partially resolved if options are set to allow unresolved) */ Config resolve(ConfigResolveOptions options); /** * Checks whether the config is completely resolved. After a successful call * to {@link Config#resolve()} it will be completely resolved, but after * calling {@link Config#resolve(ConfigResolveOptions)} with * allowUnresolved set in the options, it may or may not be * completely resolved. A newly-loaded config may or may not be completely * resolved depending on whether there were substitutions present in the * file. * * @return true if there are no unresolved substitutions remaining in this * configuration. * @since 1.2.0 */ boolean isResolved(); /** * Like {@link Config#resolve()} except that substitution values are looked * up in the given source, rather than in this instance. This is a * special-purpose method which doesn't make sense to use in most cases; * it's only needed if you're constructing some sort of app-specific custom * approach to configuration. The more usual approach if you have a source * of substitution values would be to merge that source into your config * stack using {@link Config#withFallback} and then resolve. *

* Note that this method does NOT look in this instance for substitution * values. If you want to do that, you could either merge this instance into * your value source using {@link Config#withFallback}, or you could resolve * multiple times with multiple sources (using * {@link ConfigResolveOptions#setAllowUnresolved(boolean)} so the partial * resolves don't fail). * * @param source * configuration to pull values from * @return an immutable object with substitutions resolved * @throws ConfigException.UnresolvedSubstitution * if any substitutions refer to paths which are not in the * source * @throws ConfigException * some other config exception if there are other problems * @since 1.2.0 */ Config resolveWith(Config source); /** * Like {@link Config#resolveWith(Config)} but allows you to specify * non-default options. * * @param source * source configuration to pull values from * @param options * resolve options * @return the resolved Config (may be only partially resolved * if options are set to allow unresolved) * @since 1.2.0 */ Config resolveWith(Config source, ConfigResolveOptions options); /** * Validates this config against a reference config, throwing an exception * if it is invalid. The purpose of this method is to "fail early" with a * comprehensive list of problems; in general, anything this method can find * would be detected later when trying to use the config, but it's often * more user-friendly to fail right away when loading the config. * *

* Using this method is always optional, since you can "fail late" instead. * *

* You must restrict validation to paths you "own" (those whose meaning are * defined by your code module). If you validate globally, you may trigger * errors about paths that happen to be in the config but have nothing to do * with your module. It's best to allow the modules owning those paths to * validate them. Also, if every module validates only its own stuff, there * isn't as much redundant work being done. * *

* If no paths are specified in checkValid()'s parameter list, * validation is for the entire config. * *

* If you specify paths that are not in the reference config, those paths * are ignored. (There's nothing to validate.) * *

* Here's what validation involves: * *

    *
  • All paths found in the reference config must be present in this * config or an exception will be thrown. *
  • * Some changes in type from the reference config to this config will cause * an exception to be thrown. Not all potential type problems are detected, * in particular it's assumed that strings are compatible with everything * except objects and lists. This is because string types are often "really" * some other type (system properties always start out as strings, or a * string like "5ms" could be used with {@link #getMilliseconds}). Also, * it's allowed to set any type to null or override null with any type. *
  • * Any unresolved substitutions in this config will cause a validation * failure; both the reference config and this config should be resolved * before validation. If the reference config is unresolved, it's a bug in * the caller of this method. *
* *

* If you want to allow a certain setting to have a flexible type (or * otherwise want validation to be looser for some settings), you could * either remove the problematic setting from the reference config provided * to this method, or you could intercept the validation exception and * screen out certain problems. Of course, this will only work if all other * callers of this method are careful to restrict validation to their own * paths, as they should be. * *

* If validation fails, the thrown exception contains a list of all problems * found. See {@link ConfigException.ValidationFailed#problems}. The * exception's getMessage() will have all the problems * concatenated into one huge string, as well. * *

* Again, checkValid() can't guess every domain-specific way a * setting can be invalid, so some problems may arise later when attempting * to use the config. checkValid() is limited to reporting * generic, but common, problems such as missing settings and blatant type * incompatibilities. * * @param reference * a reference configuration * @param restrictToPaths * only validate values underneath these paths that your code * module owns and understands * @throws ConfigException.ValidationFailed * if there are any validation issues * @throws ConfigException.NotResolved * if this config is not resolved * @throws ConfigException.BugOrBroken * if the reference config is unresolved or caller otherwise * misuses the API */ void checkValid(Config reference, String... restrictToPaths); /** * Checks whether a value is present and non-null at the given path. This * differs in two ways from {@code Map.containsKey()} as implemented by * {@link ConfigObject}: it looks for a path expression, not a key; and it * returns false for null values, while {@code containsKey()} returns true * indicating that the object contains a null value for the key. * *

* If a path exists according to {@link #hasPath(String)}, then * {@link #getValue(String)} will never throw an exception. However, the * typed getters, such as {@link #getInt(String)}, will still throw if the * value is not convertible to the requested type. * *

* Note that path expressions have a syntax and sometimes require quoting * (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). * * @param path * the path expression * @return true if a non-null value is present at the path * @throws ConfigException.BadPath * if the path expression is invalid */ boolean hasPath(String path); /** * Checks whether a value is present at the given path, even * if the value is null. Most of the getters on * Config will throw if you try to get a null * value, so if you plan to call {@link #getValue(String)}, * {@link #getInt(String)}, or another getter you may want to * use plain {@link #hasPath(String)} rather than this method. * *

* To handle all three cases (unset, null, and a non-null value) * the code might look like: *


     * if (config.hasPathOrNull(path)) {
     *     if (config.getIsNull(path)) {
     *        // handle null setting
     *     } else {
     *        // get and use non-null setting
     *     }
     * } else {
     *     // handle entirely unset path
     * }
     * 
* *

However, the usual thing is to allow entirely unset * paths to be a bug that throws an exception (because you set * a default in your reference.conf), so in that * case it's OK to call {@link #getIsNull(String)} without * checking hasPathOrNull first. * *

* Note that path expressions have a syntax and sometimes require quoting * (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). * * @param path * the path expression * @return true if a value is present at the path, even if the value is null * @throws ConfigException.BadPath * if the path expression is invalid */ boolean hasPathOrNull(String path); /** * Returns true if the {@code Config}'s root object contains no key-value * pairs. * * @return true if the configuration is empty */ boolean isEmpty(); /** * Returns the set of path-value pairs, excluding any null values, found by * recursing {@link #root() the root object}. Note that this is very * different from root().entrySet() which returns the set of * immediate-child keys in the root object and includes null values. *

* Entries contain path expressions meaning there may be quoting * and escaping involved. Parse path expressions with * {@link ConfigUtil#splitPath}. *

* Because a Config is conceptually a single-level map from * paths to values, there will not be any {@link ConfigObject} values in the * entries (that is, all entries represent leaf nodes). Use * {@link ConfigObject} rather than Config if you want a tree. * (OK, this is a slight lie: Config entries may contain * {@link ConfigList} and the lists may contain objects. But no objects are * directly included as entry values.) * * @return set of paths with non-null values, built up by recursing the * entire tree of {@link ConfigObject} and creating an entry for * each leaf value. */ Set> entrySet(); /** * Checks whether a value is set to null at the given path, * but throws an exception if the value is entirely * unset. This method will not throw if {@link * #hasPathOrNull(String)} returned true for the same path, so * to avoid any possible exception check * hasPathOrNull() first. However, an exception * for unset paths will usually be the right thing (because a * reference.conf should exist that has the path * set, the path should never be unset unless something is * broken). * *

* Note that path expressions have a syntax and sometimes require quoting * (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). * * @param path * the path expression * @return true if the value exists and is null, false if it * exists and is not null * @throws ConfigException.BadPath * if the path expression is invalid * @throws ConfigException.Missing * if value is not set at all */ boolean getIsNull(String path); /** * * @param path * path expression * @return the boolean value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to boolean */ boolean getBoolean(String path); /** * @param path * path expression * @return the numeric value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a number */ Number getNumber(String path); /** * Gets the integer at the given path. If the value at the * path has a fractional (floating point) component, it * will be discarded and only the integer part will be * returned (it works like a "narrowing primitive conversion" * in the Java language specification). * * @param path * path expression * @return the 32-bit integer value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to an int (for example it is out * of range, or it's a boolean value) */ int getInt(String path); /** * Gets the long integer at the given path. If the value at * the path has a fractional (floating point) component, it * will be discarded and only the integer part will be * returned (it works like a "narrowing primitive conversion" * in the Java language specification). * * @param path * path expression * @return the 64-bit long value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a long */ long getLong(String path); /** * @param path * path expression * @return the floating-point value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a double */ double getDouble(String path); /** * @param path * path expression * @return the string value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a string */ String getString(String path); /** * @param enumClass * an enum class * @param * a generic denoting a specific type of enum * @param path * path expression * @return the {@code Enum} value at the requested path * of the requested enum class * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to an Enum */ public > T getEnum(Class enumClass, String path); /** * @param path * path expression * @return the {@link ConfigObject} value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to an object */ ConfigObject getObject(String path); /** * @param path * path expression * @return the nested {@code Config} value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a Config */ Config getConfig(String path); /** * Gets the value at the path as an unwrapped Java boxed value ( * {@link java.lang.Boolean Boolean}, {@link java.lang.Integer Integer}, and * so on - see {@link ConfigValue#unwrapped()}). * * @param path * path expression * @return the unwrapped value at the requested path * @throws ConfigException.Missing * if value is absent or null */ Object getAnyRef(String path); /** * Gets the value at the given path, unless the value is a * null value or missing, in which case it throws just like * the other getters. Use {@code get()} on the {@link * Config#root()} object (or other object in the tree) if you * want an unprocessed value. * * @param path * path expression * @return the value at the requested path * @throws ConfigException.Missing * if value is absent or null */ ConfigValue getValue(String path); /** * Gets a value as a size in bytes (parses special strings like "128M"). If * the value is already a number, then it's left alone; if it's a string, * it's parsed understanding unit suffixes such as "128K", as documented in * the the * spec. * * @param path * path expression * @return the value at the requested path, in bytes * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to Long or String * @throws ConfigException.BadValue * if value cannot be parsed as a size in bytes */ Long getBytes(String path); /** * Gets a value as an amount of memory (parses special strings like "128M"). If * the value is already a number, then it's left alone; if it's a string, * it's parsed understanding unit suffixes such as "128K", as documented in * the the * spec. * * @since 1.3.0 * * @param path * path expression * @return the value at the requested path, in bytes * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to Long or String * @throws ConfigException.BadValue * if value cannot be parsed as a size in bytes */ ConfigMemorySize getMemorySize(String path); /** * Get value as a duration in milliseconds. If the value is already a * number, then it's left alone; if it's a string, it's parsed understanding * units suffixes like "10m" or "5ns" as documented in the the * spec. * * @deprecated As of release 1.1, replaced by {@link #getDuration(String, TimeUnit)} * * @param path * path expression * @return the duration value at the requested path, in milliseconds * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to Long or String * @throws ConfigException.BadValue * if value cannot be parsed as a number of milliseconds */ @Deprecated Long getMilliseconds(String path); /** * Get value as a duration in nanoseconds. If the value is already a number * it's taken as milliseconds and converted to nanoseconds. If it's a * string, it's parsed understanding unit suffixes, as for * {@link #getDuration(String, TimeUnit)}. * * @deprecated As of release 1.1, replaced by {@link #getDuration(String, TimeUnit)} * * @param path * path expression * @return the duration value at the requested path, in nanoseconds * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to Long or String * @throws ConfigException.BadValue * if value cannot be parsed as a number of nanoseconds */ @Deprecated Long getNanoseconds(String path); /** * Gets a value as a duration in a specified * {@link java.util.concurrent.TimeUnit TimeUnit}. If the value is already a * number, then it's taken as milliseconds and then converted to the * requested TimeUnit; if it's a string, it's parsed understanding units * suffixes like "10m" or "5ns" as documented in the the * spec. * * @since 1.2.0 * * @param path * path expression * @param unit * convert the return value to this time unit * @return the duration value at the requested path, in the given TimeUnit * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to Long or String * @throws ConfigException.BadValue * if value cannot be parsed as a number of the given TimeUnit */ long getDuration(String path, TimeUnit unit); /** * Gets a value as a java.time.Duration. If the value is * already a number, then it's taken as milliseconds; if it's * a string, it's parsed understanding units suffixes like * "10m" or "5ns" as documented in the the * spec. This method never returns null. * * @since 1.3.0 * * @param path * path expression * @return the duration value at the requested path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to Long or String * @throws ConfigException.BadValue * if value cannot be parsed as a number of the given TimeUnit */ Duration getDuration(String path); /** * Gets a list value (with any element type) as a {@link ConfigList}, which * implements {@code java.util.List}. Throws if the path is * unset or null. * * @param path * the path to the list value. * @return the {@link ConfigList} at the path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a ConfigList */ ConfigList getList(String path); /** * Gets a list value with boolean elements. Throws if the * path is unset or null or not a list or contains values not * convertible to boolean. * * @param path * the path to the list value. * @return the list at the path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a list of booleans */ List getBooleanList(String path); /** * Gets a list value with number elements. Throws if the * path is unset or null or not a list or contains values not * convertible to number. * * @param path * the path to the list value. * @return the list at the path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a list of numbers */ List getNumberList(String path); /** * Gets a list value with int elements. Throws if the * path is unset or null or not a list or contains values not * convertible to int. * * @param path * the path to the list value. * @return the list at the path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a list of ints */ List getIntList(String path); /** * Gets a list value with long elements. Throws if the * path is unset or null or not a list or contains values not * convertible to long. * * @param path * the path to the list value. * @return the list at the path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a list of longs */ List getLongList(String path); /** * Gets a list value with double elements. Throws if the * path is unset or null or not a list or contains values not * convertible to double. * * @param path * the path to the list value. * @return the list at the path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a list of doubles */ List getDoubleList(String path); /** * Gets a list value with string elements. Throws if the * path is unset or null or not a list or contains values not * convertible to string. * * @param path * the path to the list value. * @return the list at the path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a list of strings */ List getStringList(String path); /** * Gets a list value with {@code Enum} elements. Throws if the * path is unset or null or not a list or contains values not * convertible to {@code Enum}. * * @param enumClass * the enum class * @param * a generic denoting a specific type of enum * @param path * the path to the list value. * @return the list at the path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a list of {@code Enum} */ > List getEnumList(Class enumClass, String path); /** * Gets a list value with object elements. Throws if the * path is unset or null or not a list or contains values not * convertible to ConfigObject. * * @param path * the path to the list value. * @return the list at the path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a list of objects */ List getObjectList(String path); /** * Gets a list value with Config elements. * Throws if the path is unset or null or not a list or * contains values not convertible to Config. * * @param path * the path to the list value. * @return the list at the path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a list of configs */ List getConfigList(String path); /** * Gets a list value with any kind of elements. Throws if the * path is unset or null or not a list. Each element is * "unwrapped" (see {@link ConfigValue#unwrapped()}). * * @param path * the path to the list value. * @return the list at the path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a list */ List getAnyRefList(String path); /** * Gets a list value with elements representing a size in * bytes. Throws if the path is unset or null or not a list * or contains values not convertible to memory sizes. * * @param path * the path to the list value. * @return the list at the path * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a list of memory sizes */ List getBytesList(String path); /** * Gets a list, converting each value in the list to a memory size, using the * same rules as {@link #getMemorySize(String)}. * * @since 1.3.0 * @param path * a path expression * @return list of memory sizes * @throws ConfigException.Missing * if value is absent or null * @throws ConfigException.WrongType * if value is not convertible to a list of memory sizes */ List getMemorySizeList(String path); /** * @deprecated As of release 1.1, replaced by {@link #getDurationList(String, TimeUnit)} * @param path the path * @return list of millisecond values */ @Deprecated List getMillisecondsList(String path); /** * @deprecated As of release 1.1, replaced by {@link #getDurationList(String, TimeUnit)} * @param path the path * @return list of nanosecond values */ @Deprecated List getNanosecondsList(String path); /** * Gets a list, converting each value in the list to a duration, using the * same rules as {@link #getDuration(String, TimeUnit)}. * * @since 1.2.0 * @param path * a path expression * @param unit * time units of the returned values * @return list of durations, in the requested units */ List getDurationList(String path, TimeUnit unit); /** * Gets a list, converting each value in the list to a duration, using the * same rules as {@link #getDuration(String)}. * * @since 1.3.0 * @param path * a path expression * @return list of durations */ List getDurationList(String path); /** * Clone the config with only the given path (and its children) retained; * all sibling paths are removed. *

* Note that path expressions have a syntax and sometimes require quoting * (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). * * @param path * path to keep * @return a copy of the config minus all paths except the one specified */ Config withOnlyPath(String path); /** * Clone the config with the given path removed. *

* Note that path expressions have a syntax and sometimes require quoting * (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). * * @param path * path expression to remove * @return a copy of the config minus the specified path */ Config withoutPath(String path); /** * Places the config inside another {@code Config} at the given path. *

* Note that path expressions have a syntax and sometimes require quoting * (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). * * @param path * path expression to store this config at. * @return a {@code Config} instance containing this config at the given * path. */ Config atPath(String path); /** * Places the config inside a {@code Config} at the given key. See also * atPath(). Note that a key is NOT a path expression (see * {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). * * @param key * key to store this config at. * @return a {@code Config} instance containing this config at the given * key. */ Config atKey(String key); /** * Returns a {@code Config} based on this one, but with the given path set * to the given value. Does not modify this instance (since it's immutable). * If the path already has a value, that value is replaced. To remove a * value, use withoutPath(). *

* Note that path expressions have a syntax and sometimes require quoting * (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). * * @param path * path expression for the value's new location * @param value * value at the new path * @return the new instance with the new map entry */ Config withValue(String path, ConfigValue value); } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigBeanFactory.java000066400000000000000000000033351277147274600267400ustar00rootroot00000000000000package com.typesafe.config; import com.typesafe.config.impl.ConfigBeanImpl; /** * Factory for automatically creating a Java class from a {@link Config}. * See {@link #create(Config,Class)}. * * @since 1.3.0 */ public class ConfigBeanFactory { /** * Creates an instance of a class, initializing its fields from a {@link Config}. * * Example usage: * *

     * Config configSource = ConfigFactory.load().getConfig("foo");
     * FooConfig config = ConfigBeanFactory.create(configSource, FooConfig.class);
     * 
* * The Java class should follow JavaBean conventions. Field types * can be any of the types you can normally get from a {@link * Config}, including java.time.Duration or {@link * ConfigMemorySize}. Fields may also be another JavaBean-style * class. * * Fields are mapped to config by converting the config key to * camel case. So the key foo-bar becomes JavaBean * setter setFooBar. * * @since 1.3.0 * * @param config source of config information * @param clazz class to be instantiated * @param the type of the class to be instantiated * @return an instance of the class populated with data from the config * @throws ConfigException.BadBean * If something is wrong with the JavaBean * @throws ConfigException.ValidationFailed * If the config doesn't conform to the bean's implied schema * @throws ConfigException * Can throw the same exceptions as the getters on Config */ public static T create(Config config, Class clazz) { return ConfigBeanImpl.createInternal(config, clazz); } } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigException.java000066400000000000000000000332051277147274600265000ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Field; import com.typesafe.config.impl.ConfigImplUtil; /** * All exceptions thrown by the library are subclasses of * ConfigException. */ public abstract class ConfigException extends RuntimeException implements Serializable { private static final long serialVersionUID = 1L; final private transient ConfigOrigin origin; protected ConfigException(ConfigOrigin origin, String message, Throwable cause) { super(origin.description() + ": " + message, cause); this.origin = origin; } protected ConfigException(ConfigOrigin origin, String message) { this(origin.description() + ": " + message, null); } protected ConfigException(String message, Throwable cause) { super(message, cause); this.origin = null; } protected ConfigException(String message) { this(message, null); } /** * Returns an "origin" (such as a filename and line number) for the * exception, or null if none is available. If there's no sensible origin * for a given exception, or the kind of exception doesn't meaningfully * relate to a particular origin file, this returns null. Never assume this * will return non-null, it can always return null. * * @return origin of the problem, or null if unknown/inapplicable */ public ConfigOrigin origin() { return origin; } // we customize serialization because ConfigOrigin isn't // serializable and we don't want it to be (don't want to // support it) private void writeObject(java.io.ObjectOutputStream out) throws IOException { out.defaultWriteObject(); ConfigImplUtil.writeOrigin(out, origin); } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); ConfigOrigin origin = ConfigImplUtil.readOrigin(in); // circumvent "final" Field f; try { f = ConfigException.class.getDeclaredField("origin"); } catch (NoSuchFieldException e) { throw new IOException("ConfigException has no origin field?", e); } catch (SecurityException e) { throw new IOException("unable to fill out origin field in ConfigException", e); } f.setAccessible(true); try { f.set(this, origin); } catch (IllegalArgumentException e) { throw new IOException("unable to set origin field", e); } catch (IllegalAccessException e) { throw new IOException("unable to set origin field", e); } } /** * Exception indicating that the type of a value does not match the type you * requested. * */ public static class WrongType extends ConfigException { private static final long serialVersionUID = 1L; public WrongType(ConfigOrigin origin, String path, String expected, String actual, Throwable cause) { super(origin, path + " has type " + actual + " rather than " + expected, cause); } public WrongType(ConfigOrigin origin, String path, String expected, String actual) { this(origin, path, expected, actual, null); } public WrongType(ConfigOrigin origin, String message, Throwable cause) { super(origin, message, cause); } public WrongType(ConfigOrigin origin, String message) { super(origin, message, null); } } /** * Exception indicates that the setting was never set to anything, not even * null. */ public static class Missing extends ConfigException { private static final long serialVersionUID = 1L; public Missing(String path, Throwable cause) { super("No configuration setting found for key '" + path + "'", cause); } public Missing(String path) { this(path, null); } protected Missing(ConfigOrigin origin, String message, Throwable cause) { super(origin, message, cause); } protected Missing(ConfigOrigin origin, String message) { this(origin, message, null); } } /** * Exception indicates that the setting was treated as missing because it * was set to null. */ public static class Null extends Missing { private static final long serialVersionUID = 1L; private static String makeMessage(String path, String expected) { if (expected != null) { return "Configuration key '" + path + "' is set to null but expected " + expected; } else { return "Configuration key '" + path + "' is null"; } } public Null(ConfigOrigin origin, String path, String expected, Throwable cause) { super(origin, makeMessage(path, expected), cause); } public Null(ConfigOrigin origin, String path, String expected) { this(origin, path, expected, null); } } /** * Exception indicating that a value was messed up, for example you may have * asked for a duration and the value can't be sensibly parsed as a * duration. * */ public static class BadValue extends ConfigException { private static final long serialVersionUID = 1L; public BadValue(ConfigOrigin origin, String path, String message, Throwable cause) { super(origin, "Invalid value at '" + path + "': " + message, cause); } public BadValue(ConfigOrigin origin, String path, String message) { this(origin, path, message, null); } public BadValue(String path, String message, Throwable cause) { super("Invalid value at '" + path + "': " + message, cause); } public BadValue(String path, String message) { this(path, message, null); } } /** * Exception indicating that a path expression was invalid. Try putting * double quotes around path elements that contain "special" characters. * */ public static class BadPath extends ConfigException { private static final long serialVersionUID = 1L; public BadPath(ConfigOrigin origin, String path, String message, Throwable cause) { super(origin, path != null ? ("Invalid path '" + path + "': " + message) : message, cause); } public BadPath(ConfigOrigin origin, String path, String message) { this(origin, path, message, null); } public BadPath(String path, String message, Throwable cause) { super(path != null ? ("Invalid path '" + path + "': " + message) : message, cause); } public BadPath(String path, String message) { this(path, message, null); } public BadPath(ConfigOrigin origin, String message) { this(origin, null, message); } } /** * Exception indicating that there's a bug in something (possibly the * library itself) or the runtime environment is broken. This exception * should never be handled; instead, something should be fixed to keep the * exception from occurring. This exception can be thrown by any method in * the library. */ public static class BugOrBroken extends ConfigException { private static final long serialVersionUID = 1L; public BugOrBroken(String message, Throwable cause) { super(message, cause); } public BugOrBroken(String message) { this(message, null); } } /** * Exception indicating that there was an IO error. * */ public static class IO extends ConfigException { private static final long serialVersionUID = 1L; public IO(ConfigOrigin origin, String message, Throwable cause) { super(origin, message, cause); } public IO(ConfigOrigin origin, String message) { this(origin, message, null); } } /** * Exception indicating that there was a parse error. * */ public static class Parse extends ConfigException { private static final long serialVersionUID = 1L; public Parse(ConfigOrigin origin, String message, Throwable cause) { super(origin, message, cause); } public Parse(ConfigOrigin origin, String message) { this(origin, message, null); } } /** * Exception indicating that a substitution did not resolve to anything. * Thrown by {@link Config#resolve}. */ public static class UnresolvedSubstitution extends Parse { private static final long serialVersionUID = 1L; public UnresolvedSubstitution(ConfigOrigin origin, String detail, Throwable cause) { super(origin, "Could not resolve substitution to a value: " + detail, cause); } public UnresolvedSubstitution(ConfigOrigin origin, String detail) { this(origin, detail, null); } } /** * Exception indicating that you tried to use a function that requires * substitutions to be resolved, but substitutions have not been resolved * (that is, {@link Config#resolve} was not called). This is always a bug in * either application code or the library; it's wrong to write a handler for * this exception because you should be able to fix the code to avoid it by * adding calls to {@link Config#resolve}. */ public static class NotResolved extends BugOrBroken { private static final long serialVersionUID = 1L; public NotResolved(String message, Throwable cause) { super(message, cause); } public NotResolved(String message) { this(message, null); } } /** * Information about a problem that occurred in {@link Config#checkValid}. A * {@link ConfigException.ValidationFailed} exception thrown from * checkValid() includes a list of problems encountered. */ public static class ValidationProblem { final private String path; final private ConfigOrigin origin; final private String problem; public ValidationProblem(String path, ConfigOrigin origin, String problem) { this.path = path; this.origin = origin; this.problem = problem; } /** * Returns the config setting causing the problem. * @return the path of the problem setting */ public String path() { return path; } /** * Returns where the problem occurred (origin may include info on the * file, line number, etc.). * @return the origin of the problem setting */ public ConfigOrigin origin() { return origin; } /** * Returns a description of the problem. * @return description of the problem */ public String problem() { return problem; } @Override public String toString() { return "ValidationProblem(" + path + "," + origin + "," + problem + ")"; } } /** * Exception indicating that {@link Config#checkValid} found validity * problems. The problems are available via the {@link #problems()} method. * The getMessage() of this exception is a potentially very * long string listing all the problems found. */ public static class ValidationFailed extends ConfigException { private static final long serialVersionUID = 1L; final private Iterable problems; public ValidationFailed(Iterable problems) { super(makeMessage(problems), null); this.problems = problems; } public Iterable problems() { return problems; } private static String makeMessage(Iterable problems) { StringBuilder sb = new StringBuilder(); for (ValidationProblem p : problems) { sb.append(p.origin().description()); sb.append(": "); sb.append(p.path()); sb.append(": "); sb.append(p.problem()); sb.append(", "); } if (sb.length() == 0) throw new ConfigException.BugOrBroken( "ValidationFailed must have a non-empty list of problems"); sb.setLength(sb.length() - 2); // chop comma and space return sb.toString(); } } /** * Some problem with a JavaBean we are trying to initialize. * @since 1.3.0 */ public static class BadBean extends BugOrBroken { private static final long serialVersionUID = 1L; public BadBean(String message, Throwable cause) { super(message, cause); } public BadBean(String message) { this(message, null); } } /** * Exception that doesn't fall into any other category. */ public static class Generic extends ConfigException { private static final long serialVersionUID = 1L; public Generic(String message, Throwable cause) { super(message, cause); } public Generic(String message) { this(message, null); } } } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigFactory.java000066400000000000000000001253261277147274600261570ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; import com.typesafe.config.impl.ConfigImpl; import com.typesafe.config.impl.Parseable; import java.io.File; import java.io.Reader; import java.net.URL; import java.util.Map; import java.util.Properties; import java.util.concurrent.Callable; /** * Contains static methods for creating {@link Config} instances. * *

* See also {@link ConfigValueFactory} which contains static methods for * converting Java values into a {@link ConfigObject}. You can then convert a * {@code ConfigObject} into a {@code Config} with {@link ConfigObject#toConfig}. * *

* The static methods with "load" in the name do some sort of higher-level * operation potentially parsing multiple resources and resolving substitutions, * while the ones with "parse" in the name just create a {@link ConfigValue} * from a resource and nothing else. * *

You can find an example app and library on * GitHub. Also be sure to read the package * overview which describes the big picture as shown in those * examples. */ public final class ConfigFactory { private static final String STRATEGY_PROPERTY_NAME = "config.strategy"; private ConfigFactory() { } /** * Loads an application's configuration from the given classpath resource or * classpath resource basename, sandwiches it between default reference * config and default overrides, and then resolves it. The classpath * resource is "raw" (it should have no "/" prefix, and is not made relative * to any package, so it's like {@link ClassLoader#getResource} not * {@link Class#getResource}). * *

* Resources are loaded from the current thread's * {@link Thread#getContextClassLoader()}. In general, a library needs its * configuration to come from the class loader used to load that library, so * the proper "reference.conf" are present. * *

* The loaded object will already be resolved (substitutions have already * been processed). As a result, if you add more fallbacks then they won't * be seen by substitutions. Substitutions are the "${foo.bar}" syntax. If * you want to parse additional files or something then you need to use * {@link #load(Config)}. * *

* To load a standalone resource (without the default reference and default * overrides), use {@link #parseResourcesAnySyntax(String)} rather than this * method. To load only the reference config use {@link #defaultReference()} * and to load only the overrides use {@link #defaultOverrides()}. * * @param resourceBasename * name (optionally without extension) of a resource on classpath * @return configuration for an application relative to context class loader */ public static Config load(String resourceBasename) { return load(resourceBasename, ConfigParseOptions.defaults(), ConfigResolveOptions.defaults()); } /** * Like {@link #load(String)} but uses the supplied class loader instead of * the current thread's context class loader. * *

* To load a standalone resource (without the default reference and default * overrides), use {@link #parseResourcesAnySyntax(ClassLoader, String)} * rather than this method. To load only the reference config use * {@link #defaultReference(ClassLoader)} and to load only the overrides use * {@link #defaultOverrides(ClassLoader)}. * * @param loader class loader to look for resources in * @param resourceBasename basename (no .conf/.json/.properties suffix) * @return configuration for an application relative to given class loader */ public static Config load(ClassLoader loader, String resourceBasename) { return load(resourceBasename, ConfigParseOptions.defaults().setClassLoader(loader), ConfigResolveOptions.defaults()); } /** * Like {@link #load(String)} but allows you to specify parse and resolve * options. * * @param resourceBasename * the classpath resource name with optional extension * @param parseOptions * options to use when parsing the resource * @param resolveOptions * options to use when resolving the stack * @return configuration for an application */ public static Config load(String resourceBasename, ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) { ConfigParseOptions withLoader = ensureClassLoader(parseOptions, "load"); Config appConfig = ConfigFactory.parseResourcesAnySyntax(resourceBasename, withLoader); return load(withLoader.getClassLoader(), appConfig, resolveOptions); } /** * Like {@link #load(String,ConfigParseOptions,ConfigResolveOptions)} but * has a class loader parameter that overrides any from the * {@code ConfigParseOptions}. * * @param loader * class loader in which to find resources (overrides loader in * parse options) * @param resourceBasename * the classpath resource name with optional extension * @param parseOptions * options to use when parsing the resource (class loader * overridden) * @param resolveOptions * options to use when resolving the stack * @return configuration for an application */ public static Config load(ClassLoader loader, String resourceBasename, ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) { return load(resourceBasename, parseOptions.setClassLoader(loader), resolveOptions); } private static ClassLoader checkedContextClassLoader(String methodName) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (loader == null) throw new ConfigException.BugOrBroken("Context class loader is not set for the current thread; " + "if Thread.currentThread().getContextClassLoader() returns null, you must pass a ClassLoader " + "explicitly to ConfigFactory." + methodName); else return loader; } private static ConfigParseOptions ensureClassLoader(ConfigParseOptions options, String methodName) { if (options.getClassLoader() == null) return options.setClassLoader(checkedContextClassLoader(methodName)); else return options; } /** * Assembles a standard configuration using a custom Config * object rather than loading "application.conf". The Config * object will be sandwiched between the default reference config and * default overrides and then resolved. * * @param config * the application's portion of the configuration * @return resolved configuration with overrides and fallbacks added */ public static Config load(Config config) { return load(checkedContextClassLoader("load"), config); } /** * Like {@link #load(Config)} but allows you to specify * the class loader for looking up resources. * * @param loader * the class loader to use to find resources * @param config * the application's portion of the configuration * @return resolved configuration with overrides and fallbacks added */ public static Config load(ClassLoader loader, Config config) { return load(loader, config, ConfigResolveOptions.defaults()); } /** * Like {@link #load(Config)} but allows you to specify * {@link ConfigResolveOptions}. * * @param config * the application's portion of the configuration * @param resolveOptions * options for resolving the assembled config stack * @return resolved configuration with overrides and fallbacks added */ public static Config load(Config config, ConfigResolveOptions resolveOptions) { return load(checkedContextClassLoader("load"), config, resolveOptions); } /** * Like {@link #load(Config,ConfigResolveOptions)} but allows you to specify * a class loader other than the context class loader. * * @param loader * class loader to use when looking up override and reference * configs * @param config * the application's portion of the configuration * @param resolveOptions * options for resolving the assembled config stack * @return resolved configuration with overrides and fallbacks added */ public static Config load(ClassLoader loader, Config config, ConfigResolveOptions resolveOptions) { return defaultOverrides(loader).withFallback(config).withFallback(defaultReference(loader)) .resolve(resolveOptions); } /** * Loads a default configuration, equivalent to {@link #load(Config) * load(defaultApplication())} in most cases. This configuration should be used by * libraries and frameworks unless an application provides a different one. *

* This method may return a cached singleton so will not see changes to * system properties or config files. (Use {@link #invalidateCaches()} to * force it to reload.) * * @return configuration for an application */ public static Config load() { ClassLoader loader = checkedContextClassLoader("load"); return load(loader); } /** * Like {@link #load()} but allows specifying parse options. * * @param parseOptions * Options for parsing resources * @return configuration for an application */ public static Config load(ConfigParseOptions parseOptions) { return load(parseOptions, ConfigResolveOptions.defaults()); } /** * Like {@link #load()} but allows specifying a class loader other than the * thread's current context class loader. * * @param loader * class loader for finding resources * @return configuration for an application */ public static Config load(final ClassLoader loader) { final ConfigParseOptions withLoader = ConfigParseOptions.defaults().setClassLoader(loader); return ConfigImpl.computeCachedConfig(loader, "load", new Callable() { @Override public Config call() { return load(loader, defaultApplication(withLoader)); } }); } /** * Like {@link #load()} but allows specifying a class loader other than the * thread's current context class loader and also specify parse options. * * @param loader * class loader for finding resources (overrides any loader in parseOptions) * @param parseOptions * Options for parsing resources * @return configuration for an application */ public static Config load(ClassLoader loader, ConfigParseOptions parseOptions) { return load(parseOptions.setClassLoader(loader)); } /** * Like {@link #load()} but allows specifying a class loader other than the * thread's current context class loader and also specify resolve options. * * @param loader * class loader for finding resources * @param resolveOptions * options for resolving the assembled config stack * @return configuration for an application */ public static Config load(ClassLoader loader, ConfigResolveOptions resolveOptions) { return load(loader, ConfigParseOptions.defaults(), resolveOptions); } /** * Like {@link #load()} but allows specifying a class loader other than the * thread's current context class loader, parse options, and resolve options. * * @param loader * class loader for finding resources (overrides any loader in parseOptions) * @param parseOptions * Options for parsing resources * @param resolveOptions * options for resolving the assembled config stack * @return configuration for an application */ public static Config load(ClassLoader loader, ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) { final ConfigParseOptions withLoader = ensureClassLoader(parseOptions, "load"); return load(loader, defaultApplication(withLoader), resolveOptions); } /** * Like {@link #load()} but allows specifying parse options and resolve * options. * * @param parseOptions * Options for parsing resources * @param resolveOptions * options for resolving the assembled config stack * @return configuration for an application * * @since 1.3.0 */ public static Config load(ConfigParseOptions parseOptions, final ConfigResolveOptions resolveOptions) { final ConfigParseOptions withLoader = ensureClassLoader(parseOptions, "load"); return load(defaultApplication(withLoader), resolveOptions); } /** * Obtains the default reference configuration, which is currently created * by merging all resources "reference.conf" found on the classpath and * overriding the result with system properties. The returned reference * configuration will already have substitutions resolved. * *

* Libraries and frameworks should ship with a "reference.conf" in their * jar. * *

* The reference config must be looked up in the class loader that contains * the libraries that you want to use with this config, so the * "reference.conf" for each library can be found. Use * {@link #defaultReference(ClassLoader)} if the context class loader is not * suitable. * *

* The {@link #load()} methods merge this configuration for you * automatically. * *

* Future versions may look for reference configuration in more places. It * is not guaranteed that this method only looks at * "reference.conf". * * @return the default reference config for context class loader */ public static Config defaultReference() { return defaultReference(checkedContextClassLoader("defaultReference")); } /** * Like {@link #defaultReference()} but allows you to specify a class loader * to use rather than the current context class loader. * * @param loader class loader to look for resources in * @return the default reference config for this class loader */ public static Config defaultReference(ClassLoader loader) { return ConfigImpl.defaultReference(loader); } /** * Obtains the default override configuration, which currently consists of * system properties. The returned override configuration will already have * substitutions resolved. * *

* The {@link #load()} methods merge this configuration for you * automatically. * *

* Future versions may get overrides in more places. It is not guaranteed * that this method only uses system properties. * * @return the default override configuration */ public static Config defaultOverrides() { return systemProperties(); } /** * Like {@link #defaultOverrides()} but allows you to specify a class loader * to use rather than the current context class loader. * * @param loader class loader to look for resources in * @return the default override configuration */ public static Config defaultOverrides(ClassLoader loader) { return systemProperties(); } /** * Obtains the default application-specific configuration, * which defaults to parsing application.conf, * application.json, and * application.properties on the classpath, but * can also be rerouted using the config.file, * config.resource, and config.url * system properties. * *

The no-arguments {@link #load()} method automatically * stacks the {@link #defaultReference()}, {@link * #defaultApplication()}, and {@link #defaultOverrides()} * configs. You would use defaultApplication() * directly only if you're somehow customizing behavior by * reimplementing load(). * *

The configuration returned by * defaultApplication() will not be resolved * already, in contrast to defaultReference() and * defaultOverrides(). This is because * application.conf would normally be resolved after * merging with the reference and override configs. * *

* If the system properties config.resource, * config.file, or config.url are set, then the * classpath resource, file, or URL specified in those properties will be * used rather than the default * application.{conf,json,properties} classpath resources. * These system properties should not be set in code (after all, you can * just parse whatever you want manually and then use {@link #load(Config)} * if you don't want to use application.conf). The properties * are intended for use by the person or script launching the application. * For example someone might have a production.conf that * include application.conf but then change a couple of values. * When launching the app they could specify * -Dconfig.resource=production.conf to get production mode. * *

* If no system properties are set to change the location of the default * configuration, defaultApplication() is equivalent to * ConfigFactory.parseResources("application"). * * @since 1.3.0 * * @return the default application.conf or system-property-configured configuration */ public static Config defaultApplication() { return defaultApplication(ConfigParseOptions.defaults()); } /** * Like {@link #defaultApplication()} but allows you to specify a class loader * to use rather than the current context class loader. * * @since 1.3.0 * * @param loader class loader to look for resources in * @return the default application configuration */ public static Config defaultApplication(ClassLoader loader) { return defaultApplication(ConfigParseOptions.defaults().setClassLoader(loader)); } /** * Like {@link #defaultApplication()} but allows you to specify parse options. * * @since 1.3.0 * * @param options the options * @return the default application configuration */ public static Config defaultApplication(ConfigParseOptions options) { return getConfigLoadingStrategy().parseApplicationConfig(ensureClassLoader(options, "defaultApplication")); } /** * Reloads any cached configs, picking up changes to system properties for * example. Because a {@link Config} is immutable, anyone with a reference * to the old configs will still have the same outdated objects. However, * new calls to {@link #load()} or {@link #defaultOverrides()} or * {@link #defaultReference} may return a new object. *

* This method is primarily intended for use in unit tests, for example, * that may want to update a system property then confirm that it's used * correctly. In many cases, use of this method may indicate there's a * better way to set up your code. *

* Caches may be reloaded immediately or lazily; once you call this method, * the reload can occur at any time, even during the invalidation process. * So FIRST make the changes you'd like the caches to notice, then SECOND * call this method to invalidate caches. Don't expect that invalidating, * making changes, then calling {@link #load()}, will work. Make changes * before you invalidate. */ public static void invalidateCaches() { // We rely on this having the side effect that it drops // all caches ConfigImpl.reloadSystemPropertiesConfig(); } /** * Gets an empty configuration. See also {@link #empty(String)} to create an * empty configuration with a description, which may improve user-visible * error messages. * * @return an empty configuration */ public static Config empty() { return empty(null); } /** * Gets an empty configuration with a description to be used to create a * {@link ConfigOrigin} for this Config. The description should * be very short and say what the configuration is, like "default settings" * or "foo settings" or something. (Presumably you will merge some actual * settings into this empty config using {@link Config#withFallback}, making * the description more useful.) * * @param originDescription * description of the config * @return an empty configuration */ public static Config empty(String originDescription) { return ConfigImpl.emptyConfig(originDescription); } /** * Gets a Config containing the system properties from * {@link java.lang.System#getProperties()}, parsed and converted as with * {@link #parseProperties}. *

* This method can return a global immutable singleton, so it's preferred * over parsing system properties yourself. *

* {@link #load} will include the system properties as overrides already, as * will {@link #defaultReference} and {@link #defaultOverrides}. * *

* Because this returns a singleton, it will not notice changes to system * properties made after the first time this method is called. Use * {@link #invalidateCaches()} to force the singleton to reload if you * modify system properties. * * @return system properties parsed into a Config */ public static Config systemProperties() { return ConfigImpl.systemPropertiesAsConfig(); } /** * Gets a Config containing the system's environment variables. * This method can return a global immutable singleton. * *

* Environment variables are used as fallbacks when resolving substitutions * whether or not this object is included in the config being resolved, so * you probably don't need to use this method for most purposes. It can be a * nicer API for accessing environment variables than raw * {@link java.lang.System#getenv(String)} though, since you can use methods * such as {@link Config#getInt}. * * @return system environment variables parsed into a Config */ public static Config systemEnvironment() { return ConfigImpl.envVariablesAsConfig(); } /** * Converts a Java {@link java.util.Properties} object to a * {@link ConfigObject} using the rules documented in the HOCON * spec. The keys in the Properties object are split on the * period character '.' and treated as paths. The values will all end up as * string values. If you have both "a=foo" and "a.b=bar" in your properties * file, so "a" is both the object containing "b" and the string "foo", then * the string value is dropped. * *

* If you want to have System.getProperties() as a * ConfigObject, it's better to use the {@link #systemProperties()} method * which returns a cached global singleton. * * @param properties * a Java Properties object * @param options * the parse options * @return the parsed configuration */ public static Config parseProperties(Properties properties, ConfigParseOptions options) { return Parseable.newProperties(properties, options).parse().toConfig(); } /** * Like {@link #parseProperties(Properties, ConfigParseOptions)} but uses default * parse options. * @param properties * a Java Properties object * @return the parsed configuration */ public static Config parseProperties(Properties properties) { return parseProperties(properties, ConfigParseOptions.defaults()); } /** * Parses a Reader into a Config instance. Does not call * {@link Config#resolve} or merge the parsed stream with any * other configuration; this method parses a single stream and * does nothing else. It does process "include" statements in * the parsed stream, and may end up doing other IO due to those * statements. * * @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 Config parseReader(Reader reader, ConfigParseOptions options) { return Parseable.newReader(reader, options).parse().toConfig(); } /** * 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 Config parseReader(Reader reader) { return parseReader(reader, ConfigParseOptions.defaults()); } /** * Parses a URL into a Config instance. Does not call * {@link Config#resolve} or merge the parsed stream with any * other configuration; this method parses a single stream and * does nothing else. It does process "include" statements in * the parsed stream, and may end up doing other IO due to those * statements. * * @param url * the url to parse * @param options * parse options to control how the url is interpreted * @return the parsed configuration * @throws ConfigException on IO or parse errors */ public static Config parseURL(URL url, ConfigParseOptions options) { return Parseable.newURL(url, options).parse().toConfig(); } /** * Parses a url into a Config instance as with * {@link #parseURL(URL,ConfigParseOptions)} but always uses the * default parse options. * * @param url * the url to parse * @return the parsed configuration * @throws ConfigException on IO or parse errors */ public static Config parseURL(URL url) { return parseURL(url, ConfigParseOptions.defaults()); } /** * Parses a file into a Config instance. Does not call * {@link Config#resolve} or merge the file with any other * configuration; this method parses a single file and does * nothing else. It does process "include" statements in the * parsed file, and may end up doing other IO due to those * statements. * * @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 Config parseFile(File file, ConfigParseOptions options) { return Parseable.newFile(file, options).parse().toConfig(); } /** * Parses a file into a Config 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 Config parseFile(File file) { return parseFile(file, ConfigParseOptions.defaults()); } /** * Parses a file with a flexible extension. If the fileBasename * already ends in a known extension, this method parses it according to * that extension (the file's syntax must match its extension). If the * fileBasename does not end in an extension, it parses files * with all known extensions and merges whatever is found. * *

* In the current implementation, the extension ".conf" forces * {@link ConfigSyntax#CONF}, ".json" forces {@link ConfigSyntax#JSON}, and * ".properties" forces {@link ConfigSyntax#PROPERTIES}. When merging files, * ".conf" falls back to ".json" falls back to ".properties". * *

* Future versions of the implementation may add additional syntaxes or * additional extensions. However, the ordering (fallback priority) of the * three current extensions will remain the same. * *

* If options forces a specific syntax, this method only parses * files with an extension matching that syntax. * *

* If {@link ConfigParseOptions#getAllowMissing options.getAllowMissing()} * is true, then no files have to exist; if false, then at least one file * has to exist. * * @param fileBasename * a filename with or without extension * @param options * parse options * @return the parsed configuration */ public static Config parseFileAnySyntax(File fileBasename, ConfigParseOptions options) { return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig(); } /** * Like {@link #parseFileAnySyntax(File,ConfigParseOptions)} but always uses * default parse options. * * @param fileBasename * a filename with or without extension * @return the parsed configuration */ public static Config parseFileAnySyntax(File fileBasename) { return parseFileAnySyntax(fileBasename, ConfigParseOptions.defaults()); } /** * Parses all resources on the classpath with the given name and merges them * into a single Config. * *

* If the resource name does not begin with a "/", it will have the supplied * class's package added to it, in the same way as * {@link java.lang.Class#getResource}. * *

* Duplicate resources with the same name are merged such that ones returned * earlier from {@link ClassLoader#getResources} fall back to (have higher * priority than) the ones returned later. This implies that resources * earlier in the classpath override those later in the classpath when they * configure the same setting. However, in practice real applications may * not be consistent about classpath ordering, so be careful. It may be best * to avoid assuming too much. * * @param klass * klass.getClassLoader() will be used to load * resources, and non-absolute resource names will have this * class's package added * @param resource * resource to look up, relative to klass's package * or absolute starting with a "/" * @param options * parse options * @return the parsed configuration */ public static Config parseResources(Class klass, String resource, ConfigParseOptions options) { return Parseable.newResources(klass, resource, options).parse() .toConfig(); } /** * Like {@link #parseResources(Class,String,ConfigParseOptions)} but always uses * default parse options. * * @param klass * klass.getClassLoader() will be used to load * resources, and non-absolute resource names will have this * class's package added * @param resource * resource to look up, relative to klass's package * or absolute starting with a "/" * @return the parsed configuration */ public static Config parseResources(Class klass, String resource) { return parseResources(klass, resource, ConfigParseOptions.defaults()); } /** * Parses classpath resources with a flexible extension. In general, this * method has the same behavior as * {@link #parseFileAnySyntax(File,ConfigParseOptions)} but for classpath * resources instead, as in {@link #parseResources}. * *

* There is a thorny problem with this method, which is that * {@link java.lang.ClassLoader#getResources} must be called separately for * each possible extension. The implementation ends up with separate lists * of resources called "basename.conf" and "basename.json" for example. As a * result, the ideal ordering between two files with different extensions is * unknown; there is no way to figure out how to merge the two lists in * classpath order. To keep it simple, the lists are simply concatenated, * with the same syntax priorities as * {@link #parseFileAnySyntax(File,ConfigParseOptions) parseFileAnySyntax()} * - all ".conf" resources are ahead of all ".json" resources which are * ahead of all ".properties" resources. * * @param klass * class which determines the ClassLoader and the * package for relative resource names * @param resourceBasename * a resource name as in {@link java.lang.Class#getResource}, * with or without extension * @param options * parse options (class loader is ignored in favor of the one * from klass) * @return the parsed configuration */ public static Config parseResourcesAnySyntax(Class klass, String resourceBasename, ConfigParseOptions options) { return ConfigImpl.parseResourcesAnySyntax(klass, resourceBasename, options).toConfig(); } /** * Like {@link #parseResourcesAnySyntax(Class,String,ConfigParseOptions)} * but always uses default parse options. * * @param klass * klass.getClassLoader() will be used to load * resources, and non-absolute resource names will have this * class's package added * @param resourceBasename * a resource name as in {@link java.lang.Class#getResource}, * with or without extension * @return the parsed configuration */ public static Config parseResourcesAnySyntax(Class klass, String resourceBasename) { return parseResourcesAnySyntax(klass, resourceBasename, ConfigParseOptions.defaults()); } /** * Parses all resources on the classpath with the given name and merges them * into a single Config. * *

* This works like {@link java.lang.ClassLoader#getResource}, not like * {@link java.lang.Class#getResource}, so the name never begins with a * slash. * *

* See {@link #parseResources(Class,String,ConfigParseOptions)} for full * details. * * @param loader * will be used to load resources by setting this loader on the * provided options * @param resource * resource to look up * @param options * parse options (class loader is ignored) * @return the parsed configuration */ public static Config parseResources(ClassLoader loader, String resource, ConfigParseOptions options) { return parseResources(resource, options.setClassLoader(loader)); } /** * Like {@link #parseResources(ClassLoader,String,ConfigParseOptions)} but always uses * default parse options. * * @param loader * will be used to load resources * @param resource * resource to look up in the loader * @return the parsed configuration */ public static Config parseResources(ClassLoader loader, String resource) { return parseResources(loader, resource, ConfigParseOptions.defaults()); } /** * Parses classpath resources with a flexible extension. In general, this * method has the same behavior as * {@link #parseFileAnySyntax(File,ConfigParseOptions)} but for classpath * resources instead, as in * {@link #parseResources(ClassLoader,String,ConfigParseOptions)}. * *

* {@link #parseResourcesAnySyntax(Class,String,ConfigParseOptions)} differs * in the syntax for the resource name, but otherwise see * {@link #parseResourcesAnySyntax(Class,String,ConfigParseOptions)} for * some details and caveats on this method. * * @param loader * class loader to look up resources in, will be set on options * @param resourceBasename * a resource name as in * {@link java.lang.ClassLoader#getResource}, with or without * extension * @param options * parse options (class loader ignored) * @return the parsed configuration */ public static Config parseResourcesAnySyntax(ClassLoader loader, String resourceBasename, ConfigParseOptions options) { return ConfigImpl.parseResourcesAnySyntax(resourceBasename, options.setClassLoader(loader)) .toConfig(); } /** * Like {@link #parseResourcesAnySyntax(ClassLoader,String,ConfigParseOptions)} but always uses * default parse options. * * @param loader * will be used to load resources * @param resourceBasename * a resource name as in * {@link java.lang.ClassLoader#getResource}, with or without * extension * @return the parsed configuration */ public static Config parseResourcesAnySyntax(ClassLoader loader, String resourceBasename) { return parseResourcesAnySyntax(loader, resourceBasename, ConfigParseOptions.defaults()); } /** * Like {@link #parseResources(ClassLoader,String,ConfigParseOptions)} but * uses thread's current context class loader if none is set in the * ConfigParseOptions. * @param resource the resource name * @param options parse options * @return the parsed configuration */ public static Config parseResources(String resource, ConfigParseOptions options) { ConfigParseOptions withLoader = ensureClassLoader(options, "parseResources"); return Parseable.newResources(resource, withLoader).parse().toConfig(); } /** * Like {@link #parseResources(ClassLoader,String)} but uses thread's * current context class loader. * @param resource the resource name * @return the parsed configuration */ public static Config parseResources(String resource) { return parseResources(resource, ConfigParseOptions.defaults()); } /** * Like * {@link #parseResourcesAnySyntax(ClassLoader,String,ConfigParseOptions)} * but uses thread's current context class loader. * @param resourceBasename the resource basename (no file type suffix) * @param options parse options * @return the parsed configuration */ public static Config parseResourcesAnySyntax(String resourceBasename, ConfigParseOptions options) { return ConfigImpl.parseResourcesAnySyntax(resourceBasename, options).toConfig(); } /** * Like {@link #parseResourcesAnySyntax(ClassLoader,String)} but uses * thread's current context class loader. * @param resourceBasename the resource basename (no file type suffix) * @return the parsed configuration */ public static Config parseResourcesAnySyntax(String resourceBasename) { return parseResourcesAnySyntax(resourceBasename, ConfigParseOptions.defaults()); } /** * Parses a string (which should be valid HOCON or JSON by default, or * the syntax specified in the options otherwise). * * @param s string to parse * @param options parse options * @return the parsed configuration */ public static Config parseString(String s, ConfigParseOptions options) { return Parseable.newString(s, options).parse().toConfig(); } /** * Parses a string (which should be valid HOCON or JSON). * * @param s string to parse * @return the parsed configuration */ public static Config parseString(String s) { return parseString(s, ConfigParseOptions.defaults()); } /** * Creates a {@code Config} based on a {@link java.util.Map} from paths to * plain Java values. Similar to * {@link ConfigValueFactory#fromMap(Map,String)}, except the keys in the * map are path expressions, rather than keys; and correspondingly it * returns a {@code Config} instead of a {@code ConfigObject}. This is more * convenient if you are writing literal maps in code, and less convenient * if you are getting your maps from some data source such as a parser. * *

* An exception will be thrown (and it is a bug in the caller of the method) * if a path is both an object and a value, for example if you had both * "a=foo" and "a.b=bar", then "a" is both the string "foo" and the parent * object of "b". The caller of this method should ensure that doesn't * happen. * * @param values map from paths to plain Java objects * @param originDescription * description of what this map represents, like a filename, or * "default settings" (origin description is used in error * messages) * @return the map converted to a {@code Config} */ public static Config parseMap(Map values, String originDescription) { return ConfigImpl.fromPathMap(values, originDescription).toConfig(); } /** * See the other overload of {@link #parseMap(Map, String)} for details, * this one just uses a default origin description. * * @param values map from paths to plain Java values * @return the map converted to a {@code Config} */ public static Config parseMap(Map values) { return parseMap(values, null); } private static ConfigLoadingStrategy getConfigLoadingStrategy() { String className = System.getProperties().getProperty(STRATEGY_PROPERTY_NAME); if (className != null) { try { return ConfigLoadingStrategy.class.cast(Class.forName(className).newInstance()); } catch (Throwable e) { throw new ConfigException.BugOrBroken("Failed to load strategy: " + className, e); } } else { return new DefaultConfigLoadingStrategy(); } } } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigIncludeContext.java000066400000000000000000000037521277147274600274760ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; /** * Context provided to a {@link ConfigIncluder}; this interface is only useful * inside a {@code ConfigIncluder} implementation, and is not intended for apps * to implement. * *

* Do not implement this interface; 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 ConfigIncludeContext { /** * Tries to find a name relative to whatever is doing the including, for * example in the same directory as the file doing the including. Returns * null if it can't meaningfully create a relative name. The returned * parseable may not exist; this function is not required to do any IO, just * compute what the name would be. * * The passed-in filename has to be a complete name (with extension), not * just a basename. (Include statements in config files are allowed to give * just a basename.) * * @param filename * the name to make relative to the resource doing the including * @return parseable item relative to the resource doing the including, or * null */ ConfigParseable relativeTo(String filename); /** * Parse options to use (if you use another method to get a * {@link ConfigParseable} then use {@link ConfigParseable#options()} * instead though). * * @return the parse options */ ConfigParseOptions parseOptions(); /** * Copy this {@link ConfigIncludeContext} giving it a new value for its parseOptions. * * @param options new parse options to use * * @return the updated copy of this context */ ConfigIncludeContext setParseOptions(ConfigParseOptions options); } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigIncluder.java000066400000000000000000000042131277147274600263040ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; /** * Implement this interface and provide an instance to * {@link ConfigParseOptions#setIncluder ConfigParseOptions.setIncluder()} to * customize handling of {@code include} statements in config files. You may * also want to implement {@link ConfigIncluderClasspath}, * {@link ConfigIncluderFile}, and {@link ConfigIncluderURL}, or not. */ public interface ConfigIncluder { /** * Returns a new includer that falls back to the given includer. This is how * you can obtain the default includer; it will be provided as a fallback. * It's up to your includer to chain to it if you want to. You might want to * merge any files found by the fallback includer with any objects you load * yourself. * * It's important to handle the case where you already have the fallback * with a "return this", i.e. this method should not create a new object if * the fallback is the same one you already have. The same fallback may be * added repeatedly. * * @param fallback the previous includer for chaining * @return a new includer */ ConfigIncluder withFallback(ConfigIncluder fallback); /** * Parses another item to be included. The returned object typically would * not have substitutions resolved. You can throw a ConfigException here to * abort parsing, or return an empty object, but may not return null. * * This method is used for a "heuristic" include statement that does not * specify file, URL, or classpath resource. If the include statement does * specify, then the same class implementing {@link ConfigIncluder} must * also implement {@link ConfigIncluderClasspath}, * {@link ConfigIncluderFile}, or {@link ConfigIncluderURL} as needed, or a * default includer will be used. * * @param context * some info about the include context * @param what * the include statement's argument * @return a non-null ConfigObject */ ConfigObject include(ConfigIncludeContext context, String what); } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigIncluderClasspath.java000066400000000000000000000017301277147274600301500ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; /** * Implement this in addition to {@link ConfigIncluder} if you want to * support inclusion of files with the {@code include classpath("resource")} * syntax. If you do not implement this but do implement {@link ConfigIncluder}, * attempts to load classpath resources will use the default includer. */ public interface ConfigIncluderClasspath { /** * Parses another item to be included. The returned object typically would * not have substitutions resolved. You can throw a ConfigException here to * abort parsing, or return an empty object, but may not return null. * * @param context * some info about the include context * @param what * the include statement's argument * @return a non-null ConfigObject */ ConfigObject includeResources(ConfigIncludeContext context, String what); } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigIncluderFile.java000066400000000000000000000017171277147274600271120ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; import java.io.File; /** * Implement this in addition to {@link ConfigIncluder} if you want to * support inclusion of files with the {@code include file("filename")} syntax. * If you do not implement this but do implement {@link ConfigIncluder}, * attempts to load files will use the default includer. */ public interface ConfigIncluderFile { /** * Parses another item to be included. The returned object typically would * not have substitutions resolved. You can throw a ConfigException here to * abort parsing, or return an empty object, but may not return null. * * @param context * some info about the include context * @param what * the include statement's argument * @return a non-null ConfigObject */ ConfigObject includeFile(ConfigIncludeContext context, File what); } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigIncluderURL.java000066400000000000000000000017241277147274600266730ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; import java.net.URL; /** * Implement this in addition to {@link ConfigIncluder} if you want to * support inclusion of files with the {@code include url("http://example.com")} * syntax. If you do not implement this but do implement {@link ConfigIncluder}, * attempts to load URLs will use the default includer. */ public interface ConfigIncluderURL { /** * Parses another item to be included. The returned object typically would * not have substitutions resolved. You can throw a ConfigException here to * abort parsing, or return an empty object, but may not return null. * * @param context * some info about the include context * @param what * the include statement's argument * @return a non-null ConfigObject */ ConfigObject includeURL(ConfigIncludeContext context, URL what); } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigList.java000066400000000000000000000030551277147274600254550ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; import java.util.List; /** * Subtype of {@link ConfigValue} representing a list value, as in JSON's * {@code [1,2,3]} syntax. * *

* {@code ConfigList} implements {@code java.util.List} so you can * use it like a regular Java list. Or call {@link #unwrapped()} to unwrap the * list elements into plain Java values. * *

* Like all {@link ConfigValue} subtypes, {@code ConfigList} is immutable. This * makes it threadsafe and you never have to create "defensive copies." The * mutator methods from {@link java.util.List} all throw * {@link java.lang.UnsupportedOperationException}. * *

* The {@link ConfigValue#valueType} method on a list returns * {@link ConfigValueType#LIST}. * *

* Do not implement {@code ConfigList}; 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 ConfigList extends List, ConfigValue { /** * Recursively unwraps the list, returning a list of plain Java values such * as Integer or String or whatever is in the list. * * @return a {@link java.util.List} containing plain Java objects */ @Override List unwrapped(); @Override ConfigList withOrigin(ConfigOrigin origin); } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigLoadingStrategy.java000066400000000000000000000013121277147274600276340ustar00rootroot00000000000000package com.typesafe.config; /** * This method allows you to alter default config loading strategy for all the code which * calls {@link ConfigFactory#load}. * * Usually you don't have to implement this interface but it may be required * when you fixing a improperly implemented library with unavailable source code. * * You have to define VM property {@code config.strategy} to replace default strategy with your own. */ public interface ConfigLoadingStrategy { /** * This method must load and parse application config. * * @param parseOptions {@link ConfigParseOptions} to use * @return loaded config */ Config parseApplicationConfig(ConfigParseOptions parseOptions); } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigMemorySize.java000066400000000000000000000030071277147274600266420ustar00rootroot00000000000000/** * Copyright (C) 2015 Typesafe Inc. */ package com.typesafe.config; /** * An immutable class representing an amount of memory. Use * static factory methods such as {@link * ConfigMemorySize#ofBytes(long)} to create instances. * * @since 1.3.0 */ public final class ConfigMemorySize { private final long bytes; private ConfigMemorySize(long bytes) { if (bytes < 0) throw new IllegalArgumentException("Attempt to construct ConfigMemorySize with negative number: " + bytes); this.bytes = bytes; } /** * Constructs a ConfigMemorySize representing the given * number of bytes. * @since 1.3.0 * @param bytes a number of bytes * @return an instance representing the number of bytes */ public static ConfigMemorySize ofBytes(long bytes) { return new ConfigMemorySize(bytes); } /** * Gets the size in bytes. * @since 1.3.0 * @return how many bytes */ public long toBytes() { return bytes; } @Override public String toString() { return "ConfigMemorySize(" + bytes + ")"; } @Override public boolean equals(Object other) { if (other instanceof ConfigMemorySize) { return ((ConfigMemorySize)other).bytes == this.bytes; } else { return false; } } @Override public int hashCode() { // in Java 8 this can become Long.hashCode(bytes) return Long.valueOf(bytes).hashCode(); } } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigMergeable.java000066400000000000000000000053601277147274600264260ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; /** * Marker for types whose instances can be merged, that is {@link Config} and * {@link ConfigValue}. Instances of {@code Config} and {@code ConfigValue} can * be combined into a single new instance using the * {@link ConfigMergeable#withFallback withFallback()} method. * *

* Do not implement this interface; 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 ConfigMergeable { /** * Returns a new value computed by merging this value with another, with * keys in this value "winning" over the other one. * *

* This associative operation may be used to combine configurations from * multiple sources (such as multiple configuration files). * *

* The semantics of merging are described in the spec * for HOCON. Merging typically occurs when either the same object is * created twice in the same file, or two config files are both loaded. For * example: * *

     *  foo = { a: 42 }
     *  foo = { b: 43 }
     * 
* * Here, the two objects are merged as if you had written: * *
     *  foo = { a: 42, b: 43 }
     * 
* *

* Only {@link ConfigObject} and {@link Config} instances do anything in * this method (they need to merge the fallback keys into themselves). All * other values just return the original value, since they automatically * override any fallback. This means that objects do not merge "across" * non-objects; if you write * object.withFallback(nonObject).withFallback(otherObject), * then otherObject will simply be ignored. This is an * intentional part of how merging works, because non-objects such as * strings and integers replace (rather than merging with) any prior value: * *

     * foo = { a: 42 }
     * foo = 10
     * 
* * Here, the number 10 "wins" and the value of foo would be * simply 10. Again, for details see the spec. * * @param other * an object whose keys should be used as fallbacks, if the keys * are not present in this one * @return a new object (or the original one, if the fallback doesn't get * used) */ ConfigMergeable withFallback(ConfigMergeable other); } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigObject.java000066400000000000000000000113641277147274600257520ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; import java.util.Map; /** * Subtype of {@link ConfigValue} representing an object (AKA dictionary or map) * value, as in JSON's curly brace { "a" : 42 } syntax. * *

* An object may also be viewed as a {@link Config} by calling * {@link ConfigObject#toConfig()}. * *

* {@code ConfigObject} implements {@code java.util.Map} 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}. * *

* 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}. * *

* The {@link ConfigValue#valueType} method on an object returns * {@link ConfigValueType#OBJECT}. * *

* 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}. * *

* 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 Config} is a one-level map from paths to values. * *

* Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert * between path expressions and individual path elements (keys). * *

* A {@code ConfigObject} may contain null values, which will have * {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If * {@link ConfigObject#get(Object)} returns Java's null then the key was not * present in the parsed file (or wherever this value tree came from). If * {@code get("key")} returns a {@link ConfigValue} with type * {@code ConfigValueType#NULL} then the key was set to null explicitly in the * config file. * *

* Do not implement interface {@code ConfigObject}; 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 ConfigObject extends ConfigValue, Map { /** * Converts this object to a {@link Config} instance, enabling you to use * path expressions to find values in the object. This is a constant-time * operation (it is not proportional to the size of the object). * * @return a {@link Config} with this object as its root */ Config toConfig(); /** * Recursively unwraps the object, returning a map from String to whatever * plain Java values are unwrapped from the object's values. * * @return a {@link java.util.Map} containing plain Java objects */ @Override Map unwrapped(); @Override ConfigObject withFallback(ConfigMergeable other); /** * Gets a {@link ConfigValue} at the given key, or returns null if there is * no value. The returned {@link ConfigValue} may have * {@link ConfigValueType#NULL} or any other type, and the passed-in key * must be a key in this object (rather than a path expression). * * @param key * key to look up * * @return the value at the key or null if none */ @Override ConfigValue get(Object key); /** * Clone the object with only the given key (and its children) retained; all * sibling keys are removed. * * @param key * key to keep * @return a copy of the object minus all keys except the one specified */ ConfigObject withOnlyKey(String key); /** * Clone the object with the given key removed. * * @param key * key to remove * @return a copy of the object minus the specified key */ ConfigObject withoutKey(String key); /** * Returns a {@code ConfigObject} based on this one, but with the given key * set to the given value. Does not modify this instance (since it's * immutable). If the key already has a value, that value is replaced. To * remove a value, use {@link ConfigObject#withoutKey(String)}. * * @param key * key to add * @param value * value at the new key * @return the new instance with the new map entry */ ConfigObject withValue(String key, ConfigValue value); @Override ConfigObject withOrigin(ConfigOrigin origin); } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigOrigin.java000066400000000000000000000101641277147274600257700ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; import java.net.URL; import java.util.List; /** * Represents the origin (such as filename and line number) of a * {@link ConfigValue} for use in error messages. Obtain the origin of a value * with {@link ConfigValue#origin}. Exceptions may have an origin, see * {@link ConfigException#origin}, but be careful because * ConfigException.origin() may return null. * *

* It's best to use this interface only for debugging; its accuracy is * "best effort" rather than guaranteed, and a potentially-noticeable amount of * memory could probably be saved if origins were not kept around, so in the * future there might be some option to discard origins. * *

* Do not implement this interface; 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 ConfigOrigin { /** * Returns a string describing the origin of a value or exception. This will * never return null. * * @return string describing the origin */ public String description(); /** * Returns a filename describing the origin. This will return null if the * origin was not a file. * * @return filename of the origin or null */ public String filename(); /** * Returns a URL describing the origin. This will return null if the origin * has no meaningful URL. * * @return url of the origin or null */ public URL url(); /** * Returns a classpath resource name describing the origin. This will return * null if the origin was not a classpath resource. * * @return resource name of the origin or null */ public String resource(); /** * Returns a line number where the value or exception originated. This will * return -1 if there's no meaningful line number. * * @return line number or -1 if none is available */ public int lineNumber(); /** * Returns any comments that appeared to "go with" this place in the file. * Often an empty list, but never null. The details of this are subject to * change, but at the moment comments that are immediately before an array * element or object field, with no blank line after the comment, "go with" * that element or field. * * @return any comments that seemed to "go with" this origin, empty list if * none */ public List comments(); /** * Returns a {@code ConfigOrigin} based on this one, but with the given * comments. Does not modify this instance or any {@code ConfigValue}s with * this origin (since they are immutable). To set the returned origin to a * {@code ConfigValue}, use {@link ConfigValue#withOrigin}. * *

* Note that when the given comments are equal to the comments on this object, * a new instance may not be created and {@code this} is returned directly. * * @since 1.3.0 * * @param comments the comments used on the returned origin * @return the ConfigOrigin with the given comments */ public ConfigOrigin withComments(List comments); /** * Returns a {@code ConfigOrigin} based on this one, but with the given * line number. This origin must be a FILE, URL or RESOURCE. Does not modify * this instance or any {@code ConfigValue}s with this origin (since they are * immutable). To set the returned origin to a {@code ConfigValue}, use * {@link ConfigValue#withOrigin}. * *

* Note that when the given lineNumber are equal to the lineNumber on this * object, a new instance may not be created and {@code this} is returned * directly. * * @since 1.3.0 * * @param lineNumber the new line number * @return the created ConfigOrigin */ public ConfigOrigin withLineNumber(int lineNumber); } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigOriginFactory.java000066400000000000000000000034041277147274600273170ustar00rootroot00000000000000package com.typesafe.config; import java.net.URL; import com.typesafe.config.impl.ConfigImpl; /** * This class contains some static factory methods for building a {@link * ConfigOrigin}. {@code ConfigOrigin}s are automatically created when you * call other API methods to get a {@code ConfigValue} or {@code Config}. * But you can also set the origin of an existing {@code ConfigValue}, using * {@link ConfigValue#withOrigin(ConfigOrigin)}. * * @since 1.3.0 */ public final class ConfigOriginFactory { private ConfigOriginFactory() { } /** * Returns the default origin for values when no other information is * provided. This is the origin used in {@link ConfigValueFactory * #fromAnyRef(Object)}. * * @since 1.3.0 * * @return the default origin */ public static ConfigOrigin newSimple() { return newSimple(null); } /** * Returns an origin with the given description. * * @since 1.3.0 * * @param description brief description of what the origin is * @return a new origin */ public static ConfigOrigin newSimple(String description) { return ConfigImpl.newSimpleOrigin(description); } /** * Creates a file origin with the given filename. * * @since 1.3.0 * * @param filename the filename of this origin * @return a new origin */ public static ConfigOrigin newFile(String filename) { return ConfigImpl.newFileOrigin(filename); } /** * Creates a url origin with the given URL object. * * @since 1.3.0 * * @param url the url of this origin * @return a new origin */ public static ConfigOrigin newURL(URL url) { return ConfigImpl.newURLOrigin(url); } } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigParseOptions.java000066400000000000000000000177701277147274600272010ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; /** * A set of options related to parsing. * *

* This object is immutable, so the "setters" return a new object. * *

* Here is an example of creating a custom {@code ConfigParseOptions}: * *

 *     ConfigParseOptions options = ConfigParseOptions.defaults()
 *         .setSyntax(ConfigSyntax.JSON)
 *         .setAllowMissing(false)
 * 
* */ public final class ConfigParseOptions { final ConfigSyntax syntax; final String originDescription; final boolean allowMissing; final ConfigIncluder includer; final ClassLoader classLoader; private ConfigParseOptions(ConfigSyntax syntax, String originDescription, boolean allowMissing, ConfigIncluder includer, ClassLoader classLoader) { this.syntax = syntax; this.originDescription = originDescription; this.allowMissing = allowMissing; this.includer = includer; this.classLoader = classLoader; } /** * Gets an instance of ConfigParseOptions with all fields * set to the default values. Start with this instance and make any * changes you need. * @return the default parse options */ public static ConfigParseOptions defaults() { return new ConfigParseOptions(null, null, true, null, null); } /** * Set the file format. If set to null, try to guess from any available * filename extension; if guessing fails, assume {@link ConfigSyntax#CONF}. * * @param syntax * a syntax or {@code null} for best guess * @return options with the syntax set */ public ConfigParseOptions setSyntax(ConfigSyntax syntax) { if (this.syntax == syntax) return this; else return new ConfigParseOptions(syntax, this.originDescription, this.allowMissing, this.includer, this.classLoader); } /** * Gets the current syntax option, which may be null for "any". * @return the current syntax or null */ public ConfigSyntax getSyntax() { return syntax; } /** * Set a description for the thing being parsed. In most cases this will be * set up for you to something like the filename, but if you provide just an * input stream you might want to improve on it. Set to null to allow the * library to come up with something automatically. This description is the * basis for the {@link ConfigOrigin} of the parsed values. * * @param originDescription description to put in the {@link ConfigOrigin} * @return options with the origin description set */ public ConfigParseOptions setOriginDescription(String originDescription) { // findbugs complains about == here but is wrong, do not "fix" if (this.originDescription == originDescription) return this; else if (this.originDescription != null && originDescription != null && this.originDescription.equals(originDescription)) return this; else return new ConfigParseOptions(this.syntax, originDescription, this.allowMissing, this.includer, this.classLoader); } /** * Gets the current origin description, which may be null for "automatic". * @return the current origin description or null */ public String getOriginDescription() { return originDescription; } /** this is package-private, not public API */ ConfigParseOptions withFallbackOriginDescription(String originDescription) { if (this.originDescription == null) return setOriginDescription(originDescription); else return this; } /** * Set to false to throw an exception if the item being parsed (for example * a file) is missing. Set to true to just return an empty document in that * case. Note that this setting applies on only to fetching the root document, * it has no effect on any nested includes. * * @param allowMissing true to silently ignore missing item * @return options with the "allow missing" flag set */ public ConfigParseOptions setAllowMissing(boolean allowMissing) { if (this.allowMissing == allowMissing) return this; else return new ConfigParseOptions(this.syntax, this.originDescription, allowMissing, this.includer, this.classLoader); } /** * Gets the current "allow missing" flag. * @return whether we allow missing files */ public boolean getAllowMissing() { return allowMissing; } /** * Set a {@link ConfigIncluder} which customizes how includes are handled. * null means to use the default includer. * * @param includer the includer to use or null for default * @return new version of the parse options with different includer */ public ConfigParseOptions setIncluder(ConfigIncluder includer) { if (this.includer == includer) return this; else return new ConfigParseOptions(this.syntax, this.originDescription, this.allowMissing, includer, this.classLoader); } /** * Prepends a {@link ConfigIncluder} which customizes how * includes are handled. To prepend your includer, the * library calls {@link ConfigIncluder#withFallback} on your * includer to append the existing includer to it. * * @param includer the includer to prepend (may not be null) * @return new version of the parse options with different includer */ public ConfigParseOptions prependIncluder(ConfigIncluder includer) { if (includer == null) throw new NullPointerException("null includer passed to prependIncluder"); if (this.includer == includer) return this; else if (this.includer != null) return setIncluder(includer.withFallback(this.includer)); else return setIncluder(includer); } /** * Appends a {@link ConfigIncluder} which customizes how * includes are handled. To append, the library calls {@link * ConfigIncluder#withFallback} on the existing includer. * * @param includer the includer to append (may not be null) * @return new version of the parse options with different includer */ public ConfigParseOptions appendIncluder(ConfigIncluder includer) { if (includer == null) throw new NullPointerException("null includer passed to appendIncluder"); if (this.includer == includer) return this; else if (this.includer != null) return setIncluder(this.includer.withFallback(includer)); else return setIncluder(includer); } /** * Gets the current includer (will be null for the default includer). * @return current includer or null */ public ConfigIncluder getIncluder() { return includer; } /** * Set the class loader. If set to null, * Thread.currentThread().getContextClassLoader() will be used. * * @param loader * a class loader or {@code null} to use thread context class * loader * @return options with the class loader set */ public ConfigParseOptions setClassLoader(ClassLoader loader) { if (this.classLoader == loader) return this; else return new ConfigParseOptions(this.syntax, this.originDescription, this.allowMissing, this.includer, loader); } /** * Get the class loader; never returns {@code null}, if the class loader was * unset, returns * Thread.currentThread().getContextClassLoader(). * * @return class loader to use */ public ClassLoader getClassLoader() { if (this.classLoader == null) return Thread.currentThread().getContextClassLoader(); else return this.classLoader; } } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigParseable.java000066400000000000000000000026511277147274600264410ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; /** * An opaque handle to something that can be parsed, obtained from * {@link ConfigIncludeContext}. * *

* Do not implement this interface; 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 ConfigParseable { /** * Parse whatever it is. The options should come from * {@link ConfigParseable#options options()} but you could tweak them if you * like. * * @param options * parse options, should be based on the ones from * {@link ConfigParseable#options options()} * @return the parsed object */ ConfigObject parse(ConfigParseOptions options); /** * Returns a {@link ConfigOrigin} describing the origin of the parseable * item. * @return the origin of the parseable item */ ConfigOrigin origin(); /** * Get the initial options, which can be modified then passed to parse(). * These options will have the right description, includer, and other * parameters already set up. * @return the initial options */ ConfigParseOptions options(); } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigRenderOptions.java000066400000000000000000000137321277147274600273400ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; /** *

* A set of options related to rendering a {@link ConfigValue}. Passed to * {@link ConfigValue#render(ConfigRenderOptions)}. * *

* Here is an example of creating a {@code ConfigRenderOptions}: * *

 *     ConfigRenderOptions options =
 *         ConfigRenderOptions.defaults().setComments(false)
 * 
*/ public final class ConfigRenderOptions { private final boolean originComments; private final boolean comments; private final boolean formatted; private final boolean json; private ConfigRenderOptions(boolean originComments, boolean comments, boolean formatted, boolean json) { this.originComments = originComments; this.comments = comments; this.formatted = formatted; this.json = json; } /** * Returns the default render options which are verbose (commented and * formatted). See {@link ConfigRenderOptions#concise} for stripped-down * options. This rendering will not be valid JSON since it has comments. * * @return the default render options */ public static ConfigRenderOptions defaults() { return new ConfigRenderOptions(true, true, true, true); } /** * Returns concise render options (no whitespace or comments). For a * resolved {@link Config}, the concise rendering will be valid JSON. * * @return the concise render options */ public static ConfigRenderOptions concise() { return new ConfigRenderOptions(false, false, false, true); } /** * Returns options with comments toggled. This controls human-written * comments but not the autogenerated "origin of this setting" comments, * which are controlled by {@link ConfigRenderOptions#setOriginComments}. * * @param value * true to include comments in the render * @return options with requested setting for comments */ public ConfigRenderOptions setComments(boolean value) { if (value == comments) return this; else return new ConfigRenderOptions(originComments, value, formatted, json); } /** * Returns whether the options enable comments. This method is mostly used * by the config lib internally, not by applications. * * @return true if comments should be rendered */ public boolean getComments() { return comments; } /** * Returns options with origin comments toggled. If this is enabled, the * library generates comments for each setting based on the * {@link ConfigValue#origin} of that setting's value. For example these * comments might tell you which file a setting comes from. * *

* {@code setOriginComments()} controls only these autogenerated * "origin of this setting" comments, to toggle regular comments use * {@link ConfigRenderOptions#setComments}. * * @param value * true to include autogenerated setting-origin comments in the * render * @return options with origin comments toggled */ public ConfigRenderOptions setOriginComments(boolean value) { if (value == originComments) return this; else return new ConfigRenderOptions(value, comments, formatted, json); } /** * Returns whether the options enable automated origin comments. This method * is mostly used by the config lib internally, not by applications. * * @return true if origin comments should be rendered */ public boolean getOriginComments() { return originComments; } /** * Returns options with formatting toggled. Formatting means indentation and * whitespace, enabling formatting makes things prettier but larger. * * @param value * true to enable formatting * @return options with requested setting for formatting */ public ConfigRenderOptions setFormatted(boolean value) { if (value == formatted) return this; else return new ConfigRenderOptions(originComments, comments, value, json); } /** * Returns whether the options enable formatting. This method is mostly used * by the config lib internally, not by applications. * * @return true if the options enable formatting */ public boolean getFormatted() { return formatted; } /** * Returns options with JSON toggled. JSON means that HOCON extensions * (omitting commas, quotes for example) won't be used. However, whether to * use comments is controlled by the separate {@link #setComments(boolean)} * and {@link #setOriginComments(boolean)} options. So if you enable * comments you will get invalid JSON despite setting this to true. * * @param value * true to include non-JSON extensions in the render * @return options with requested setting for JSON */ public ConfigRenderOptions setJson(boolean value) { if (value == json) return this; else return new ConfigRenderOptions(originComments, comments, formatted, value); } /** * Returns whether the options enable JSON. This method is mostly used by * the config lib internally, not by applications. * * @return true if only JSON should be rendered */ public boolean getJson() { return json; } @Override public String toString() { StringBuilder sb = new StringBuilder("ConfigRenderOptions("); if (originComments) sb.append("originComments,"); if (comments) sb.append("comments,"); if (formatted) sb.append("formatted,"); if (json) sb.append("json,"); if (sb.charAt(sb.length() - 1) == ',') sb.setLength(sb.length() - 1); sb.append(")"); return sb.toString(); } } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigResolveOptions.java000066400000000000000000000073701277147274600275410ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; /** * A set of options related to resolving substitutions. Substitutions use the * ${foo.bar} syntax and are documented in the HOCON * spec. *

* Typically this class would be used with the method * {@link Config#resolve(ConfigResolveOptions)}. *

* This object is immutable, so the "setters" return a new object. *

* Here is an example of creating a custom {@code ConfigResolveOptions}: * *

 *     ConfigResolveOptions options = ConfigResolveOptions.defaults()
 *         .setUseSystemEnvironment(false)
 * 
*

* In addition to {@link ConfigResolveOptions#defaults}, there's a prebuilt * {@link ConfigResolveOptions#noSystem} which avoids looking at any system * environment variables or other external system information. (Right now, * environment variables are the only example.) */ public final class ConfigResolveOptions { private final boolean useSystemEnvironment; private final boolean allowUnresolved; private ConfigResolveOptions(boolean useSystemEnvironment, boolean allowUnresolved) { this.useSystemEnvironment = useSystemEnvironment; this.allowUnresolved = allowUnresolved; } /** * Returns the default resolve options. By default the system environment * will be used and unresolved substitutions are not allowed. * * @return the default resolve options */ public static ConfigResolveOptions defaults() { return new ConfigResolveOptions(true, false); } /** * Returns resolve options that disable any reference to "system" data * (currently, this means environment variables). * * @return the resolve options with env variables disabled */ public static ConfigResolveOptions noSystem() { return defaults().setUseSystemEnvironment(false); } /** * Returns options with use of environment variables set to the given value. * * @param value * true to resolve substitutions falling back to environment * variables. * @return options with requested setting for use of environment variables */ public ConfigResolveOptions setUseSystemEnvironment(boolean value) { return new ConfigResolveOptions(value, allowUnresolved); } /** * Returns whether the options enable use of system environment variables. * This method is mostly used by the config lib internally, not by * applications. * * @return true if environment variables should be used */ public boolean getUseSystemEnvironment() { return useSystemEnvironment; } /** * Returns options with "allow unresolved" set to the given value. By * default, unresolved substitutions are an error. If unresolved * substitutions are allowed, then a future attempt to use the unresolved * value may fail, but {@link Config#resolve(ConfigResolveOptions)} itself * will not throw. * * @param value * true to silently ignore unresolved substitutions. * @return options with requested setting for whether to allow substitutions * @since 1.2.0 */ public ConfigResolveOptions setAllowUnresolved(boolean value) { return new ConfigResolveOptions(useSystemEnvironment, value); } /** * Returns whether the options allow unresolved substitutions. This method * is mostly used by the config lib internally, not by applications. * * @return true if unresolved substitutions are allowed * @since 1.2.0 */ public boolean getAllowUnresolved() { return allowUnresolved; } } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigSyntax.java000066400000000000000000000024601277147274600260270ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; /** * The syntax of a character stream (JSON, HOCON * aka ".conf", or Java properties). * */ public enum ConfigSyntax { /** * Pedantically strict JSON format; no * comments, no unexpected commas, no duplicate keys in the same object. * Associated with the .json file extension and * application/json Content-Type. */ JSON, /** * The JSON-superset HOCON format. Associated with the .conf file extension * and application/hocon Content-Type. */ CONF, /** * Standard Java properties format. Associated with the .properties * file extension and text/x-java-properties Content-Type. */ PROPERTIES; } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigUtil.java000066400000000000000000000051031277147274600254530ustar00rootroot00000000000000package com.typesafe.config; import java.util.List; import com.typesafe.config.impl.ConfigImplUtil; /** * Contains static utility methods. * */ 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}. *

* See the overview documentation for {@link Config} for more detail on path * expressions vs. keys. * * @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}. *

* See the overview documentation for {@link Config} for more detail on path * expressions vs. keys. * * @param elements * the keys in the path * @return a path expression * @throws ConfigException * if the list is empty */ public static String joinPath(List 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}. *

* See the overview documentation for {@link Config} for more detail on path * expressions vs. keys. * * @param path * a path expression * @return the individual keys in the path * @throws ConfigException * if the path expression is invalid */ public static List splitPath(String path) { return ConfigImplUtil.splitPath(path); } } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigValue.java000066400000000000000000000102531277147274600256140ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; /** * An immutable value, following the JSON type * schema. * *

* Because this object is immutable, it is safe to use from multiple threads and * there's no need for "defensive copies." * *

* Do not implement interface {@code ConfigValue}; 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 ConfigValue extends ConfigMergeable { /** * The origin of the value (file, line number, etc.), for debugging and * error messages. * * @return where the value came from */ ConfigOrigin origin(); /** * The {@link ConfigValueType} of the value; matches the JSON type schema. * * @return value's type */ ConfigValueType valueType(); /** * Returns the value as a plain Java boxed value, that is, a {@code String}, * {@code Number}, {@code Boolean}, {@code Map}, * {@code List}, or {@code null}, matching the {@link #valueType()} * of this {@code ConfigValue}. If the value is a {@link ConfigObject} or * {@link ConfigList}, it is recursively unwrapped. * @return a plain Java value corresponding to this ConfigValue */ Object unwrapped(); /** * Renders the config value as a HOCON string. This method is primarily * intended for debugging, so it tries to add helpful comments and * whitespace. * *

* If the config value has not been resolved (see {@link Config#resolve}), * it's possible that it can't be rendered as valid HOCON. In that case the * rendering should still be useful for debugging but you might not be able * to parse it. If the value has been resolved, it will always be parseable. * *

* This method is equivalent to * {@code render(ConfigRenderOptions.defaults())}. * * @return the rendered value */ String render(); /** * Renders the config value to a string, using the provided options. * *

* If the config value has not been resolved (see {@link Config#resolve}), * it's possible that it can't be rendered as valid HOCON. In that case the * rendering should still be useful for debugging but you might not be able * to parse it. If the value has been resolved, it will always be parseable. * *

* If the config value has been resolved and the options disable all * HOCON-specific features (such as comments), the rendering will be valid * JSON. If you enable HOCON-only features such as comments, the rendering * will not be valid JSON. * * @param options * the rendering options * @return the rendered value */ String render(ConfigRenderOptions options); @Override ConfigValue withFallback(ConfigMergeable other); /** * Places the value inside a {@link Config} at the given path. See also * {@link ConfigValue#atKey(String)}. * * @param path * path to store this value at. * @return a {@code Config} instance containing this value at the given * path. */ Config atPath(String path); /** * Places the value inside a {@link Config} at the given key. See also * {@link ConfigValue#atPath(String)}. * * @param key * key to store this value at. * @return a {@code Config} instance containing this value at the given key. */ Config atKey(String key); /** * Returns a {@code ConfigValue} based on this one, but with the given * origin. This is useful when you are parsing a new format of file or setting * comments for a single ConfigValue. * * @since 1.3.0 * * @param origin the origin set on the returned value * @return the new ConfigValue with the given origin */ ConfigValue withOrigin(ConfigOrigin origin); } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigValueFactory.java000066400000000000000000000144171277147274600271520ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; import java.util.Map; import com.typesafe.config.impl.ConfigImpl; /** * This class holds some static factory methods for building {@link ConfigValue} * instances. See also {@link ConfigFactory} which has methods for parsing files * and certain in-memory data structures. */ public final class ConfigValueFactory { private ConfigValueFactory() { } /** * Creates a {@link ConfigValue} from a plain Java boxed value, which may be * a Boolean, Number, String, * Map, Iterable, or null. A * Map must be a Map from String to more values * that can be supplied to fromAnyRef(). An * Iterable must iterate over more values that can be supplied * to fromAnyRef(). A Map will become a * {@link ConfigObject} and an Iterable will become a * {@link ConfigList}. If the Iterable is not an ordered * collection, results could be strange, since ConfigList is * ordered. * *

* In a Map passed to fromAnyRef(), the map's keys * are plain keys, not path expressions. So if your Map has a * key "foo.bar" then you will get one object with a key called "foo.bar", * rather than an object with a key "foo" containing another object with a * key "bar". * *

* The originDescription will be used to set the origin() field on the * ConfigValue. It should normally be the name of the file the values came * from, or something short describing the value such as "default settings". * The originDescription is prefixed to error messages so users can tell * where problematic values are coming from. * *

* Supplying the result of ConfigValue.unwrapped() to this function is * guaranteed to work and should give you back a ConfigValue that matches * the one you unwrapped. The re-wrapped ConfigValue will lose some * information that was present in the original such as its origin, but it * will have matching values. * *

* If you pass in a ConfigValue to this * function, it will be returned unmodified. (The * originDescription will be ignored in this * case.) * *

* This function throws if you supply a value that cannot be converted to a * ConfigValue, but supplying such a value is a bug in your program, so you * should never handle the exception. Just fix your program (or report a bug * against this library). * * @param object * object to convert to ConfigValue * @param originDescription * name of origin file or brief description of what the value is * @return a new value */ public static ConfigValue fromAnyRef(Object object, String originDescription) { return ConfigImpl.fromAnyRef(object, originDescription); } /** * See the {@link #fromAnyRef(Object,String)} documentation for details. * This is a typesafe wrapper that only works on {@link java.util.Map} and * returns {@link ConfigObject} rather than {@link ConfigValue}. * *

* If your Map has a key "foo.bar" then you will get one object * with a key called "foo.bar", rather than an object with a key "foo" * containing another object with a key "bar". The keys in the map are keys; * not path expressions. That is, the Map corresponds exactly * to a single {@code ConfigObject}. The keys will not be parsed or * modified, and the values are wrapped in ConfigValue. To get nested * {@code ConfigObject}, some of the values in the map would have to be more * maps. * *

* See also {@link ConfigFactory#parseMap(Map,String)} which interprets the * keys in the map as path expressions. * * @param values map from keys to plain Java values * @param originDescription description to use in {@link ConfigOrigin} of created values * @return a new {@link ConfigObject} value */ public static ConfigObject fromMap(Map values, String originDescription) { return (ConfigObject) fromAnyRef(values, originDescription); } /** * See the {@link #fromAnyRef(Object,String)} documentation for details. * This is a typesafe wrapper that only works on {@link java.lang.Iterable} * and returns {@link ConfigList} rather than {@link ConfigValue}. * * @param values list of plain Java values * @param originDescription description to use in {@link ConfigOrigin} of created values * @return a new {@link ConfigList} value */ public static ConfigList fromIterable(Iterable values, String originDescription) { return (ConfigList) fromAnyRef(values, originDescription); } /** * See the other overload {@link #fromAnyRef(Object,String)} for details, * this one just uses a default origin description. * * @param object a plain Java value * @return a new {@link ConfigValue} */ public static ConfigValue fromAnyRef(Object object) { return fromAnyRef(object, null); } /** * See the other overload {@link #fromMap(Map,String)} for details, this one * just uses a default origin description. * *

* See also {@link ConfigFactory#parseMap(Map)} which interprets the keys in * the map as path expressions. * * @param values map from keys to plain Java values * @return a new {@link ConfigObject} */ public static ConfigObject fromMap(Map values) { return fromMap(values, null); } /** * See the other overload of {@link #fromIterable(Iterable, String)} for * details, this one just uses a default origin description. * * @param values list of plain Java values * @return a new {@link ConfigList} */ public static ConfigList fromIterable(Iterable values) { return fromIterable(values, null); } } config-1.3.1/config/src/main/java/com/typesafe/config/ConfigValueType.java000066400000000000000000000004471277147274600264620ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config; /** * The type of a configuration value (following the JSON type schema). */ public enum ConfigValueType { OBJECT, LIST, NUMBER, BOOLEAN, NULL, STRING } config-1.3.1/config/src/main/java/com/typesafe/config/DefaultConfigLoadingStrategy.java000066400000000000000000000055261277147274600311540ustar00rootroot00000000000000package com.typesafe.config; import java.io.File; import java.net.MalformedURLException; import java.net.URL; /** * Default config loading strategy. Able to load resource, file or URL. * Behavior may be altered by defining one of VM properties * {@code config.resource}, {@code config.file} or {@code config.url} */ public class DefaultConfigLoadingStrategy implements ConfigLoadingStrategy { @Override public Config parseApplicationConfig(ConfigParseOptions parseOptions) { ClassLoader loader = parseOptions.getClassLoader(); if (loader == null) throw new ConfigException.BugOrBroken( "ClassLoader should have been set here; bug in ConfigFactory. " + "(You can probably work around this bug by passing in a class loader or calling currentThread().setContextClassLoader() though.)"); int specified = 0; // override application.conf with config.file, config.resource, // config.url if requested. String resource = System.getProperty("config.resource"); if (resource != null) specified += 1; String file = System.getProperty("config.file"); if (file != null) specified += 1; String url = System.getProperty("config.url"); if (url != null) specified += 1; if (specified == 0) { return ConfigFactory.parseResourcesAnySyntax("application", parseOptions); } else if (specified > 1) { throw new ConfigException.Generic("You set more than one of config.file='" + file + "', config.url='" + url + "', config.resource='" + resource + "'; don't know which one to use!"); } else { // the override file/url/resource MUST be present or it's an error ConfigParseOptions overrideOptions = parseOptions.setAllowMissing(false); if (resource != null) { if (resource.startsWith("/")) resource = resource.substring(1); // this deliberately does not parseResourcesAnySyntax; if // people want that they can use an include statement. return ConfigFactory.parseResources(loader, resource, overrideOptions); } else if (file != null) { return ConfigFactory.parseFile(new File(file), overrideOptions); } else { try { return ConfigFactory.parseURL(new URL(url), overrideOptions); } catch (MalformedURLException e) { throw new ConfigException.Generic("Bad URL in config.url system property: '" + url + "': " + e.getMessage(), e); } } } } } config-1.3.1/config/src/main/java/com/typesafe/config/Optional.java000066400000000000000000000004441277147274600252000ustar00rootroot00000000000000package com.typesafe.config; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Allows an config property to be {@code null}. */ @Documented @Retention(RetentionPolicy.RUNTIME) public @interface Optional { } config-1.3.1/config/src/main/java/com/typesafe/config/impl/000077500000000000000000000000001277147274600235075ustar00rootroot00000000000000config-1.3.1/config/src/main/java/com/typesafe/config/impl/AbstractConfigNode.java000066400000000000000000000014521277147274600300530ustar00rootroot00000000000000/** * Copyright (C) 2015 Typesafe Inc. */ package com.typesafe.config.impl; import com.typesafe.config.parser.ConfigNode; import java.util.Collection; abstract class AbstractConfigNode implements ConfigNode { abstract Collection tokens(); final public String render() { StringBuilder origText = new StringBuilder(); Iterable 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(); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/AbstractConfigNodeValue.java000066400000000000000000000005611277147274600310500ustar00rootroot00000000000000/** * Copyright (C) 2015 Typesafe Inc. */ 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 { } config-1.3.1/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java000066400000000000000000000161421277147274600303760ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; abstract class AbstractConfigObject extends AbstractConfigValue implements ConfigObject, Container { final private SimpleConfig config; protected AbstractConfigObject(ConfigOrigin origin) { super(origin); this.config = new SimpleConfig(this); } @Override public SimpleConfig toConfig() { return config; } @Override public AbstractConfigObject toFallbackValue() { return this; } @Override abstract public AbstractConfigObject withOnlyKey(String key); @Override abstract public AbstractConfigObject withoutKey(String key); @Override abstract public AbstractConfigObject withValue(String key, ConfigValue value); abstract protected AbstractConfigObject withOnlyPathOrNull(Path path); abstract AbstractConfigObject withOnlyPath(Path path); abstract AbstractConfigObject withoutPath(Path path); abstract AbstractConfigObject withValue(Path path, ConfigValue value); /** * This looks up the key with no transformation or type conversion of any * kind, and returns null if the key is not present. The object must be * resolved along the nodes needed to get the key or * ConfigException.NotResolved will be thrown. * * @param key * @return the unmodified raw value or null */ protected final AbstractConfigValue peekAssumingResolved(String key, Path originalPath) { try { return attemptPeekWithPartialResolve(key); } catch (ConfigException.NotResolved e) { throw ConfigImpl.improveNotResolved(originalPath, e); } } /** * Look up the key on an only-partially-resolved object, with no * transformation or type conversion of any kind; if 'this' is not resolved * then try to look up the key anyway if possible. * * @param key * key to look up * @return the value of the key, or null if known not to exist * @throws ConfigException.NotResolved * if can't figure out key's value (or existence) without more * resolving */ abstract AbstractConfigValue attemptPeekWithPartialResolve(String key); /** * Looks up the path with no transformation or type conversion. Returns null * if the path is not found; throws ConfigException.NotResolved if we need * to go through an unresolved node to look up the path. */ protected AbstractConfigValue peekPath(Path path) { return peekPath(this, path); } private static AbstractConfigValue peekPath(AbstractConfigObject self, Path path) { try { // we'll fail if anything along the path can't // be looked at without resolving. Path next = path.remainder(); AbstractConfigValue v = self.attemptPeekWithPartialResolve(path.first()); if (next == null) { return v; } else { if (v instanceof AbstractConfigObject) { return peekPath((AbstractConfigObject) v, next); } else { return null; } } } catch (ConfigException.NotResolved e) { throw ConfigImpl.improveNotResolved(path, e); } } @Override public ConfigValueType valueType() { return ConfigValueType.OBJECT; } protected abstract AbstractConfigObject newCopy(ResolveStatus status, ConfigOrigin origin); @Override protected AbstractConfigObject newCopy(ConfigOrigin origin) { return newCopy(resolveStatus(), origin); } @Override protected AbstractConfigObject constructDelayedMerge(ConfigOrigin origin, List stack) { return new ConfigDelayedMergeObject(origin, stack); } @Override protected abstract AbstractConfigObject mergedWithObject(AbstractConfigObject fallback); @Override public AbstractConfigObject withFallback(ConfigMergeable mergeable) { return (AbstractConfigObject) super.withFallback(mergeable); } static ConfigOrigin mergeOrigins( Collection stack) { if (stack.isEmpty()) throw new ConfigException.BugOrBroken( "can't merge origins on empty list"); List origins = new ArrayList(); ConfigOrigin firstOrigin = null; int numMerged = 0; for (AbstractConfigValue v : stack) { if (firstOrigin == null) firstOrigin = v.origin(); if (v instanceof AbstractConfigObject && ((AbstractConfigObject) v).resolveStatus() == ResolveStatus.RESOLVED && ((ConfigObject) v).isEmpty()) { // don't include empty files or the .empty() // config in the description, since they are // likely to be "implementation details" } else { origins.add(v.origin()); numMerged += 1; } } if (numMerged == 0) { // the configs were all empty, so just use the first one origins.add(firstOrigin); } return SimpleConfigOrigin.mergeOrigins(origins); } static ConfigOrigin mergeOrigins(AbstractConfigObject... stack) { return mergeOrigins(Arrays.asList(stack)); } @Override abstract ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve; @Override abstract AbstractConfigObject relativized(final Path prefix); @Override public abstract AbstractConfigValue get(Object key); @Override protected abstract void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options); private static UnsupportedOperationException weAreImmutable(String method) { return new UnsupportedOperationException("ConfigObject is immutable, you can't call Map." + method); } @Override public void clear() { throw weAreImmutable("clear"); } @Override public ConfigValue put(String arg0, ConfigValue arg1) { throw weAreImmutable("put"); } @Override public void putAll(Map arg0) { throw weAreImmutable("putAll"); } @Override public ConfigValue remove(Object arg0) { throw weAreImmutable("remove"); } @Override public AbstractConfigObject withOrigin(ConfigOrigin origin) { return (AbstractConfigObject) super.withOrigin(origin); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java000066400000000000000000000347641277147274600302560ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValue; /** * * Trying very hard to avoid a parent reference in config values; when you have * a tree like this, the availability of parent() tends to result in a lot of * improperly-factored and non-modular code. Please don't add parent(). * */ abstract class AbstractConfigValue implements ConfigValue, MergeableValue { final private SimpleConfigOrigin origin; AbstractConfigValue(ConfigOrigin origin) { this.origin = (SimpleConfigOrigin) origin; } @Override public SimpleConfigOrigin origin() { return this.origin; } /** * This exception means that a value is inherently not resolveable, at the * moment the only known cause is a cycle of substitutions. This is a * checked exception since it's internal to the library and we want to be * sure we handle it before passing it out to public API. This is only * supposed to be thrown by the target of a cyclic reference and it's * supposed to be caught by the ConfigReference looking up that reference, * so it should be impossible for an outermost resolve() to throw this. * * Contrast with ConfigException.NotResolved which just means nobody called * resolve(). */ static class NotPossibleToResolve extends Exception { private static final long serialVersionUID = 1L; final private String traceString; NotPossibleToResolve(ResolveContext context) { super("was not possible to resolve"); this.traceString = context.traceString(); } String traceString() { return traceString; } } /** * Called only by ResolveContext.resolve(). * * @param context * state of the current resolve * @param source * where to look up values * @return a new value if there were changes, or this if no changes */ ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve { return ResolveResult.make(context, this); } ResolveStatus resolveStatus() { return ResolveStatus.RESOLVED; } protected static List replaceChildInList(List list, AbstractConfigValue child, AbstractConfigValue replacement) { int i = 0; while (i < list.size() && list.get(i) != child) ++i; if (i == list.size()) throw new ConfigException.BugOrBroken("tried to replace " + child + " which is not in " + list); List newStack = new ArrayList(list); if (replacement != null) newStack.set(i, replacement); else newStack.remove(i); if (newStack.isEmpty()) return null; else return newStack; } protected static boolean hasDescendantInList(List list, AbstractConfigValue descendant) { for (AbstractConfigValue v : list) { if (v == descendant) return true; } // now the expensive traversal for (AbstractConfigValue v : list) { if (v instanceof Container && ((Container) v).hasDescendant(descendant)) return true; } return false; } /** * This is used when including one file in another; the included file is * relativized to the path it's included into in the parent file. The point * is that if you include a file at foo.bar in the parent, and the included * file as a substitution ${a.b.c}, the included substitution now needs to * be ${foo.bar.a.b.c} because we resolve substitutions globally only after * parsing everything. * * @param prefix * @return value relativized to the given path or the same value if nothing * to do */ AbstractConfigValue relativized(Path prefix) { return this; } protected interface Modifier { // keyOrNull is null for non-objects AbstractConfigValue modifyChildMayThrow(String keyOrNull, AbstractConfigValue v) throws Exception; } protected abstract class NoExceptionsModifier implements Modifier { @Override public final AbstractConfigValue modifyChildMayThrow(String keyOrNull, AbstractConfigValue v) throws Exception { try { return modifyChild(keyOrNull, v); } catch (RuntimeException e) { throw e; } catch(Exception e) { throw new ConfigException.BugOrBroken("Unexpected exception", e); } } abstract AbstractConfigValue modifyChild(String keyOrNull, AbstractConfigValue v); } @Override public AbstractConfigValue toFallbackValue() { return this; } protected abstract AbstractConfigValue newCopy(ConfigOrigin origin); // this is virtualized rather than a field because only some subclasses // really need to store the boolean, and they may be able to pack it // with another boolean to save space. protected boolean ignoresFallbacks() { // if we are not resolved, then somewhere in this value there's // a substitution that may need to look at the fallbacks. return resolveStatus() == ResolveStatus.RESOLVED; } protected AbstractConfigValue withFallbacksIgnored() { if (ignoresFallbacks()) return this; else throw new ConfigException.BugOrBroken( "value class doesn't implement forced fallback-ignoring " + this); } // the withFallback() implementation is supposed to avoid calling // mergedWith* if we're ignoring fallbacks. protected final void requireNotIgnoringFallbacks() { if (ignoresFallbacks()) throw new ConfigException.BugOrBroken( "method should not have been called with ignoresFallbacks=true " + getClass().getSimpleName()); } protected AbstractConfigValue constructDelayedMerge(ConfigOrigin origin, List stack) { return new ConfigDelayedMerge(origin, stack); } protected final AbstractConfigValue mergedWithTheUnmergeable( Collection stack, Unmergeable fallback) { requireNotIgnoringFallbacks(); // if we turn out to be an object, and the fallback also does, // then a merge may be required; delay until we resolve. List newStack = new ArrayList(); newStack.addAll(stack); newStack.addAll(fallback.unmergedValues()); return constructDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack); } private final AbstractConfigValue delayMerge(Collection stack, AbstractConfigValue fallback) { // if we turn out to be an object, and the fallback also does, // then a merge may be required. // if we contain a substitution, resolving it may need to look // back to the fallback. List newStack = new ArrayList(); newStack.addAll(stack); newStack.add(fallback); return constructDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack); } protected final AbstractConfigValue mergedWithObject(Collection stack, AbstractConfigObject fallback) { requireNotIgnoringFallbacks(); if (this instanceof AbstractConfigObject) throw new ConfigException.BugOrBroken("Objects must reimplement mergedWithObject"); return mergedWithNonObject(stack, fallback); } protected final AbstractConfigValue mergedWithNonObject(Collection stack, AbstractConfigValue fallback) { requireNotIgnoringFallbacks(); if (resolveStatus() == ResolveStatus.RESOLVED) { // falling back to a non-object doesn't merge anything, and also // prohibits merging any objects that we fall back to later. // so we have to switch to ignoresFallbacks mode. return withFallbacksIgnored(); } else { // if unresolved, we may have to look back to fallbacks as part of // the resolution process, so always delay return delayMerge(stack, fallback); } } protected AbstractConfigValue mergedWithTheUnmergeable(Unmergeable fallback) { requireNotIgnoringFallbacks(); return mergedWithTheUnmergeable(Collections.singletonList(this), fallback); } protected AbstractConfigValue mergedWithObject(AbstractConfigObject fallback) { requireNotIgnoringFallbacks(); return mergedWithObject(Collections.singletonList(this), fallback); } protected AbstractConfigValue mergedWithNonObject(AbstractConfigValue fallback) { requireNotIgnoringFallbacks(); return mergedWithNonObject(Collections.singletonList(this), fallback); } @Override public AbstractConfigValue withOrigin(ConfigOrigin origin) { if (this.origin == origin) return this; else return newCopy(origin); } // this is only overridden to change the return type @Override public AbstractConfigValue withFallback(ConfigMergeable mergeable) { if (ignoresFallbacks()) { return this; } else { ConfigValue other = ((MergeableValue) mergeable).toFallbackValue(); if (other instanceof Unmergeable) { return mergedWithTheUnmergeable((Unmergeable) other); } else if (other instanceof AbstractConfigObject) { return mergedWithObject((AbstractConfigObject) other); } else { return mergedWithNonObject((AbstractConfigValue) other); } } } protected boolean canEqual(Object other) { return other instanceof ConfigValue; } @Override public boolean equals(Object other) { // note that "origin" is deliberately NOT part of equality if (other instanceof ConfigValue) { return canEqual(other) && (this.valueType() == ((ConfigValue) other).valueType()) && ConfigImplUtil.equalsHandlingNull(this.unwrapped(), ((ConfigValue) other).unwrapped()); } else { return false; } } @Override public int hashCode() { // note that "origin" is deliberately NOT part of equality Object o = this.unwrapped(); if (o == null) return 0; else return o.hashCode(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); render(sb, 0, true /* atRoot */, null /* atKey */, ConfigRenderOptions.concise()); return getClass().getSimpleName() + "(" + sb.toString() + ")"; } protected static void indent(StringBuilder sb, int indent, ConfigRenderOptions options) { if (options.getFormatted()) { int remaining = indent; while (remaining > 0) { sb.append(" "); --remaining; } } } protected void render(StringBuilder sb, int indent, boolean atRoot, String atKey, ConfigRenderOptions options) { if (atKey != null) { String renderedKey; if (options.getJson()) renderedKey = ConfigImplUtil.renderJsonString(atKey); else renderedKey = ConfigImplUtil.renderStringUnquotedIfPossible(atKey); sb.append(renderedKey); if (options.getJson()) { if (options.getFormatted()) sb.append(" : "); else sb.append(":"); } else { // in non-JSON we can omit the colon or equals before an object if (this instanceof ConfigObject) { if (options.getFormatted()) sb.append(' '); } else { sb.append("="); } } } render(sb, indent, atRoot, options); } protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { Object u = unwrapped(); sb.append(u.toString()); } @Override public final String render() { return render(ConfigRenderOptions.defaults()); } @Override public final String render(ConfigRenderOptions options) { StringBuilder sb = new StringBuilder(); render(sb, 0, true, null, options); return sb.toString(); } // toString() is a debugging-oriented string but this is defined // to create a string that would parse back to the value in JSON. // It only works for primitive values (that would be a single token) // which are auto-converted to strings when concatenating with // other strings or by the DefaultTransformer. String transformToString() { return null; } SimpleConfig atKey(ConfigOrigin origin, String key) { Map m = Collections.singletonMap(key, this); return (new SimpleConfigObject(origin, m)).toConfig(); } @Override public SimpleConfig atKey(String key) { return atKey(SimpleConfigOrigin.newSimple("atKey(" + key + ")"), key); } SimpleConfig atPath(ConfigOrigin origin, Path path) { Path parent = path.parent(); SimpleConfig result = atKey(origin, path.last()); while (parent != null) { String key = parent.last(); result = result.atKey(origin, key); parent = parent.parent(); } return result; } @Override public SimpleConfig atPath(String pathExpression) { SimpleConfigOrigin origin = SimpleConfigOrigin.newSimple("atPath(" + pathExpression + ")"); return atPath(origin, Path.newPath(pathExpression)); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigBeanImpl.java000066400000000000000000000344241277147274600271760ustar00rootroot00000000000000package com.typesafe.config.impl; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.time.Duration; import com.typesafe.config.Config; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigMemorySize; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; import com.typesafe.config.Optional; /** * Internal implementation detail, not ABI stable, do not touch. * For use only by the {@link com.typesafe.config} package. */ public class ConfigBeanImpl { /** * This is public ONLY for use by the "config" package, DO NOT USE this ABI * may change. * @param type of the bean * @param config config to use * @param clazz class of the bean * @return the bean instance */ public static T createInternal(Config config, Class clazz) { if (((SimpleConfig)config).root().resolveStatus() != ResolveStatus.RESOLVED) throw new ConfigException.NotResolved( "need to Config#resolve() a config before using it to initialize a bean, see the API docs for Config#resolve()"); Map configProps = new HashMap(); Map originalNames = new HashMap(); for (Map.Entry configProp : config.root().entrySet()) { String originalName = configProp.getKey(); String camelName = ConfigImplUtil.toCamelCase(originalName); // if a setting is in there both as some hyphen name and the camel name, // the camel one wins if (originalNames.containsKey(camelName) && !originalName.equals(camelName)) { // if we aren't a camel name to start with, we lose. // if we are or we are the first matching key, we win. } else { configProps.put(camelName, (AbstractConfigValue) configProp.getValue()); originalNames.put(camelName, originalName); } } BeanInfo beanInfo = null; try { beanInfo = Introspector.getBeanInfo(clazz); } catch (IntrospectionException e) { throw new ConfigException.BadBean("Could not get bean information for class " + clazz.getName(), e); } try { List beanProps = new ArrayList(); for (PropertyDescriptor beanProp : beanInfo.getPropertyDescriptors()) { if (beanProp.getReadMethod() == null || beanProp.getWriteMethod() == null) { continue; } beanProps.add(beanProp); } // Try to throw all validation issues at once (this does not comprehensively // find every issue, but it should find common ones). List problems = new ArrayList(); for (PropertyDescriptor beanProp : beanProps) { Method setter = beanProp.getWriteMethod(); Class parameterClass = setter.getParameterTypes()[0]; ConfigValueType expectedType = getValueTypeOrNull(parameterClass); if (expectedType != null) { String name = originalNames.get(beanProp.getName()); if (name == null) name = beanProp.getName(); Path path = Path.newKey(name); AbstractConfigValue configValue = configProps.get(beanProp.getName()); if (configValue != null) { SimpleConfig.checkValid(path, expectedType, configValue, problems); } else { if (!isOptionalProperty(clazz, beanProp)) { SimpleConfig.addMissing(problems, expectedType, path, config.origin()); } } } } if (!problems.isEmpty()) { throw new ConfigException.ValidationFailed(problems); } // Fill in the bean instance T bean = clazz.newInstance(); for (PropertyDescriptor beanProp : beanProps) { Method setter = beanProp.getWriteMethod(); Type parameterType = setter.getGenericParameterTypes()[0]; Class parameterClass = setter.getParameterTypes()[0]; String configPropName = originalNames.get(beanProp.getName()); // Is the property key missing in the config? if (configPropName == null) { // If so, continue if the field is marked as @{link Optional} if (isOptionalProperty(clazz, beanProp)) { continue; } // Otherwise, raise a {@link Missing} exception right here throw new ConfigException.Missing(beanProp.getName()); } Object unwrapped = getValue(clazz, parameterType, parameterClass, config, configPropName); setter.invoke(bean, unwrapped); } return bean; } catch (InstantiationException e) { throw new ConfigException.BadBean(clazz.getName() + " needs a public no-args constructor to be used as a bean", e); } catch (IllegalAccessException e) { throw new ConfigException.BadBean(clazz.getName() + " getters and setters are not accessible, they must be for use as a bean", e); } catch (InvocationTargetException e) { throw new ConfigException.BadBean("Calling bean method on " + clazz.getName() + " caused an exception", e); } } // we could magically make this work in many cases by doing // getAnyRef() (or getValue().unwrapped()), but anytime we // rely on that, we aren't doing the type conversions Config // usually does, and we will throw ClassCastException instead // of a nicer error message giving the name of the bad // setting. So, instead, we only support a limited number of // types plus you can always use Object, ConfigValue, Config, // ConfigObject, etc. as an escape hatch. private static Object getValue(Class beanClass, Type parameterType, Class parameterClass, Config config, String configPropName) { if (parameterClass == Boolean.class || parameterClass == boolean.class) { return config.getBoolean(configPropName); } else if (parameterClass == Integer.class || parameterClass == int.class) { return config.getInt(configPropName); } else if (parameterClass == Double.class || parameterClass == double.class) { return config.getDouble(configPropName); } else if (parameterClass == Long.class || parameterClass == long.class) { return config.getLong(configPropName); } else if (parameterClass == String.class) { return config.getString(configPropName); } else if (parameterClass == Duration.class) { return config.getDuration(configPropName); } else if (parameterClass == ConfigMemorySize.class) { return config.getMemorySize(configPropName); } else if (parameterClass == Object.class) { return config.getAnyRef(configPropName); } else if (parameterClass == List.class) { return getListValue(beanClass, parameterType, parameterClass, config, configPropName); } else if (parameterClass == Map.class) { // we could do better here, but right now we don't. Type[] typeArgs = ((ParameterizedType)parameterType).getActualTypeArguments(); if (typeArgs[0] != String.class || typeArgs[1] != Object.class) { throw new ConfigException.BadBean("Bean property '" + configPropName + "' of class " + beanClass.getName() + " has unsupported Map<" + typeArgs[0] + "," + typeArgs[1] + ">, only Map is supported right now"); } return config.getObject(configPropName).unwrapped(); } else if (parameterClass == Config.class) { return config.getConfig(configPropName); } else if (parameterClass == ConfigObject.class) { return config.getObject(configPropName); } else if (parameterClass == ConfigValue.class) { return config.getValue(configPropName); } else if (parameterClass == ConfigList.class) { return config.getList(configPropName); } else if (parameterClass.isEnum()) { @SuppressWarnings("unchecked") Enum enumValue = config.getEnum((Class) parameterClass, configPropName); return enumValue; } else if (hasAtLeastOneBeanProperty(parameterClass)) { return createInternal(config.getConfig(configPropName), parameterClass); } else { throw new ConfigException.BadBean("Bean property " + configPropName + " of class " + beanClass.getName() + " has unsupported type " + parameterType); } } private static Object getListValue(Class beanClass, Type parameterType, Class parameterClass, Config config, String configPropName) { Type elementType = ((ParameterizedType)parameterType).getActualTypeArguments()[0]; if (elementType == Boolean.class) { return config.getBooleanList(configPropName); } else if (elementType == Integer.class) { return config.getIntList(configPropName); } else if (elementType == Double.class) { return config.getDoubleList(configPropName); } else if (elementType == Long.class) { return config.getLongList(configPropName); } else if (elementType == String.class) { return config.getStringList(configPropName); } else if (elementType == Duration.class) { return config.getDurationList(configPropName); } else if (elementType == ConfigMemorySize.class) { return config.getMemorySizeList(configPropName); } else if (elementType == Object.class) { return config.getAnyRefList(configPropName); } else if (elementType == Config.class) { return config.getConfigList(configPropName); } else if (elementType == ConfigObject.class) { return config.getObjectList(configPropName); } else if (elementType == ConfigValue.class) { return config.getList(configPropName); } else if (((Class) elementType).isEnum()) { @SuppressWarnings("unchecked") List enumValues = config.getEnumList((Class) elementType, configPropName); return enumValues; } else if (hasAtLeastOneBeanProperty((Class) elementType)) { List beanList = new ArrayList(); List configList = config.getConfigList(configPropName); for (Config listMember : configList) { beanList.add(createInternal(listMember, (Class) elementType)); } return beanList; } else { throw new ConfigException.BadBean("Bean property '" + configPropName + "' of class " + beanClass.getName() + " has unsupported list element type " + elementType); } } // null if we can't easily say; this is heuristic/best-effort private static ConfigValueType getValueTypeOrNull(Class parameterClass) { if (parameterClass == Boolean.class || parameterClass == boolean.class) { return ConfigValueType.BOOLEAN; } else if (parameterClass == Integer.class || parameterClass == int.class) { return ConfigValueType.NUMBER; } else if (parameterClass == Double.class || parameterClass == double.class) { return ConfigValueType.NUMBER; } else if (parameterClass == Long.class || parameterClass == long.class) { return ConfigValueType.NUMBER; } else if (parameterClass == String.class) { return ConfigValueType.STRING; } else if (parameterClass == Duration.class) { return null; } else if (parameterClass == ConfigMemorySize.class) { return null; } else if (parameterClass == List.class) { return ConfigValueType.LIST; } else if (parameterClass == Map.class) { return ConfigValueType.OBJECT; } else if (parameterClass == Config.class) { return ConfigValueType.OBJECT; } else if (parameterClass == ConfigObject.class) { return ConfigValueType.OBJECT; } else if (parameterClass == ConfigList.class) { return ConfigValueType.LIST; } else { return null; } } private static boolean hasAtLeastOneBeanProperty(Class clazz) { BeanInfo beanInfo = null; try { beanInfo = Introspector.getBeanInfo(clazz); } catch (IntrospectionException e) { return false; } for (PropertyDescriptor beanProp : beanInfo.getPropertyDescriptors()) { if (beanProp.getReadMethod() != null && beanProp.getWriteMethod() != null) { return true; } } return false; } private static boolean isOptionalProperty(Class beanClass, PropertyDescriptor beanProp) { Field field = getField(beanClass, beanProp.getName()); return (field.getAnnotationsByType(Optional.class).length > 0); } private static Field getField(Class beanClass, String fieldName) { try { Field field = beanClass.getDeclaredField(fieldName); field.setAccessible(true); return field; } catch (NoSuchFieldException e) { // Don't give up yet. Try to look for field in super class, if any. } beanClass = beanClass.getSuperclass(); if (beanClass == null) { return null; } return getField(beanClass, fieldName); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigBoolean.java000066400000000000000000000021501277147274600270550ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.ObjectStreamException; import java.io.Serializable; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigValueType; final class ConfigBoolean extends AbstractConfigValue implements Serializable { private static final long serialVersionUID = 2L; final private boolean value; ConfigBoolean(ConfigOrigin origin, boolean value) { super(origin); this.value = value; } @Override public ConfigValueType valueType() { return ConfigValueType.BOOLEAN; } @Override public Boolean unwrapped() { return value; } @Override String transformToString() { return value ? "true" : "false"; } @Override protected ConfigBoolean newCopy(ConfigOrigin origin) { return new ConfigBoolean(origin, value); } // serialization all goes through SerializedConfigValue private Object writeReplace() throws ObjectStreamException { return new SerializedConfigValue(this); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigConcatenation.java000066400000000000000000000270541277147274600302750ustar00rootroot00000000000000package com.typesafe.config.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValueType; /** * A ConfigConcatenation represents a list of values to be concatenated (see the * spec). It only has to exist if at least one value is an unresolved * substitution, otherwise we could go ahead and collapse the list into a single * value. * * Right now this is always a list of strings and ${} references, but in the * future should support a list of ConfigList. We may also support * concatenations of objects, but ConfigDelayedMerge should be used for that * since a concat of objects really will merge, not concatenate. */ final class ConfigConcatenation extends AbstractConfigValue implements Unmergeable, Container { final private List pieces; ConfigConcatenation(ConfigOrigin origin, List pieces) { super(origin); this.pieces = pieces; if (pieces.size() < 2) throw new ConfigException.BugOrBroken("Created concatenation with less than 2 items: " + this); boolean hadUnmergeable = false; for (AbstractConfigValue p : pieces) { if (p instanceof ConfigConcatenation) throw new ConfigException.BugOrBroken( "ConfigConcatenation should never be nested: " + this); if (p instanceof Unmergeable) hadUnmergeable = true; } if (!hadUnmergeable) throw new ConfigException.BugOrBroken( "Created concatenation without an unmergeable in it: " + this); } private ConfigException.NotResolved notResolved() { return new ConfigException.NotResolved( "need to Config#resolve(), see the API docs for Config#resolve(); substitution not resolved: " + this); } @Override public ConfigValueType valueType() { throw notResolved(); } @Override public Object unwrapped() { throw notResolved(); } @Override protected ConfigConcatenation newCopy(ConfigOrigin newOrigin) { return new ConfigConcatenation(newOrigin, pieces); } @Override protected boolean ignoresFallbacks() { // we can never ignore fallbacks because if a child ConfigReference // is self-referential we have to look lower in the merge stack // for its value. return false; } @Override public Collection unmergedValues() { return Collections.singleton(this); } private static boolean isIgnoredWhitespace(AbstractConfigValue value) { return (value instanceof ConfigString) && !((ConfigString)value).wasQuoted(); } /** * Add left and right, or their merger, to builder. */ private static void join(ArrayList builder, AbstractConfigValue origRight) { AbstractConfigValue left = builder.get(builder.size() - 1); AbstractConfigValue right = origRight; // check for an object which can be converted to a list // (this will be an object with numeric keys, like foo.0, foo.1) if (left instanceof ConfigObject && right instanceof SimpleConfigList) { left = DefaultTransformer.transform(left, ConfigValueType.LIST); } else if (left instanceof SimpleConfigList && right instanceof ConfigObject) { right = DefaultTransformer.transform(right, ConfigValueType.LIST); } // Since this depends on the type of two instances, I couldn't think // of much alternative to an instanceof chain. Visitors are sometimes // used for multiple dispatch but seems like overkill. AbstractConfigValue joined = null; if (left instanceof ConfigObject && right instanceof ConfigObject) { joined = right.withFallback(left); } else if (left instanceof SimpleConfigList && right instanceof SimpleConfigList) { joined = ((SimpleConfigList)left).concatenate((SimpleConfigList)right); } else if ((left instanceof SimpleConfigList || left instanceof ConfigObject) && isIgnoredWhitespace(right)) { joined = left; // it should be impossible that left is whitespace and right is a list or object } else if (left instanceof ConfigConcatenation || right instanceof ConfigConcatenation) { throw new ConfigException.BugOrBroken("unflattened ConfigConcatenation"); } else if (left instanceof Unmergeable || right instanceof Unmergeable) { // leave joined=null, cannot join } else { // handle primitive type or primitive type mixed with object or list String s1 = left.transformToString(); String s2 = right.transformToString(); if (s1 == null || s2 == null) { throw new ConfigException.WrongType(left.origin(), "Cannot concatenate object or list with a non-object-or-list, " + left + " and " + right + " are not compatible"); } else { ConfigOrigin joinedOrigin = SimpleConfigOrigin.mergeOrigins(left.origin(), right.origin()); joined = new ConfigString.Quoted(joinedOrigin, s1 + s2); } } if (joined == null) { builder.add(right); } else { builder.remove(builder.size() - 1); builder.add(joined); } } static List consolidate(List pieces) { if (pieces.size() < 2) { return pieces; } else { List flattened = new ArrayList(pieces.size()); for (AbstractConfigValue v : pieces) { if (v instanceof ConfigConcatenation) { flattened.addAll(((ConfigConcatenation) v).pieces); } else { flattened.add(v); } } ArrayList consolidated = new ArrayList( flattened.size()); for (AbstractConfigValue v : flattened) { if (consolidated.isEmpty()) consolidated.add(v); else join(consolidated, v); } return consolidated; } } static AbstractConfigValue concatenate(List pieces) { List consolidated = consolidate(pieces); if (consolidated.isEmpty()) { return null; } else if (consolidated.size() == 1) { return consolidated.get(0); } else { ConfigOrigin mergedOrigin = SimpleConfigOrigin.mergeOrigins(consolidated); return new ConfigConcatenation(mergedOrigin, consolidated); } } @Override ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve { if (ConfigImpl.traceSubstitutionsEnabled()) { int indent = context.depth() + 2; ConfigImpl.trace(indent - 1, "concatenation has " + pieces.size() + " pieces:"); int count = 0; for (AbstractConfigValue v : pieces) { ConfigImpl.trace(indent, count + ": " + v); count += 1; } } // Right now there's no reason to pushParent here because the // content of ConfigConcatenation should not need to replaceChild, // but if it did we'd have to do this. ResolveSource sourceWithParent = source; // .pushParent(this); ResolveContext newContext = context; List resolved = new ArrayList(pieces.size()); for (AbstractConfigValue p : pieces) { // to concat into a string we have to do a full resolve, // so unrestrict the context, then put restriction back afterward Path restriction = newContext.restrictToChild(); ResolveResult result = newContext.unrestricted() .resolve(p, sourceWithParent); AbstractConfigValue r = result.value; newContext = result.context.restrict(restriction); if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(context.depth(), "resolved concat piece to " + r); if (r == null) { // it was optional... omit } else { resolved.add(r); } } // now need to concat everything List joined = consolidate(resolved); // if unresolved is allowed we can just become another // ConfigConcatenation if (joined.size() > 1 && context.options().getAllowUnresolved()) return ResolveResult.make(newContext, new ConfigConcatenation(this.origin(), joined)); else if (joined.isEmpty()) // we had just a list of optional references using ${?} return ResolveResult.make(newContext, null); else if (joined.size() == 1) return ResolveResult.make(newContext, joined.get(0)); else throw new ConfigException.BugOrBroken("Bug in the library; resolved list was joined to too many values: " + joined); } @Override ResolveStatus resolveStatus() { return ResolveStatus.UNRESOLVED; } @Override public ConfigConcatenation replaceChild(AbstractConfigValue child, AbstractConfigValue replacement) { List newPieces = replaceChildInList(pieces, child, replacement); if (newPieces == null) return null; else return new ConfigConcatenation(origin(), newPieces); } @Override public boolean hasDescendant(AbstractConfigValue descendant) { return hasDescendantInList(pieces, descendant); } // when you graft a substitution into another object, // you have to prefix it with the location in that object // where you grafted it; but save prefixLength so // system property and env variable lookups don't get // broken. @Override ConfigConcatenation relativized(Path prefix) { List newPieces = new ArrayList(); for (AbstractConfigValue p : pieces) { newPieces.add(p.relativized(prefix)); } return new ConfigConcatenation(origin(), newPieces); } @Override protected boolean canEqual(Object other) { return other instanceof ConfigConcatenation; } @Override public boolean equals(Object other) { // note that "origin" is deliberately NOT part of equality if (other instanceof ConfigConcatenation) { return canEqual(other) && this.pieces.equals(((ConfigConcatenation) other).pieces); } else { return false; } } @Override public int hashCode() { // note that "origin" is deliberately NOT part of equality return pieces.hashCode(); } @Override protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { for (AbstractConfigValue p : pieces) { p.render(sb, indent, atRoot, options); } } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java000066400000000000000000000324021277147274600300300ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValueType; /** * The issue here is that we want to first merge our stack of config files, and * then we want to evaluate substitutions. But if two substitutions both expand * to an object, we might need to merge those two objects. Thus, we can't ever * "override" a substitution when we do a merge; instead we have to save the * stack of values that should be merged, and resolve the merge when we evaluate * substitutions. */ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeable, ReplaceableMergeStack { // earlier items in the stack win final private List stack; ConfigDelayedMerge(ConfigOrigin origin, List stack) { super(origin); this.stack = stack; if (stack.isEmpty()) throw new ConfigException.BugOrBroken( "creating empty delayed merge value"); for (AbstractConfigValue v : stack) { if (v instanceof ConfigDelayedMerge || v instanceof ConfigDelayedMergeObject) throw new ConfigException.BugOrBroken( "placed nested DelayedMerge in a ConfigDelayedMerge, should have consolidated stack"); } } @Override public ConfigValueType valueType() { throw new ConfigException.NotResolved( "called valueType() on value with unresolved substitutions, need to Config#resolve() first, see API docs"); } @Override public Object unwrapped() { throw new ConfigException.NotResolved( "called unwrapped() on value with unresolved substitutions, need to Config#resolve() first, see API docs"); } @Override ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve { return resolveSubstitutions(this, stack, context, source); } // static method also used by ConfigDelayedMergeObject static ResolveResult resolveSubstitutions(ReplaceableMergeStack replaceable, List stack, ResolveContext context, ResolveSource source) throws NotPossibleToResolve { if (ConfigImpl.traceSubstitutionsEnabled()) { ConfigImpl.trace(context.depth(), "delayed merge stack has " + stack.size() + " items:"); int count = 0; for (AbstractConfigValue v : stack) { ConfigImpl.trace(context.depth() + 1, count + ": " + v); count += 1; } } // to resolve substitutions, we need to recursively resolve // the stack of stuff to merge, and merge the stack so // we won't be a delayed merge anymore. If restrictToChildOrNull // is non-null, or resolve options allow partial resolves, // we may remain a delayed merge though. ResolveContext newContext = context; int count = 0; AbstractConfigValue merged = null; for (AbstractConfigValue end : stack) { // the end value may or may not be resolved already ResolveSource sourceForEnd; if (end instanceof ReplaceableMergeStack) throw new ConfigException.BugOrBroken("A delayed merge should not contain another one: " + replaceable); else if (end instanceof Unmergeable) { // the remainder could be any kind of value, including another // ConfigDelayedMerge AbstractConfigValue remainder = replaceable.makeReplacement(context, count + 1); if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(newContext.depth(), "remainder portion: " + remainder); // If, while resolving 'end' we come back to the same // merge stack, we only want to look _below_ 'end' // in the stack. So we arrange to replace the // ConfigDelayedMerge with a value that is only // the remainder of the stack below this one. if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(newContext.depth(), "building sourceForEnd"); // we resetParents() here because we'll be resolving "end" // against a root which does NOT contain "end" sourceForEnd = source.replaceWithinCurrentParent((AbstractConfigValue) replaceable, remainder); if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(newContext.depth(), " sourceForEnd before reset parents but after replace: " + sourceForEnd); sourceForEnd = sourceForEnd.resetParents(); } else { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(newContext.depth(), "will resolve end against the original source with parent pushed"); sourceForEnd = source.pushParent(replaceable); } if (ConfigImpl.traceSubstitutionsEnabled()) { ConfigImpl.trace(newContext.depth(), "sourceForEnd =" + sourceForEnd); } if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(newContext.depth(), "Resolving highest-priority item in delayed merge " + end + " against " + sourceForEnd + " endWasRemoved=" + (source != sourceForEnd)); ResolveResult result = newContext.resolve(end, sourceForEnd); AbstractConfigValue resolvedEnd = result.value; newContext = result.context; if (resolvedEnd != null) { if (merged == null) { merged = resolvedEnd; } else { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(newContext.depth() + 1, "merging " + merged + " with fallback " + resolvedEnd); merged = merged.withFallback(resolvedEnd); } } count += 1; if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(newContext.depth(), "stack merged, yielding: " + merged); } return ResolveResult.make(newContext, merged); } @Override public AbstractConfigValue makeReplacement(ResolveContext context, int skipping) { return ConfigDelayedMerge.makeReplacement(context, stack, skipping); } // static method also used by ConfigDelayedMergeObject; end may be null static AbstractConfigValue makeReplacement(ResolveContext context, List stack, int skipping) { List subStack = stack.subList(skipping, stack.size()); if (subStack.isEmpty()) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(context.depth(), "Nothing else in the merge stack, replacing with null"); return null; } else { // generate a new merge stack from only the remaining items AbstractConfigValue merged = null; for (AbstractConfigValue v : subStack) { if (merged == null) merged = v; else merged = merged.withFallback(v); } return merged; } } @Override ResolveStatus resolveStatus() { return ResolveStatus.UNRESOLVED; } @Override public AbstractConfigValue replaceChild(AbstractConfigValue child, AbstractConfigValue replacement) { List newStack = replaceChildInList(stack, child, replacement); if (newStack == null) return null; else return new ConfigDelayedMerge(origin(), newStack); } @Override public boolean hasDescendant(AbstractConfigValue descendant) { return hasDescendantInList(stack, descendant); } @Override ConfigDelayedMerge relativized(Path prefix) { List newStack = new ArrayList(); for (AbstractConfigValue o : stack) { newStack.add(o.relativized(prefix)); } return new ConfigDelayedMerge(origin(), newStack); } // static utility shared with ConfigDelayedMergeObject static boolean stackIgnoresFallbacks(List stack) { AbstractConfigValue last = stack.get(stack.size() - 1); return last.ignoresFallbacks(); } @Override protected boolean ignoresFallbacks() { return stackIgnoresFallbacks(stack); } @Override protected AbstractConfigValue newCopy(ConfigOrigin newOrigin) { return new ConfigDelayedMerge(newOrigin, stack); } @Override protected final ConfigDelayedMerge mergedWithTheUnmergeable(Unmergeable fallback) { return (ConfigDelayedMerge) mergedWithTheUnmergeable(stack, fallback); } @Override protected final ConfigDelayedMerge mergedWithObject(AbstractConfigObject fallback) { return (ConfigDelayedMerge) mergedWithObject(stack, fallback); } @Override protected ConfigDelayedMerge mergedWithNonObject(AbstractConfigValue fallback) { return (ConfigDelayedMerge) mergedWithNonObject(stack, fallback); } @Override public Collection unmergedValues() { return stack; } @Override protected boolean canEqual(Object other) { return other instanceof ConfigDelayedMerge; } @Override public boolean equals(Object other) { // note that "origin" is deliberately NOT part of equality if (other instanceof ConfigDelayedMerge) { return canEqual(other) && (this.stack == ((ConfigDelayedMerge) other).stack || this.stack .equals(((ConfigDelayedMerge) other).stack)); } else { return false; } } @Override public int hashCode() { // note that "origin" is deliberately NOT part of equality return stack.hashCode(); } @Override protected void render(StringBuilder sb, int indent, boolean atRoot, String atKey, ConfigRenderOptions options) { render(stack, sb, indent, atRoot, atKey, options); } @Override protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { render(sb, indent, atRoot, null, options); } // static method also used by ConfigDelayedMergeObject. static void render(List stack, StringBuilder sb, int indent, boolean atRoot, String atKey, ConfigRenderOptions options) { boolean commentMerge = options.getComments(); if (commentMerge) { sb.append("# unresolved merge of " + stack.size() + " values follows (\n"); if (atKey == null) { indent(sb, indent, options); sb.append("# this unresolved merge will not be parseable because it's at the root of the object\n"); indent(sb, indent, options); sb.append("# the HOCON format has no way to list multiple root objects in a single file\n"); } } List reversed = new ArrayList(); reversed.addAll(stack); Collections.reverse(reversed); int i = 0; for (AbstractConfigValue v : reversed) { if (commentMerge) { indent(sb, indent, options); if (atKey != null) { sb.append("# unmerged value " + i + " for key " + ConfigImplUtil.renderJsonString(atKey) + " from "); } else { sb.append("# unmerged value " + i + " from "); } i += 1; sb.append(v.origin().description()); sb.append("\n"); for (String comment : v.origin().comments()) { indent(sb, indent, options); sb.append("# "); sb.append(comment); sb.append("\n"); } } indent(sb, indent, options); if (atKey != null) { sb.append(ConfigImplUtil.renderJsonString(atKey)); if (options.getFormatted()) sb.append(" : "); else sb.append(":"); } v.render(sb, indent, atRoot, options); sb.append(","); if (options.getFormatted()) sb.append('\n'); } // chop comma or newline sb.setLength(sb.length() - 1); if (options.getFormatted()) { sb.setLength(sb.length() - 1); // also chop comma sb.append("\n"); // put a newline back } if (commentMerge) { indent(sb, indent, options); sb.append("# ) end of unresolved merge\n"); } } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java000066400000000000000000000272741277147274600311720ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValue; // This is just like ConfigDelayedMerge except we know statically // that it will turn out to be an object. final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unmergeable, ReplaceableMergeStack { final private List stack; ConfigDelayedMergeObject(ConfigOrigin origin, List stack) { super(origin); this.stack = stack; if (stack.isEmpty()) throw new ConfigException.BugOrBroken( "creating empty delayed merge object"); if (!(stack.get(0) instanceof AbstractConfigObject)) throw new ConfigException.BugOrBroken( "created a delayed merge object not guaranteed to be an object"); for (AbstractConfigValue v : stack) { if (v instanceof ConfigDelayedMerge || v instanceof ConfigDelayedMergeObject) throw new ConfigException.BugOrBroken( "placed nested DelayedMerge in a ConfigDelayedMergeObject, should have consolidated stack"); } } @Override protected ConfigDelayedMergeObject newCopy(ResolveStatus status, ConfigOrigin origin) { if (status != resolveStatus()) throw new ConfigException.BugOrBroken( "attempt to create resolved ConfigDelayedMergeObject"); return new ConfigDelayedMergeObject(origin, stack); } @Override ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve { ResolveResult merged = ConfigDelayedMerge.resolveSubstitutions(this, stack, context, source); return merged.asObjectResult(); } @Override public AbstractConfigValue makeReplacement(ResolveContext context, int skipping) { return ConfigDelayedMerge.makeReplacement(context, stack, skipping); } @Override ResolveStatus resolveStatus() { return ResolveStatus.UNRESOLVED; } @Override public AbstractConfigValue replaceChild(AbstractConfigValue child, AbstractConfigValue replacement) { List newStack = replaceChildInList(stack, child, replacement); if (newStack == null) return null; else return new ConfigDelayedMergeObject(origin(), newStack); } @Override public boolean hasDescendant(AbstractConfigValue descendant) { return hasDescendantInList(stack, descendant); } @Override ConfigDelayedMergeObject relativized(Path prefix) { List newStack = new ArrayList(); for (AbstractConfigValue o : stack) { newStack.add(o.relativized(prefix)); } return new ConfigDelayedMergeObject(origin(), newStack); } @Override protected boolean ignoresFallbacks() { return ConfigDelayedMerge.stackIgnoresFallbacks(stack); } @Override protected final ConfigDelayedMergeObject mergedWithTheUnmergeable(Unmergeable fallback) { requireNotIgnoringFallbacks(); return (ConfigDelayedMergeObject) mergedWithTheUnmergeable(stack, fallback); } @Override protected final ConfigDelayedMergeObject mergedWithObject(AbstractConfigObject fallback) { return mergedWithNonObject(fallback); } @Override protected final ConfigDelayedMergeObject mergedWithNonObject(AbstractConfigValue fallback) { requireNotIgnoringFallbacks(); return (ConfigDelayedMergeObject) mergedWithNonObject(stack, fallback); } @Override public ConfigDelayedMergeObject withFallback(ConfigMergeable mergeable) { return (ConfigDelayedMergeObject) super.withFallback(mergeable); } @Override public ConfigDelayedMergeObject withOnlyKey(String key) { throw notResolved(); } @Override public ConfigDelayedMergeObject withoutKey(String key) { throw notResolved(); } @Override protected AbstractConfigObject withOnlyPathOrNull(Path path) { throw notResolved(); } @Override AbstractConfigObject withOnlyPath(Path path) { throw notResolved(); } @Override AbstractConfigObject withoutPath(Path path) { throw notResolved(); } @Override public ConfigDelayedMergeObject withValue(String key, ConfigValue value) { throw notResolved(); } @Override ConfigDelayedMergeObject withValue(Path path, ConfigValue value) { throw notResolved(); } @Override public Collection unmergedValues() { return stack; } @Override protected boolean canEqual(Object other) { return other instanceof ConfigDelayedMergeObject; } @Override public boolean equals(Object other) { // note that "origin" is deliberately NOT part of equality if (other instanceof ConfigDelayedMergeObject) { return canEqual(other) && (this.stack == ((ConfigDelayedMergeObject) other).stack || this.stack .equals(((ConfigDelayedMergeObject) other).stack)); } else { return false; } } @Override public int hashCode() { // note that "origin" is deliberately NOT part of equality return stack.hashCode(); } @Override protected void render(StringBuilder sb, int indent, boolean atRoot, String atKey, ConfigRenderOptions options) { ConfigDelayedMerge.render(stack, sb, indent, atRoot, atKey, options); } @Override protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { render(sb, indent, atRoot, null, options); } private static ConfigException notResolved() { return new ConfigException.NotResolved( "need to Config#resolve() before using this object, see the API docs for Config#resolve()"); } @Override public Map unwrapped() { throw notResolved(); } @Override public AbstractConfigValue get(Object key) { throw notResolved(); } @Override public boolean containsKey(Object key) { throw notResolved(); } @Override public boolean containsValue(Object value) { throw notResolved(); } @Override public Set> entrySet() { throw notResolved(); } @Override public boolean isEmpty() { throw notResolved(); } @Override public Set keySet() { throw notResolved(); } @Override public int size() { throw notResolved(); } @Override public Collection values() { throw notResolved(); } @Override protected AbstractConfigValue attemptPeekWithPartialResolve(String key) { // a partial resolve of a ConfigDelayedMergeObject always results in a // SimpleConfigObject because all the substitutions in the stack get // resolved in order to look up the partial. // So we know here that we have not been resolved at all even // partially. // Given that, all this code is probably gratuitous, since the app code // is likely broken. But in general we only throw NotResolved if you try // to touch the exact key that isn't resolved, so this is in that // spirit. // we'll be able to return a key if we have a value that ignores // fallbacks, prior to any unmergeable values. for (AbstractConfigValue layer : stack) { if (layer instanceof AbstractConfigObject) { AbstractConfigObject objectLayer = (AbstractConfigObject) layer; AbstractConfigValue v = objectLayer.attemptPeekWithPartialResolve(key); if (v != null) { if (v.ignoresFallbacks()) { // we know we won't need to merge anything in to this // value return v; } else { // we can't return this value because we know there are // unmergeable values later in the stack that may // contain values that need to be merged with this // value. we'll throw the exception when we get to those // unmergeable values, so continue here. continue; } } else if (layer instanceof Unmergeable) { // an unmergeable object (which would be another // ConfigDelayedMergeObject) can't know that a key is // missing, so it can't return null; it can only return a // value or throw NotPossibleToResolve throw new ConfigException.BugOrBroken( "should not be reached: unmergeable object returned null value"); } else { // a non-unmergeable AbstractConfigObject that returned null // for the key in question is not relevant, we can keep // looking for a value. continue; } } else if (layer instanceof Unmergeable) { throw new ConfigException.NotResolved("Key '" + key + "' is not available at '" + origin().description() + "' because value at '" + layer.origin().description() + "' has not been resolved and may turn out to contain or hide '" + key + "'." + " Be sure to Config#resolve() before using a config object."); } else if (layer.resolveStatus() == ResolveStatus.UNRESOLVED) { // if the layer is not an object, and not a substitution or // merge, // then it's something that's unresolved because it _contains_ // an unresolved object... i.e. it's an array if (!(layer instanceof ConfigList)) throw new ConfigException.BugOrBroken("Expecting a list here, not " + layer); // all later objects will be hidden so we can say we won't find // the key return null; } else { // non-object, but resolved, like an integer or something. // has no children so the one we're after won't be in it. // we would only have this in the stack in case something // else "looks back" to it due to a cycle. // anyway at this point we know we can't find the key anymore. if (!layer.ignoresFallbacks()) { throw new ConfigException.BugOrBroken( "resolved non-object should ignore fallbacks"); } return null; } } // If we get here, then we never found anything unresolved which means // the ConfigDelayedMergeObject should not have existed. some // invariant was violated. throw new ConfigException.BugOrBroken( "Delayed merge stack does not contain any unmergeable values"); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigDocumentParser.java000066400000000000000000000752101277147274600304400ustar00rootroot00000000000000/** * Copyright (C) 2015 Typesafe Inc. */ package com.typesafe.config.impl; import java.util.*; import com.typesafe.config.*; final class ConfigDocumentParser { static ConfigNodeRoot parse(Iterator tokens, ConfigOrigin origin, ConfigParseOptions options) { ConfigSyntax syntax = options.getSyntax() == null ? ConfigSyntax.CONF : options.getSyntax(); ParseContext context = new ParseContext(syntax, origin, tokens); return context.parse(); } static AbstractConfigNodeValue parseValue(Iterator tokens, ConfigOrigin origin, ConfigParseOptions options) { ConfigSyntax syntax = options.getSyntax() == null ? ConfigSyntax.CONF : options.getSyntax(); ParseContext context = new ParseContext(syntax, origin, tokens); return context.parseSingleValue(); } static private final class ParseContext { private int lineNumber; final private Stack buffer; final private Iterator tokens; final private ConfigSyntax flavor; final private ConfigOrigin baseOrigin; // this is the number of "equals" we are inside, // used to modify the error message to reflect that // someone may think this is .properties format. int equalsCount; ParseContext(ConfigSyntax flavor, ConfigOrigin origin, Iterator tokens) { lineNumber = 1; buffer = new Stack(); this.tokens = tokens; this.flavor = flavor; this.equalsCount = 0; this.baseOrigin = origin; } private Token popToken() { if (buffer.isEmpty()) { return tokens.next(); } return buffer.pop(); } private Token nextToken() { Token t = popToken(); if (flavor == ConfigSyntax.JSON) { if (Tokens.isUnquotedText(t) && !isUnquotedWhitespace(t)) { throw parseError("Token not allowed in valid JSON: '" + Tokens.getUnquotedText(t) + "'"); } else if (Tokens.isSubstitution(t)) { throw parseError("Substitutions (${} syntax) not allowed in JSON"); } } return t; } private Token nextTokenCollectingWhitespace(Collection nodes) { while (true) { Token t = nextToken(); if (Tokens.isIgnoredWhitespace(t) || Tokens.isNewline(t) || isUnquotedWhitespace(t)) { nodes.add(new ConfigNodeSingleToken(t)); if (Tokens.isNewline(t)) { lineNumber = t.lineNumber() + 1; } } else if (Tokens.isComment(t)) { nodes.add(new ConfigNodeComment(t)); } else { int newNumber = t.lineNumber(); if (newNumber >= 0) lineNumber = newNumber; return t; } } } private void putBack(Token token) { buffer.push(token); } // In arrays and objects, comma can be omitted // as long as there's at least one newline instead. // this skips any newlines in front of a comma, // skips the comma, and returns true if it found // either a newline or a comma. The iterator // is left just after the comma or the newline. private boolean checkElementSeparator(Collection nodes) { if (flavor == ConfigSyntax.JSON) { Token t = nextTokenCollectingWhitespace(nodes); if (t == Tokens.COMMA) { nodes.add(new ConfigNodeSingleToken(t)); return true; } else { putBack(t); return false; } } else { boolean sawSeparatorOrNewline = false; Token t = nextToken(); while (true) { if (Tokens.isIgnoredWhitespace(t) || isUnquotedWhitespace(t)) { nodes.add(new ConfigNodeSingleToken(t)); } else if (Tokens.isComment(t)) { nodes.add(new ConfigNodeComment(t)); } else if (Tokens.isNewline(t)) { sawSeparatorOrNewline = true; lineNumber++; nodes.add(new ConfigNodeSingleToken(t)); // we want to continue to also eat // a comma if there is one. } else if (t == Tokens.COMMA) { nodes.add(new ConfigNodeSingleToken(t)); return true; } else { // non-newline-or-comma putBack(t); return sawSeparatorOrNewline; } t = nextToken(); } } } // parse a concatenation. If there is no concatenation, return the next value private AbstractConfigNodeValue consolidateValues(Collection nodes) { // this trick is not done in JSON if (flavor == ConfigSyntax.JSON) return null; // create only if we have value tokens ArrayList values = new ArrayList(); int valueCount = 0; // ignore a newline up front Token t = nextTokenCollectingWhitespace(nodes); while (true) { AbstractConfigNodeValue v = null; if (Tokens.isIgnoredWhitespace(t)) { values.add(new ConfigNodeSingleToken(t)); t = nextToken(); continue; } else if (Tokens.isValue(t) || Tokens.isUnquotedText(t) || Tokens.isSubstitution(t) || t == Tokens.OPEN_CURLY || t == Tokens.OPEN_SQUARE) { // there may be newlines _within_ the objects and arrays v = parseValue(t); valueCount++; } else { break; } if (v == null) throw new ConfigException.BugOrBroken("no value"); values.add(v); t = nextToken(); // but don't consolidate across a newline } putBack(t); // No concatenation was seen, but a single value may have been parsed, so return it, and put back // all succeeding tokens if (valueCount < 2) { AbstractConfigNodeValue value = null; for (AbstractConfigNode node : values) { if (node instanceof AbstractConfigNodeValue) value = (AbstractConfigNodeValue)node; else if (value == null) nodes.add(node); else putBack((new ArrayList(node.tokens())).get(0)); } return value; } // Put back any trailing whitespace, as the parent object is responsible for tracking // any leading/trailing whitespace for (int i = values.size() - 1; i >= 0; i--) { if (values.get(i) instanceof ConfigNodeSingleToken) { putBack(((ConfigNodeSingleToken) values.get(i)).token()); values.remove(i); } else { break; } } return new ConfigNodeConcatenation(values); } private ConfigException parseError(String message) { return parseError(message, null); } private ConfigException parseError(String message, Throwable cause) { return new ConfigException.Parse(baseOrigin.withLineNumber(lineNumber), message, cause); } private String addQuoteSuggestion(String badToken, String message) { return addQuoteSuggestion(null, equalsCount > 0, badToken, message); } private String addQuoteSuggestion(Path lastPath, boolean insideEquals, String badToken, String message) { String previousFieldName = lastPath != null ? lastPath.render() : null; String part; if (badToken.equals(Tokens.END.toString())) { // EOF requires special handling for the error to make sense. if (previousFieldName != null) part = message + " (if you intended '" + previousFieldName + "' to be part of a value, instead of a key, " + "try adding double quotes around the whole value"; else return message; } else { if (previousFieldName != null) { part = message + " (if you intended " + badToken + " to be part of the value for '" + previousFieldName + "', " + "try enclosing the value in double quotes"; } else { part = message + " (if you intended " + badToken + " to be part of a key or string value, " + "try enclosing the key or value in double quotes"; } } if (insideEquals) return part + ", or you may be able to rename the file .properties rather than .conf)"; else return part + ")"; } private AbstractConfigNodeValue parseValue(Token t) { AbstractConfigNodeValue v = null; int startingEqualsCount = equalsCount; if (Tokens.isValue(t) || Tokens.isUnquotedText(t) || Tokens.isSubstitution(t)) { v = new ConfigNodeSimpleValue(t); } else if (t == Tokens.OPEN_CURLY) { v = parseObject(true); } else if (t== Tokens.OPEN_SQUARE) { v = parseArray(); } else { throw parseError(addQuoteSuggestion(t.toString(), "Expecting a value but got wrong token: " + t)); } if (equalsCount != startingEqualsCount) throw new ConfigException.BugOrBroken("Bug in config parser: unbalanced equals count"); return v; } private ConfigNodePath parseKey(Token token) { if (flavor == ConfigSyntax.JSON) { if (Tokens.isValueWithType(token, ConfigValueType.STRING)) { return PathParser.parsePathNodeExpression(Collections.singletonList(token).iterator(), null); } else { throw parseError("Expecting close brace } or a field name here, got " + token); } } else { List expression = new ArrayList(); Token t = token; while (Tokens.isValue(t) || Tokens.isUnquotedText(t)) { expression.add(t); t = nextToken(); // note: don't cross a newline } if (expression.isEmpty()) { throw parseError(ExpectingClosingParenthesisError + t); } putBack(t); // put back the token we ended with return PathParser.parsePathNodeExpression(expression.iterator(), null); } } private static boolean isIncludeKeyword(Token t) { return Tokens.isUnquotedText(t) && Tokens.getUnquotedText(t).equals("include"); } private static boolean isUnquotedWhitespace(Token t) { if (!Tokens.isUnquotedText(t)) return false; String s = Tokens.getUnquotedText(t); for (int i = 0; i < s.length(); ++i) { char c = s.charAt(i); if (!ConfigImplUtil.isWhitespace(c)) return false; } return true; } private boolean isKeyValueSeparatorToken(Token t) { if (flavor == ConfigSyntax.JSON) { return t == Tokens.COLON; } else { return t == Tokens.COLON || t == Tokens.EQUALS || t == Tokens.PLUS_EQUALS; } } private final String ExpectingClosingParenthesisError = "expecting a close parentheses ')' here, not: "; private ConfigNodeInclude parseInclude(ArrayList children) { Token t = nextTokenCollectingWhitespace(children); // we either have a 'required()' or a quoted string or the "file()" syntax if (Tokens.isUnquotedText(t)) { String kindText = Tokens.getUnquotedText(t); if (kindText.startsWith("required(")) { String r = kindText.replaceFirst("required\\(",""); if (r.length()>0) { putBack(Tokens.newUnquotedText(t.origin(),r)); } children.add(new ConfigNodeSingleToken(t)); //children.add(new ConfigNodeSingleToken(tOpen)); ConfigNodeInclude res = parseIncludeResource(children, true); t = nextTokenCollectingWhitespace(children); if (Tokens.isUnquotedText(t) && Tokens.getUnquotedText(t).equals(")")) { // OK, close paren } else { throw parseError(ExpectingClosingParenthesisError + t); } return res; } else { putBack(t); return parseIncludeResource(children, false); } } else { putBack(t); return parseIncludeResource(children, false); } } private ConfigNodeInclude parseIncludeResource(ArrayList children, boolean isRequired) { Token t = nextTokenCollectingWhitespace(children); // we either have a quoted string or the "file()" syntax if (Tokens.isUnquotedText(t)) { // get foo( String kindText = Tokens.getUnquotedText(t); ConfigIncludeKind kind; String prefix; if (kindText.startsWith("url(")) { kind = ConfigIncludeKind.URL; prefix = "url("; } else if (kindText.startsWith("file(")) { kind = ConfigIncludeKind.FILE; prefix = "file("; } else if (kindText.startsWith("classpath(")) { kind = ConfigIncludeKind.CLASSPATH; prefix = "classpath("; } else { throw parseError("expecting include parameter to be quoted filename, file(), classpath(), or url(). No spaces are allowed before the open paren. Not expecting: " + t); } String r = kindText.replaceFirst("[^(]*\\(",""); if (r.length()>0) { putBack(Tokens.newUnquotedText(t.origin(),r)); } children.add(new ConfigNodeSingleToken(t)); // skip space inside parens t = nextTokenCollectingWhitespace(children); // quoted string if (!Tokens.isValueWithType(t, ConfigValueType.STRING)) { throw parseError("expecting include " + prefix + ") parameter to be a quoted string, rather than: " + t); } children.add(new ConfigNodeSimpleValue(t)); // skip space after string, inside parens t = nextTokenCollectingWhitespace(children); if (Tokens.isUnquotedText(t) && Tokens.getUnquotedText(t).startsWith(")")) { String rest = Tokens.getUnquotedText(t).substring(1); if (rest.length()>0) { putBack(Tokens.newUnquotedText(t.origin(),rest)); } // OK, close paren } else { throw parseError(ExpectingClosingParenthesisError + t); } return new ConfigNodeInclude(children, kind, isRequired); } else if (Tokens.isValueWithType(t, ConfigValueType.STRING)) { children.add(new ConfigNodeSimpleValue(t)); return new ConfigNodeInclude(children, ConfigIncludeKind.HEURISTIC, isRequired); } else { throw parseError("include keyword is not followed by a quoted string, but by: " + t); } } private ConfigNodeComplexValue parseObject(boolean hadOpenCurly) { // invoked just after the OPEN_CURLY (or START, if !hadOpenCurly) boolean afterComma = false; Path lastPath = null; boolean lastInsideEquals = false; ArrayList objectNodes = new ArrayList(); ArrayList keyValueNodes; HashMap keys = new HashMap(); if (hadOpenCurly) objectNodes.add(new ConfigNodeSingleToken(Tokens.OPEN_CURLY)); while (true) { Token t = nextTokenCollectingWhitespace(objectNodes); if (t == Tokens.CLOSE_CURLY) { if (flavor == ConfigSyntax.JSON && afterComma) { throw parseError(addQuoteSuggestion(t.toString(), "expecting a field name after a comma, got a close brace } instead")); } else if (!hadOpenCurly) { throw parseError(addQuoteSuggestion(t.toString(), "unbalanced close brace '}' with no open brace")); } objectNodes.add(new ConfigNodeSingleToken(Tokens.CLOSE_CURLY)); break; } else if (t == Tokens.END && !hadOpenCurly) { putBack(t); break; } else if (flavor != ConfigSyntax.JSON && isIncludeKeyword(t)) { ArrayList includeNodes = new ArrayList(); includeNodes.add(new ConfigNodeSingleToken(t)); objectNodes.add(parseInclude(includeNodes)); afterComma = false; } else { keyValueNodes = new ArrayList(); Token keyToken = t; ConfigNodePath path = parseKey(keyToken); keyValueNodes.add(path); Token afterKey = nextTokenCollectingWhitespace(keyValueNodes); boolean insideEquals = false; AbstractConfigNodeValue nextValue; if (flavor == ConfigSyntax.CONF && afterKey == Tokens.OPEN_CURLY) { // can omit the ':' or '=' before an object value nextValue = parseValue(afterKey); } else { if (!isKeyValueSeparatorToken(afterKey)) { throw parseError(addQuoteSuggestion(afterKey.toString(), "Key '" + path.render() + "' may not be followed by token: " + afterKey)); } keyValueNodes.add(new ConfigNodeSingleToken(afterKey)); if (afterKey == Tokens.EQUALS) { insideEquals = true; equalsCount += 1; } nextValue = consolidateValues(keyValueNodes); if (nextValue == null) { nextValue = parseValue(nextTokenCollectingWhitespace(keyValueNodes)); } } keyValueNodes.add(nextValue); if (insideEquals) { equalsCount -= 1; } lastInsideEquals = insideEquals; String key = path.value().first(); Path remaining = path.value().remainder(); if (remaining == null) { Boolean existing = keys.get(key); if (existing != null) { // In strict JSON, dups should be an error; while in // our custom config language, they should be merged // if the value is an object (or substitution that // could become an object). if (flavor == ConfigSyntax.JSON) { throw parseError("JSON does not allow duplicate fields: '" + key + "' was already seen"); } } keys.put(key, true); } else { if (flavor == ConfigSyntax.JSON) { throw new ConfigException.BugOrBroken( "somehow got multi-element path in JSON mode"); } keys.put(key, true); } afterComma = false; objectNodes.add(new ConfigNodeField(keyValueNodes)); } if (checkElementSeparator(objectNodes)) { // continue looping afterComma = true; } else { t = nextTokenCollectingWhitespace(objectNodes); if (t == Tokens.CLOSE_CURLY) { if (!hadOpenCurly) { throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals, t.toString(), "unbalanced close brace '}' with no open brace")); } objectNodes.add(new ConfigNodeSingleToken(t)); break; } else if (hadOpenCurly) { throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals, t.toString(), "Expecting close brace } or a comma, got " + t)); } else { if (t == Tokens.END) { putBack(t); break; } else { throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals, t.toString(), "Expecting end of input or a comma, got " + t)); } } } } return new ConfigNodeObject(objectNodes); } private ConfigNodeComplexValue parseArray() { ArrayList children = new ArrayList(); children.add(new ConfigNodeSingleToken(Tokens.OPEN_SQUARE)); // invoked just after the OPEN_SQUARE Token t; AbstractConfigNodeValue nextValue = consolidateValues(children); if (nextValue != null) { children.add(nextValue); } else { t = nextTokenCollectingWhitespace(children); // special-case the first element if (t == Tokens.CLOSE_SQUARE) { children.add(new ConfigNodeSingleToken(t)); return new ConfigNodeArray(children); } else if (Tokens.isValue(t) || t == Tokens.OPEN_CURLY || t == Tokens.OPEN_SQUARE || Tokens.isUnquotedText(t) || Tokens.isSubstitution(t)) { nextValue = parseValue(t); children.add(nextValue); } else { throw parseError("List should have ] or a first element after the open [, instead had token: " + t + " (if you want " + t + " to be part of a string value, then double-quote it)"); } } // now remaining elements while (true) { // just after a value if (checkElementSeparator(children)) { // comma (or newline equivalent) consumed } else { t = nextTokenCollectingWhitespace(children); if (t == Tokens.CLOSE_SQUARE) { children.add(new ConfigNodeSingleToken(t)); return new ConfigNodeArray(children); } else { throw parseError("List should have ended with ] or had a comma, instead had token: " + t + " (if you want " + t + " to be part of a string value, then double-quote it)"); } } // now just after a comma nextValue = consolidateValues(children); if (nextValue != null) { children.add(nextValue); } else { t = nextTokenCollectingWhitespace(children); if (Tokens.isValue(t) || t == Tokens.OPEN_CURLY || t == Tokens.OPEN_SQUARE || Tokens.isUnquotedText(t) || Tokens.isSubstitution(t)) { nextValue = parseValue(t); children.add(nextValue); } else if (flavor != ConfigSyntax.JSON && t == Tokens.CLOSE_SQUARE) { // we allow one trailing comma putBack(t); } else { throw parseError("List should have had new element after a comma, instead had token: " + t + " (if you want the comma or " + t + " to be part of a string value, then double-quote it)"); } } } } ConfigNodeRoot parse() { ArrayList children = new ArrayList(); Token t = nextToken(); if (t == Tokens.START) { // OK } else { throw new ConfigException.BugOrBroken( "token stream did not begin with START, had " + t); } t = nextTokenCollectingWhitespace(children); AbstractConfigNode result = null; boolean missingCurly = false; if (t == Tokens.OPEN_CURLY || t == Tokens.OPEN_SQUARE) { result = parseValue(t); } else { if (flavor == ConfigSyntax.JSON) { if (t == Tokens.END) { throw parseError("Empty document"); } else { throw parseError("Document must have an object or array at root, unexpected token: " + t); } } else { // the root object can omit the surrounding braces. // this token should be the first field's key, or part // of it, so put it back. putBack(t); missingCurly = true; result = parseObject(false); } } // Need to pull the children out of the resulting node so we can keep leading // and trailing whitespace if this was a no-brace object. Otherwise, we need to add // the result into the list of children. if (result instanceof ConfigNodeObject && missingCurly) { children.addAll(((ConfigNodeComplexValue) result).children()); } else { children.add(result); } t = nextTokenCollectingWhitespace(children); if (t == Tokens.END) { if (missingCurly) { // If there were no braces, the entire document should be treated as a single object return new ConfigNodeRoot(Collections.singletonList((AbstractConfigNode)new ConfigNodeObject(children)), baseOrigin); } else { return new ConfigNodeRoot(children, baseOrigin); } } else { throw parseError("Document has trailing tokens after first object or array: " + t); } } // Parse a given input stream into a single value node. Used when doing a replace inside a ConfigDocument. AbstractConfigNodeValue parseSingleValue() { Token t = nextToken(); if (t == Tokens.START) { // OK } else { throw new ConfigException.BugOrBroken( "token stream did not begin with START, had " + t); } t = nextToken(); if (Tokens.isIgnoredWhitespace(t) || Tokens.isNewline(t) || isUnquotedWhitespace(t) || Tokens.isComment(t)) { throw parseError("The value from withValueText cannot have leading or trailing newlines, whitespace, or comments"); } if (t == Tokens.END) { throw parseError("Empty value"); } if (flavor == ConfigSyntax.JSON) { AbstractConfigNodeValue node = parseValue(t); t = nextToken(); if (t == Tokens.END) { return node; } else { throw parseError("Parsing JSON and the value set in withValueText was either a concatenation or " + "had trailing whitespace, newlines, or comments"); } } else { putBack(t); ArrayList nodes = new ArrayList(); AbstractConfigNodeValue node = consolidateValues(nodes); t = nextToken(); if (t == Tokens.END) { return node; } else { throw parseError("The value from withValueText cannot have leading or trailing newlines, whitespace, or comments"); } } } } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigDouble.java000066400000000000000000000026271277147274600267210ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.ObjectStreamException; import java.io.Serializable; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigValueType; final class ConfigDouble extends ConfigNumber implements Serializable { private static final long serialVersionUID = 2L; final private double value; ConfigDouble(ConfigOrigin origin, double value, String originalText) { super(origin, originalText); this.value = value; } @Override public ConfigValueType valueType() { return ConfigValueType.NUMBER; } @Override public Double unwrapped() { return value; } @Override String transformToString() { String s = super.transformToString(); if (s == null) return Double.toString(value); else return s; } @Override protected long longValue() { return (long) value; } @Override protected double doubleValue() { return value; } @Override protected ConfigDouble newCopy(ConfigOrigin origin) { return new ConfigDouble(origin, value, originalText); } // serialization all goes through SerializedConfigValue private Object writeReplace() throws ObjectStreamException { return new SerializedConfigValue(this); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java000066400000000000000000000451051277147274600264060ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.File; import java.lang.ref.WeakReference; import java.net.URL; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.Callable; import com.typesafe.config.Config; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigIncluder; import com.typesafe.config.ConfigMemorySize; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigParseOptions; import com.typesafe.config.ConfigParseable; import com.typesafe.config.ConfigValue; import com.typesafe.config.impl.SimpleIncluder.NameSource; /** * Internal implementation detail, not ABI stable, do not touch. * For use only by the {@link com.typesafe.config} package. */ public class ConfigImpl { private static class LoaderCache { private Config currentSystemProperties; private WeakReference currentLoader; private Map cache; LoaderCache() { this.currentSystemProperties = null; this.currentLoader = new WeakReference(null); this.cache = new HashMap(); } // for now, caching as long as the loader remains the same, // drop entire cache if it changes. synchronized Config getOrElseUpdate(ClassLoader loader, String key, Callable updater) { if (loader != currentLoader.get()) { // reset the cache if we start using a different loader cache.clear(); currentLoader = new WeakReference(loader); } Config systemProperties = systemPropertiesAsConfig(); if (systemProperties != currentSystemProperties) { cache.clear(); currentSystemProperties = systemProperties; } Config config = cache.get(key); if (config == null) { try { config = updater.call(); } catch (RuntimeException e) { throw e; // this will include ConfigException } catch (Exception e) { throw new ConfigException.Generic(e.getMessage(), e); } if (config == null) throw new ConfigException.BugOrBroken("null config from cache updater"); cache.put(key, config); } return config; } } private static class LoaderCacheHolder { static final LoaderCache cache = new LoaderCache(); } public static Config computeCachedConfig(ClassLoader loader, String key, Callable updater) { LoaderCache cache; try { cache = LoaderCacheHolder.cache; } catch (ExceptionInInitializerError e) { throw ConfigImplUtil.extractInitializerError(e); } return cache.getOrElseUpdate(loader, key, updater); } static class FileNameSource implements SimpleIncluder.NameSource { @Override public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) { return Parseable.newFile(new File(name), parseOptions); } }; static class ClasspathNameSource implements SimpleIncluder.NameSource { @Override public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) { return Parseable.newResources(name, parseOptions); } }; static class ClasspathNameSourceWithClass implements SimpleIncluder.NameSource { final private Class klass; public ClasspathNameSourceWithClass(Class klass) { this.klass = klass; } @Override public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) { return Parseable.newResources(klass, name, parseOptions); } }; public static ConfigObject parseResourcesAnySyntax(Class klass, String resourceBasename, ConfigParseOptions baseOptions) { NameSource source = new ClasspathNameSourceWithClass(klass); return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions); } public static ConfigObject parseResourcesAnySyntax(String resourceBasename, ConfigParseOptions baseOptions) { NameSource source = new ClasspathNameSource(); return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions); } public static ConfigObject parseFileAnySyntax(File basename, ConfigParseOptions baseOptions) { NameSource source = new FileNameSource(); return SimpleIncluder.fromBasename(source, basename.getPath(), baseOptions); } static AbstractConfigObject emptyObject(String originDescription) { ConfigOrigin origin = originDescription != null ? SimpleConfigOrigin .newSimple(originDescription) : null; return emptyObject(origin); } public static Config emptyConfig(String originDescription) { return emptyObject(originDescription).toConfig(); } static AbstractConfigObject empty(ConfigOrigin origin) { return emptyObject(origin); } // default origin for values created with fromAnyRef and no origin specified final private static ConfigOrigin defaultValueOrigin = SimpleConfigOrigin .newSimple("hardcoded value"); final private static ConfigBoolean defaultTrueValue = new ConfigBoolean( defaultValueOrigin, true); final private static ConfigBoolean defaultFalseValue = new ConfigBoolean( defaultValueOrigin, false); final private static ConfigNull defaultNullValue = new ConfigNull( defaultValueOrigin); final private static SimpleConfigList defaultEmptyList = new SimpleConfigList( defaultValueOrigin, Collections. emptyList()); final private static SimpleConfigObject defaultEmptyObject = SimpleConfigObject .empty(defaultValueOrigin); private static SimpleConfigList emptyList(ConfigOrigin origin) { if (origin == null || origin == defaultValueOrigin) return defaultEmptyList; else return new SimpleConfigList(origin, Collections. emptyList()); } private static AbstractConfigObject emptyObject(ConfigOrigin origin) { // we want null origin to go to SimpleConfigObject.empty() to get the // origin "empty config" rather than "hardcoded value" if (origin == defaultValueOrigin) return defaultEmptyObject; else return SimpleConfigObject.empty(origin); } private static ConfigOrigin valueOrigin(String originDescription) { if (originDescription == null) return defaultValueOrigin; else return SimpleConfigOrigin.newSimple(originDescription); } public static ConfigValue fromAnyRef(Object object, String originDescription) { ConfigOrigin origin = valueOrigin(originDescription); return fromAnyRef(object, origin, FromMapMode.KEYS_ARE_KEYS); } public static ConfigObject fromPathMap( Map pathMap, String originDescription) { ConfigOrigin origin = valueOrigin(originDescription); return (ConfigObject) fromAnyRef(pathMap, origin, FromMapMode.KEYS_ARE_PATHS); } static AbstractConfigValue fromAnyRef(Object object, ConfigOrigin origin, FromMapMode mapMode) { if (origin == null) throw new ConfigException.BugOrBroken( "origin not supposed to be null"); if (object == null) { if (origin != defaultValueOrigin) return new ConfigNull(origin); else return defaultNullValue; } else if(object instanceof AbstractConfigValue) { return (AbstractConfigValue) object; } else if (object instanceof Boolean) { if (origin != defaultValueOrigin) { return new ConfigBoolean(origin, (Boolean) object); } else if ((Boolean) object) { return defaultTrueValue; } else { return defaultFalseValue; } } else if (object instanceof String) { return new ConfigString.Quoted(origin, (String) object); } else if (object instanceof Number) { // here we always keep the same type that was passed to us, // rather than figuring out if a Long would fit in an Int // or a Double has no fractional part. i.e. deliberately // not using ConfigNumber.newNumber() when we have a // Double, Integer, or Long. if (object instanceof Double) { return new ConfigDouble(origin, (Double) object, null); } else if (object instanceof Integer) { return new ConfigInt(origin, (Integer) object, null); } else if (object instanceof Long) { return new ConfigLong(origin, (Long) object, null); } else { return ConfigNumber.newNumber(origin, ((Number) object).doubleValue(), null); } } else if (object instanceof Duration) { return new ConfigLong(origin, ((Duration) object).toMillis(), null); } else if (object instanceof Map) { if (((Map) object).isEmpty()) return emptyObject(origin); if (mapMode == FromMapMode.KEYS_ARE_KEYS) { Map values = new HashMap(); for (Map.Entry entry : ((Map) object).entrySet()) { Object key = entry.getKey(); if (!(key instanceof String)) throw new ConfigException.BugOrBroken( "bug in method caller: not valid to create ConfigObject from map with non-String key: " + key); AbstractConfigValue value = fromAnyRef(entry.getValue(), origin, mapMode); values.put((String) key, value); } return new SimpleConfigObject(origin, values); } else { return PropertiesParser.fromPathMap(origin, (Map) object); } } else if (object instanceof Iterable) { Iterator i = ((Iterable) object).iterator(); if (!i.hasNext()) return emptyList(origin); List values = new ArrayList(); while (i.hasNext()) { AbstractConfigValue v = fromAnyRef(i.next(), origin, mapMode); values.add(v); } return new SimpleConfigList(origin, values); } else if (object instanceof ConfigMemorySize) { return new ConfigLong(origin, ((ConfigMemorySize) object).toBytes(), null); } else { throw new ConfigException.BugOrBroken( "bug in method caller: not valid to create ConfigValue from: " + object); } } private static class DefaultIncluderHolder { static final ConfigIncluder defaultIncluder = new SimpleIncluder(null); } static ConfigIncluder defaultIncluder() { try { return DefaultIncluderHolder.defaultIncluder; } catch (ExceptionInInitializerError e) { throw ConfigImplUtil.extractInitializerError(e); } } private static Properties getSystemProperties() { // Avoid ConcurrentModificationException due to parallel setting of system properties by copying properties final Properties systemProperties = System.getProperties(); final Properties systemPropertiesCopy = new Properties(); synchronized (systemProperties) { systemPropertiesCopy.putAll(systemProperties); } return systemPropertiesCopy; } private static AbstractConfigObject loadSystemProperties() { return (AbstractConfigObject) Parseable.newProperties(getSystemProperties(), ConfigParseOptions.defaults().setOriginDescription("system properties")).parse(); } private static class SystemPropertiesHolder { // this isn't final due to the reloadSystemPropertiesConfig() hack below static volatile AbstractConfigObject systemProperties = loadSystemProperties(); } static AbstractConfigObject systemPropertiesAsConfigObject() { try { return SystemPropertiesHolder.systemProperties; } catch (ExceptionInInitializerError e) { throw ConfigImplUtil.extractInitializerError(e); } } public static Config systemPropertiesAsConfig() { return systemPropertiesAsConfigObject().toConfig(); } public static void reloadSystemPropertiesConfig() { // ConfigFactory.invalidateCaches() relies on this having the side // effect that it drops all caches SystemPropertiesHolder.systemProperties = loadSystemProperties(); } private static AbstractConfigObject loadEnvVariables() { Map env = System.getenv(); Map m = new HashMap(); for (Map.Entry entry : env.entrySet()) { String key = entry.getKey(); m.put(key, new ConfigString.Quoted(SimpleConfigOrigin.newSimple("env var " + key), entry .getValue())); } return new SimpleConfigObject(SimpleConfigOrigin.newSimple("env variables"), m, ResolveStatus.RESOLVED, false /* ignoresFallbacks */); } private static class EnvVariablesHolder { static final AbstractConfigObject envVariables = loadEnvVariables(); } static AbstractConfigObject envVariablesAsConfigObject() { try { return EnvVariablesHolder.envVariables; } catch (ExceptionInInitializerError e) { throw ConfigImplUtil.extractInitializerError(e); } } public static Config envVariablesAsConfig() { return envVariablesAsConfigObject().toConfig(); } public static Config defaultReference(final ClassLoader loader) { return computeCachedConfig(loader, "defaultReference", new Callable() { @Override public Config call() { Config unresolvedResources = Parseable .newResources("reference.conf", ConfigParseOptions.defaults().setClassLoader(loader)) .parse().toConfig(); return systemPropertiesAsConfig().withFallback(unresolvedResources).resolve(); } }); } private static class DebugHolder { private static String LOADS = "loads"; private static String SUBSTITUTIONS = "substitutions"; private static Map loadDiagnostics() { Map result = new HashMap(); result.put(LOADS, false); result.put(SUBSTITUTIONS, false); // People do -Dconfig.trace=foo,bar to enable tracing of different things String s = System.getProperty("config.trace"); if (s == null) { return result; } else { String[] keys = s.split(","); for (String k : keys) { if (k.equals(LOADS)) { result.put(LOADS, true); } else if (k.equals(SUBSTITUTIONS)) { result.put(SUBSTITUTIONS, true); } else { System.err.println("config.trace property contains unknown trace topic '" + k + "'"); } } return result; } } private static final Map diagnostics = loadDiagnostics(); private static final boolean traceLoadsEnabled = diagnostics.get(LOADS); private static final boolean traceSubstitutionsEnabled = diagnostics.get(SUBSTITUTIONS); static boolean traceLoadsEnabled() { return traceLoadsEnabled; } static boolean traceSubstitutionsEnabled() { return traceSubstitutionsEnabled; } } public static boolean traceLoadsEnabled() { try { return DebugHolder.traceLoadsEnabled(); } catch (ExceptionInInitializerError e) { throw ConfigImplUtil.extractInitializerError(e); } } public static boolean traceSubstitutionsEnabled() { try { return DebugHolder.traceSubstitutionsEnabled(); } catch (ExceptionInInitializerError e) { throw ConfigImplUtil.extractInitializerError(e); } } public static void trace(String message) { System.err.println(message); } public static void trace(int indentLevel, String message) { while (indentLevel > 0) { System.err.print(" "); indentLevel -= 1; } System.err.println(message); } // the basic idea here is to add the "what" and have a canonical // toplevel error message. the "original" exception may however have extra // detail about what happened. call this if you have a better "what" than // further down on the stack. static ConfigException.NotResolved improveNotResolved(Path what, ConfigException.NotResolved original) { String newMessage = what.render() + " has not been resolved, you need to call Config#resolve()," + " see API docs for Config#resolve()"; if (newMessage.equals(original.getMessage())) return original; else return new ConfigException.NotResolved(newMessage, original); } public static ConfigOrigin newSimpleOrigin(String description) { if (description == null) { return defaultValueOrigin; } else { return SimpleConfigOrigin.newSimple(description); } } public static ConfigOrigin newFileOrigin(String filename) { return SimpleConfigOrigin.newFile(filename); } public static ConfigOrigin newURLOrigin(URL url) { return SimpleConfigOrigin.newURL(url); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigImplUtil.java000066400000000000000000000167451277147274600272540ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.List; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; /** * Internal implementation detail, not ABI stable, do not touch. * For use only by the {@link com.typesafe.config} package. */ final public class ConfigImplUtil { static boolean equalsHandlingNull(Object a, Object b) { if (a == null && b != null) return false; else if (a != null && b == null) return false; else if (a == b) // catches null == null plus optimizes identity case return true; else return a.equals(b); } static boolean isC0Control(int codepoint) { return (codepoint >= 0x0000 && codepoint <= 0x001F); } public static String renderJsonString(String s) { StringBuilder sb = new StringBuilder(); sb.append('"'); for (int i = 0; i < s.length(); ++i) { char c = s.charAt(i); switch (c) { case '"': sb.append("\\\""); break; case '\\': sb.append("\\\\"); break; case '\n': sb.append("\\n"); break; case '\b': sb.append("\\b"); break; case '\f': sb.append("\\f"); break; case '\r': sb.append("\\r"); break; case '\t': sb.append("\\t"); break; default: if (isC0Control(c)) sb.append(String.format("\\u%04x", (int) c)); else sb.append(c); } } sb.append('"'); return sb.toString(); } static String renderStringUnquotedIfPossible(String s) { // this can quote unnecessarily as long as it never fails to quote when // necessary if (s.length() == 0) return renderJsonString(s); // if it starts with a hyphen or number, we have to quote // to ensure we end up with a string and not a number int first = s.codePointAt(0); if (Character.isDigit(first) || first == '-') return renderJsonString(s); if (s.startsWith("include") || s.startsWith("true") || s.startsWith("false") || s.startsWith("null") || s.contains("//")) return renderJsonString(s); // only unquote if it's pure alphanumeric for (int i = 0; i < s.length(); ++i) { char c = s.charAt(i); if (!(Character.isLetter(c) || Character.isDigit(c) || c == '-')) return renderJsonString(s); } return s; } static boolean isWhitespace(int codepoint) { switch (codepoint) { // try to hit the most common ASCII ones first, then the nonbreaking // spaces that Java brokenly leaves out of isWhitespace. case ' ': case '\n': case '\u00A0': case '\u2007': case '\u202F': // this one is the BOM, see // http://www.unicode.org/faq/utf_bom.html#BOM // we just accept it as a zero-width nonbreaking space. case '\uFEFF': return true; default: return Character.isWhitespace(codepoint); } } public static String unicodeTrim(String s) { // this is dumb because it looks like there aren't any whitespace // characters that need surrogate encoding. But, points for // pedantic correctness! It's future-proof or something. // String.trim() actually is broken, since there are plenty of // non-ASCII whitespace characters. final int length = s.length(); if (length == 0) return s; int start = 0; while (start < length) { char c = s.charAt(start); if (c == ' ' || c == '\n') { start += 1; } else { int cp = s.codePointAt(start); if (isWhitespace(cp)) start += Character.charCount(cp); else break; } } int end = length; while (end > start) { char c = s.charAt(end - 1); if (c == ' ' || c == '\n') { --end; } else { int cp; int delta; if (Character.isLowSurrogate(c)) { cp = s.codePointAt(end - 2); delta = 2; } else { cp = s.codePointAt(end - 1); delta = 1; } if (isWhitespace(cp)) end -= delta; else break; } } return s.substring(start, end); } public static ConfigException extractInitializerError(ExceptionInInitializerError e) { Throwable cause = e.getCause(); if (cause != null && cause instanceof ConfigException) { return (ConfigException) cause; } else { throw e; } } static File urlToFile(URL url) { // this isn't really right, clearly, but not sure what to do. try { // this will properly handle hex escapes, etc. return new File(url.toURI()); } catch (URISyntaxException e) { // this handles some stuff like file:///c:/Whatever/ // apparently but mangles handling of hex escapes return new File(url.getPath()); } catch (IllegalArgumentException e) { // file://foo with double slash causes // IllegalArgumentException "url has an authority component" return new File(url.getPath()); } } public static String joinPath(String... elements) { return (new Path(elements)).render(); } public static String joinPath(List elements) { return joinPath(elements.toArray(new String[0])); } public static List splitPath(String path) { Path p = Path.newPath(path); List elements = new ArrayList(); while (p != null) { elements.add(p.first()); p = p.remainder(); } return elements; } public static ConfigOrigin readOrigin(ObjectInputStream in) throws IOException { return SerializedConfigValue.readOrigin(in, null); } public static void writeOrigin(ObjectOutputStream out, ConfigOrigin origin) throws IOException { SerializedConfigValue.writeOrigin(new DataOutputStream(out), (SimpleConfigOrigin) origin, null); } static String toCamelCase(String originalName) { String[] words = originalName.split("-+"); StringBuilder nameBuilder = new StringBuilder(originalName.length()); for (String word : words) { if (nameBuilder.length() == 0) { nameBuilder.append(word); } else { nameBuilder.append(word.substring(0, 1).toUpperCase()); nameBuilder.append(word.substring(1)); } } return nameBuilder.toString(); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigIncludeKind.java000066400000000000000000000001421277147274600276660ustar00rootroot00000000000000package com.typesafe.config.impl; enum ConfigIncludeKind { URL, FILE, CLASSPATH, HEURISTIC } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigInt.java000066400000000000000000000026001277147274600262300ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.ObjectStreamException; import java.io.Serializable; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigValueType; final class ConfigInt extends ConfigNumber implements Serializable { private static final long serialVersionUID = 2L; final private int value; ConfigInt(ConfigOrigin origin, int value, String originalText) { super(origin, originalText); this.value = value; } @Override public ConfigValueType valueType() { return ConfigValueType.NUMBER; } @Override public Integer unwrapped() { return value; } @Override String transformToString() { String s = super.transformToString(); if (s == null) return Integer.toString(value); else return s; } @Override protected long longValue() { return value; } @Override protected double doubleValue() { return value; } @Override protected ConfigInt newCopy(ConfigOrigin origin) { return new ConfigInt(origin, value, originalText); } // serialization all goes through SerializedConfigValue private Object writeReplace() throws ObjectStreamException { return new SerializedConfigValue(this); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigLong.java000066400000000000000000000026001277147274600263750ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.ObjectStreamException; import java.io.Serializable; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigValueType; final class ConfigLong extends ConfigNumber implements Serializable { private static final long serialVersionUID = 2L; final private long value; ConfigLong(ConfigOrigin origin, long value, String originalText) { super(origin, originalText); this.value = value; } @Override public ConfigValueType valueType() { return ConfigValueType.NUMBER; } @Override public Long unwrapped() { return value; } @Override String transformToString() { String s = super.transformToString(); if (s == null) return Long.toString(value); else return s; } @Override protected long longValue() { return value; } @Override protected double doubleValue() { return value; } @Override protected ConfigLong newCopy(ConfigOrigin origin) { return new ConfigLong(origin, value, originalText); } // serialization all goes through SerializedConfigValue private Object writeReplace() throws ObjectStreamException { return new SerializedConfigValue(this); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigNodeArray.java000066400000000000000000000005541277147274600273700ustar00rootroot00000000000000package com.typesafe.config.impl; import java.util.Collection; final class ConfigNodeArray extends ConfigNodeComplexValue { ConfigNodeArray(Collection children) { super(children); } @Override protected ConfigNodeArray newNode(Collection nodes) { return new ConfigNodeArray(nodes); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigNodeComment.java000066400000000000000000000007401277147274600277110ustar00rootroot00000000000000package com.typesafe.config.impl; import com.typesafe.config.ConfigException; final class ConfigNodeComment extends ConfigNodeSingleToken { ConfigNodeComment(Token comment) { super(comment); if (!Tokens.isComment(super.token)) { throw new ConfigException.BugOrBroken("Tried to create a ConfigNodeComment from a non-comment token"); } } protected String commentText() { return Tokens.getCommentText(super.token); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigNodeComplexValue.java000066400000000000000000000041161277147274600307140ustar00rootroot00000000000000/** * Copyright (C) 2015 Typesafe Inc. */ package com.typesafe.config.impl; import java.util.*; abstract class ConfigNodeComplexValue extends AbstractConfigNodeValue { final protected ArrayList children; ConfigNodeComplexValue(Collection children) { this.children = new ArrayList(children); } final public Collection children() { return children; } @Override protected Collection tokens() { ArrayList tokens = new ArrayList(); for (AbstractConfigNode child : children) { tokens.addAll(child.tokens()); } return tokens; } protected ConfigNodeComplexValue indentText(AbstractConfigNode indentation) { ArrayList childrenCopy = new ArrayList(children); for (int i = 0; i < childrenCopy.size(); i++) { AbstractConfigNode child = childrenCopy.get(i); if (child instanceof ConfigNodeSingleToken && Tokens.isNewline(((ConfigNodeSingleToken) child).token())) { childrenCopy.add(i + 1, indentation); i++; } else if (child instanceof ConfigNodeField) { AbstractConfigNode value = ((ConfigNodeField) child).value(); if (value instanceof ConfigNodeComplexValue) { childrenCopy.set(i, ((ConfigNodeField) child).replaceValue(((ConfigNodeComplexValue) value).indentText(indentation))); } } else if (child instanceof ConfigNodeComplexValue) { childrenCopy.set(i, ((ConfigNodeComplexValue) child).indentText(indentation)); } } return newNode(childrenCopy); } // This method will just call into the object's constructor, but it's needed // for use in the indentText() method so we can avoid a gross if/else statement // checking the type of this abstract ConfigNodeComplexValue newNode(Collection nodes); } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigNodeConcatenation.java000066400000000000000000000006141277147274600310740ustar00rootroot00000000000000package com.typesafe.config.impl; import java.util.Collection; final class ConfigNodeConcatenation extends ConfigNodeComplexValue { ConfigNodeConcatenation(Collection children) { super(children); } @Override protected ConfigNodeConcatenation newNode(Collection nodes) { return new ConfigNodeConcatenation(nodes); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigNodeField.java000066400000000000000000000051421277147274600273330ustar00rootroot00000000000000/** * Copyright (C) 2015 Typesafe Inc. */ package com.typesafe.config.impl; import com.typesafe.config.ConfigException; import java.util.ArrayList; import java.util.Collection; import java.util.List; final class ConfigNodeField extends AbstractConfigNode { final private ArrayList children; public ConfigNodeField(Collection children) { this.children = new ArrayList(children); } @Override protected Collection tokens() { ArrayList tokens = new ArrayList(); for (AbstractConfigNode child : children) { tokens.addAll(child.tokens()); } return tokens; } public ConfigNodeField replaceValue(AbstractConfigNodeValue newValue) { ArrayList childrenCopy = new ArrayList(children); for (int i = 0; i < childrenCopy.size(); i++) { if (childrenCopy.get(i) instanceof AbstractConfigNodeValue) { childrenCopy.set(i, newValue); return new ConfigNodeField(childrenCopy); } } throw new ConfigException.BugOrBroken("Field node doesn't have a value"); } public AbstractConfigNodeValue value() { for (int i = 0; i < children.size(); i++) { if (children.get(i) instanceof AbstractConfigNodeValue) { return (AbstractConfigNodeValue)children.get(i); } } throw new ConfigException.BugOrBroken("Field node doesn't have a value"); } public ConfigNodePath path() { for (int i = 0; i < children.size(); i++) { if (children.get(i) instanceof ConfigNodePath) { return (ConfigNodePath)children.get(i); } } throw new ConfigException.BugOrBroken("Field node doesn't have a path"); } protected Token separator() { for (AbstractConfigNode child : children) { if (child instanceof ConfigNodeSingleToken) { Token t = ((ConfigNodeSingleToken) child).token(); if (t == Tokens.PLUS_EQUALS || t == Tokens.COLON || t == Tokens.EQUALS) { return t; } } } return null; } protected List comments() { List comments = new ArrayList(); for (AbstractConfigNode child : children) { if (child instanceof ConfigNodeComment) { comments.add(((ConfigNodeComment) child).commentText()); } } return comments; } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigNodeInclude.java000066400000000000000000000024311277147274600276710ustar00rootroot00000000000000package com.typesafe.config.impl; import java.util.ArrayList; import java.util.Collection; final class ConfigNodeInclude extends AbstractConfigNode { final private ArrayList children; final private ConfigIncludeKind kind; final private boolean isRequired; ConfigNodeInclude(Collection children, ConfigIncludeKind kind, boolean isRequired) { this.children = new ArrayList(children); this.kind = kind; this.isRequired = isRequired; } final public Collection children() { return children; } @Override protected Collection tokens() { ArrayList tokens = new ArrayList(); for (AbstractConfigNode child : children) { tokens.addAll(child.tokens()); } return tokens; } protected ConfigIncludeKind kind() { return kind; } protected boolean isRequired() { return isRequired; } protected String name() { for (AbstractConfigNode n : children) { if (n instanceof ConfigNodeSimpleValue) { return (String)Tokens.getValue(((ConfigNodeSimpleValue) n).token()).unwrapped(); } } return null; } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigNodeObject.java000066400000000000000000000347071277147274600275270ustar00rootroot00000000000000package com.typesafe.config.impl; import com.typesafe.config.ConfigSyntax; import java.util.ArrayList; import java.util.Collection; final class ConfigNodeObject extends ConfigNodeComplexValue { ConfigNodeObject(Collection children) { super(children); } @Override protected ConfigNodeObject newNode(Collection nodes) { return new ConfigNodeObject(nodes); } public boolean hasValue(Path desiredPath) { for (AbstractConfigNode node : children) { if (node instanceof ConfigNodeField) { ConfigNodeField field = (ConfigNodeField) node; Path key = field.path().value(); if (key.equals(desiredPath) || key.startsWith(desiredPath)) { return true; } else if (desiredPath.startsWith(key)) { if (field.value() instanceof ConfigNodeObject) { ConfigNodeObject obj = (ConfigNodeObject) field.value(); Path remainingPath = desiredPath.subPath(key.length()); if (obj.hasValue(remainingPath)) { return true; } } } } } return false; } protected ConfigNodeObject changeValueOnPath(Path desiredPath, AbstractConfigNodeValue value, ConfigSyntax flavor) { ArrayList childrenCopy = new ArrayList(super.children); boolean seenNonMatching = false; // Copy the value so we can change it to null but not modify the original parameter AbstractConfigNodeValue valueCopy = value; for (int i = childrenCopy.size() - 1; i >= 0; i--) { if (childrenCopy.get(i) instanceof ConfigNodeSingleToken) { Token t = ((ConfigNodeSingleToken) childrenCopy.get(i)).token(); // Ensure that, when we are removing settings in JSON, we don't end up with a trailing comma if (flavor == ConfigSyntax.JSON && !seenNonMatching && t == Tokens.COMMA) { childrenCopy.remove(i); } continue; } else if (!(childrenCopy.get(i) instanceof ConfigNodeField)) { continue; } ConfigNodeField node = (ConfigNodeField) childrenCopy.get(i); Path key = node.path().value(); // Delete all multi-element paths that start with the desired path, since technically they are duplicates if ((valueCopy == null && key.equals(desiredPath))|| (key.startsWith(desiredPath) && !key.equals(desiredPath))) { childrenCopy.remove(i); // Remove any whitespace or commas after the deleted setting for (int j = i; j < childrenCopy.size(); j++) { if (childrenCopy.get(j) instanceof ConfigNodeSingleToken) { Token t = ((ConfigNodeSingleToken) childrenCopy.get(j)).token(); if (Tokens.isIgnoredWhitespace(t) || t == Tokens.COMMA) { childrenCopy.remove(j); j--; } else { break; } } else { break; } } } else if (key.equals(desiredPath)) { seenNonMatching = true; AbstractConfigNodeValue indentedValue; AbstractConfigNode before = i - 1 > 0 ? childrenCopy.get(i - 1) : null; if (value instanceof ConfigNodeComplexValue && before instanceof ConfigNodeSingleToken && Tokens.isIgnoredWhitespace(((ConfigNodeSingleToken) before).token())) indentedValue = ((ConfigNodeComplexValue) value).indentText(before); else indentedValue = value; childrenCopy.set(i, node.replaceValue(indentedValue)); valueCopy = null; } else if (desiredPath.startsWith(key)) { seenNonMatching = true; if (node.value() instanceof ConfigNodeObject) { Path remainingPath = desiredPath.subPath(key.length()); childrenCopy.set(i, node.replaceValue(((ConfigNodeObject) node.value()).changeValueOnPath(remainingPath, valueCopy, flavor))); if (valueCopy != null && !node.equals(super.children.get(i))) valueCopy = null; } } else { seenNonMatching = true; } } return new ConfigNodeObject(childrenCopy); } public ConfigNodeObject setValueOnPath(String desiredPath, AbstractConfigNodeValue value) { return setValueOnPath(desiredPath, value, ConfigSyntax.CONF); } public ConfigNodeObject setValueOnPath(String desiredPath, AbstractConfigNodeValue value, ConfigSyntax flavor) { ConfigNodePath path = PathParser.parsePathNode(desiredPath, flavor); return setValueOnPath(path, value, flavor); } private ConfigNodeObject setValueOnPath(ConfigNodePath desiredPath, AbstractConfigNodeValue value, ConfigSyntax flavor) { ConfigNodeObject node = changeValueOnPath(desiredPath.value(), value, flavor); // If the desired Path did not exist, add it if (!node.hasValue(desiredPath.value())) { return node.addValueOnPath(desiredPath, value, flavor); } return node; } private Collection indentation() { boolean seenNewLine = false; ArrayList indentation = new ArrayList(); if (children.isEmpty()) { return indentation; } for (int i = 0; i < children.size(); i++) { if (!seenNewLine) { if (children.get(i) instanceof ConfigNodeSingleToken && Tokens.isNewline(((ConfigNodeSingleToken) children.get(i)).token())) { seenNewLine = true; indentation.add(new ConfigNodeSingleToken(Tokens.newLine(null))); } } else { if (children.get(i) instanceof ConfigNodeSingleToken && Tokens.isIgnoredWhitespace(((ConfigNodeSingleToken) children.get(i)).token()) && i + 1 < children.size() && (children.get(i+1) instanceof ConfigNodeField || children.get(i+1) instanceof ConfigNodeInclude)) { // Return the indentation of the first setting on its own line indentation.add(children.get(i)); return indentation; } } } if (indentation.isEmpty()) { indentation.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " "))); } else { // Calculate the indentation of the ending curly-brace to get the indentation of the root object AbstractConfigNode last = children.get(children.size() - 1); if (last instanceof ConfigNodeSingleToken && ((ConfigNodeSingleToken) last).token() == Tokens.CLOSE_CURLY) { AbstractConfigNode beforeLast = children.get(children.size() - 2); String indent = ""; if (beforeLast instanceof ConfigNodeSingleToken && Tokens.isIgnoredWhitespace(((ConfigNodeSingleToken) beforeLast).token())) indent = ((ConfigNodeSingleToken) beforeLast).token().tokenText(); indent += " "; indentation.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, indent))); return indentation; } } // The object has no curly braces and is at the root level, so don't indent return indentation; } protected ConfigNodeObject addValueOnPath(ConfigNodePath desiredPath, AbstractConfigNodeValue value, ConfigSyntax flavor) { Path path = desiredPath.value(); ArrayList childrenCopy = new ArrayList(super.children); ArrayList indentation = new ArrayList(indentation()); // If the value we're inserting is a complex value, we'll need to indent it for insertion AbstractConfigNodeValue indentedValue; if (value instanceof ConfigNodeComplexValue && !indentation.isEmpty()) { indentedValue = ((ConfigNodeComplexValue) value).indentText(indentation.get(indentation.size() - 1)); } else { indentedValue = value; } boolean sameLine = !(indentation.size() > 0 && indentation.get(0) instanceof ConfigNodeSingleToken && Tokens.isNewline(((ConfigNodeSingleToken) indentation.get(0)).token())); // If the path is of length greater than one, see if the value needs to be added further down if (path.length() > 1) { for (int i = super.children.size() - 1; i >= 0; i--) { if (!(super.children.get(i) instanceof ConfigNodeField)) { continue; } ConfigNodeField node = (ConfigNodeField) super.children.get(i); Path key = node.path().value(); if (path.startsWith(key) && node.value() instanceof ConfigNodeObject) { ConfigNodePath remainingPath = desiredPath.subPath(key.length()); ConfigNodeObject newValue = (ConfigNodeObject) node.value(); childrenCopy.set(i, node.replaceValue(newValue.addValueOnPath(remainingPath, value, flavor))); return new ConfigNodeObject(childrenCopy); } } } // Otherwise, construct the new setting boolean startsWithBrace = !super.children.isEmpty() && super.children.get(0) instanceof ConfigNodeSingleToken && ((ConfigNodeSingleToken) super.children.get(0)).token() == Tokens.OPEN_CURLY; ArrayList newNodes = new ArrayList(); newNodes.addAll(indentation); newNodes.add(desiredPath.first()); newNodes.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " "))); newNodes.add(new ConfigNodeSingleToken(Tokens.COLON)); newNodes.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " "))); if (path.length() == 1) { newNodes.add(indentedValue); } else { // If the path is of length greater than one add the required new objects along the path ArrayList newObjectNodes = new ArrayList(); newObjectNodes.add(new ConfigNodeSingleToken(Tokens.OPEN_CURLY)); if (indentation.isEmpty()) { newObjectNodes.add(new ConfigNodeSingleToken(Tokens.newLine(null))); } newObjectNodes.addAll(indentation); newObjectNodes.add(new ConfigNodeSingleToken(Tokens.CLOSE_CURLY)); ConfigNodeObject newObject = new ConfigNodeObject(newObjectNodes); newNodes.add(newObject.addValueOnPath(desiredPath.subPath(1), indentedValue, flavor)); } // Combine these two cases so that we only have to iterate once if (flavor == ConfigSyntax.JSON || startsWithBrace || sameLine) { for (int i = childrenCopy.size() - 1; i >= 0; i--) { // If we are in JSON or are adding a setting on the same line, we need to add a comma to the // last setting if ((flavor == ConfigSyntax.JSON || sameLine) && childrenCopy.get(i) instanceof ConfigNodeField) { if (i+1 >= childrenCopy.size() || !(childrenCopy.get(i+1) instanceof ConfigNodeSingleToken && ((ConfigNodeSingleToken) childrenCopy.get(i+1)).token() == Tokens.COMMA)) childrenCopy.add(i+1, new ConfigNodeSingleToken(Tokens.COMMA)); break; } // Add the value into the copy of the children map, keeping any whitespace/newlines // before the close curly brace if (startsWithBrace && childrenCopy.get(i) instanceof ConfigNodeSingleToken && ((ConfigNodeSingleToken) childrenCopy.get(i)).token == Tokens.CLOSE_CURLY) { AbstractConfigNode previous = childrenCopy.get(i - 1); if (previous instanceof ConfigNodeSingleToken && Tokens.isNewline(((ConfigNodeSingleToken) previous).token())) { childrenCopy.add(i - 1, new ConfigNodeField(newNodes)); i--; } else if (previous instanceof ConfigNodeSingleToken && Tokens.isIgnoredWhitespace(((ConfigNodeSingleToken) previous).token())) { AbstractConfigNode beforePrevious = childrenCopy.get(i - 2); if (sameLine) { childrenCopy.add(i - 1, new ConfigNodeField(newNodes)); i--; } else if (beforePrevious instanceof ConfigNodeSingleToken && Tokens.isNewline(((ConfigNodeSingleToken) beforePrevious).token())) { childrenCopy.add(i - 2, new ConfigNodeField(newNodes)); i -= 2; } else { childrenCopy.add(i, new ConfigNodeField(newNodes)); } } else childrenCopy.add(i, new ConfigNodeField(newNodes)); } } } if (!startsWithBrace) { if (!childrenCopy.isEmpty() && childrenCopy.get(childrenCopy.size() - 1) instanceof ConfigNodeSingleToken && Tokens.isNewline(((ConfigNodeSingleToken) childrenCopy.get(childrenCopy.size() - 1)).token())) childrenCopy.add(childrenCopy.size() - 1, new ConfigNodeField(newNodes)); else childrenCopy.add(new ConfigNodeField(newNodes)); } return new ConfigNodeObject(childrenCopy); } public ConfigNodeObject removeValueOnPath(String desiredPath, ConfigSyntax flavor) { Path path = PathParser.parsePathNode(desiredPath, flavor).value(); return changeValueOnPath(path, null, flavor); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigNodePath.java000066400000000000000000000032101277147274600271760ustar00rootroot00000000000000/** * Copyright (C) 2015 Typesafe Inc. */ package com.typesafe.config.impl; import com.typesafe.config.ConfigException; import java.util.ArrayList; import java.util.Collection; final class ConfigNodePath extends AbstractConfigNode { final private Path path; final ArrayList tokens; ConfigNodePath(Path path, Collection tokens) { this.path = path; this.tokens = new ArrayList(tokens); } @Override protected Collection tokens() { return tokens; } protected Path value() { return path; } protected ConfigNodePath subPath(int toRemove) { int periodCount = 0; ArrayList tokensCopy = new ArrayList(tokens); for (int i = 0; i < tokensCopy.size(); i++) { if (Tokens.isUnquotedText(tokensCopy.get(i)) && tokensCopy.get(i).tokenText().equals(".")) periodCount++; if (periodCount == toRemove) { return new ConfigNodePath(path.subPath(toRemove), tokensCopy.subList(i + 1, tokensCopy.size())); } } throw new ConfigException.BugOrBroken("Tried to remove too many elements from a Path node"); } protected ConfigNodePath first() { ArrayList tokensCopy = new ArrayList(tokens); for (int i = 0; i < tokensCopy.size(); i++) { if (Tokens.isUnquotedText(tokensCopy.get(i)) && tokensCopy.get(i).tokenText().equals(".")) return new ConfigNodePath(path.subPath(0, 1), tokensCopy.subList(0, i)); } return this; } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigNodeRoot.java000066400000000000000000000057341277147274600272420ustar00rootroot00000000000000package com.typesafe.config.impl; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigSyntax; import java.util.ArrayList; import java.util.Collection; final class ConfigNodeRoot extends ConfigNodeComplexValue { final private ConfigOrigin origin; ConfigNodeRoot(Collection children, ConfigOrigin origin) { super(children); this.origin = origin; } @Override protected ConfigNodeRoot newNode(Collection nodes) { throw new ConfigException.BugOrBroken("Tried to indent the root object"); } protected ConfigNodeComplexValue value() { for (AbstractConfigNode node : children) { if (node instanceof ConfigNodeComplexValue) { return (ConfigNodeComplexValue)node; } } throw new ConfigException.BugOrBroken("ConfigNodeRoot did not contain a value"); } protected ConfigNodeRoot setValue(String desiredPath, AbstractConfigNodeValue value, ConfigSyntax flavor) { ArrayList childrenCopy = new ArrayList(children); for (int i = 0; i < childrenCopy.size(); i++) { AbstractConfigNode node = childrenCopy.get(i); if (node instanceof ConfigNodeComplexValue) { if (node instanceof ConfigNodeArray) { throw new ConfigException.WrongType(origin, "The ConfigDocument had an array at the root level, and values cannot be modified inside an array."); } else if (node instanceof ConfigNodeObject) { if (value == null) { childrenCopy.set(i, ((ConfigNodeObject)node).removeValueOnPath(desiredPath, flavor)); } else { childrenCopy.set(i, ((ConfigNodeObject) node).setValueOnPath(desiredPath, value, flavor)); } return new ConfigNodeRoot(childrenCopy, origin); } } } throw new ConfigException.BugOrBroken("ConfigNodeRoot did not contain a value"); } protected boolean hasValue(String desiredPath) { Path path = PathParser.parsePath(desiredPath); ArrayList childrenCopy = new ArrayList(children); for (int i = 0; i < childrenCopy.size(); i++) { AbstractConfigNode node = childrenCopy.get(i); if (node instanceof ConfigNodeComplexValue) { if (node instanceof ConfigNodeArray) { throw new ConfigException.WrongType(origin, "The ConfigDocument had an array at the root level, and values cannot be modified inside an array."); } else if (node instanceof ConfigNodeObject) { return ((ConfigNodeObject) node).hasValue(path); } } } throw new ConfigException.BugOrBroken("ConfigNodeRoot did not contain a value"); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigNodeSimpleValue.java000066400000000000000000000024721277147274600305410ustar00rootroot00000000000000/** * Copyright (C) 2015 Typesafe Inc. */ package com.typesafe.config.impl; import com.typesafe.config.ConfigException; import java.util.Collection; import java.util.Collections; import java.util.List; final class ConfigNodeSimpleValue extends AbstractConfigNodeValue { final Token token; ConfigNodeSimpleValue(Token value) { token = value; } @Override protected Collection tokens() { return Collections.singletonList(token); } protected Token token() { return token; } protected AbstractConfigValue value() { if (Tokens.isValue(token)) return Tokens.getValue(token); else if (Tokens.isUnquotedText(token)) return new ConfigString.Unquoted(token.origin(), Tokens.getUnquotedText(token)); else if (Tokens.isSubstitution(token)) { List expression = Tokens.getSubstitutionPathExpression(token); Path path = PathParser.parsePathExpression(expression.iterator(), token.origin()); boolean optional = Tokens.getSubstitutionOptional(token); return new ConfigReference(token.origin(), new SubstitutionExpression(path, optional)); } throw new ConfigException.BugOrBroken("ConfigNodeSimpleValue did not contain a valid value token"); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigNodeSingleToken.java000066400000000000000000000007221277147274600305310ustar00rootroot00000000000000/** * Copyright (C) 2015 Typesafe Inc. */ package com.typesafe.config.impl; import java.util.Collection; import java.util.Collections; class ConfigNodeSingleToken extends AbstractConfigNode { final Token token; ConfigNodeSingleToken(Token t) { token = t; } @Override protected Collection tokens() { return Collections.singletonList(token); } protected Token token() { return token; } }config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigNull.java000066400000000000000000000030011277147274600264040ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.ObjectStreamException; import java.io.Serializable; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValueType; /** * This exists because sometimes null is not the same as missing. Specifically, * if a value is set to null we can give a better error message (indicating * where it was set to null) in case someone asks for the value. Also, null * overrides values set "earlier" in the search path, while missing values do * not. * */ final class ConfigNull extends AbstractConfigValue implements Serializable { private static final long serialVersionUID = 2L; ConfigNull(ConfigOrigin origin) { super(origin); } @Override public ConfigValueType valueType() { return ConfigValueType.NULL; } @Override public Object unwrapped() { return null; } @Override String transformToString() { return "null"; } @Override protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { sb.append("null"); } @Override protected ConfigNull newCopy(ConfigOrigin origin) { return new ConfigNull(origin); } // serialization all goes through SerializedConfigValue private Object writeReplace() throws ObjectStreamException { return new SerializedConfigValue(this); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigNumber.java000066400000000000000000000065361277147274600267420ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.ObjectStreamException; import java.io.Serializable; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; abstract class ConfigNumber extends AbstractConfigValue implements Serializable { private static final long serialVersionUID = 2L; // This is so when we concatenate a number into a string (say it appears in // a sentence) we always have it exactly as the person typed it into the // config file. It's purely cosmetic; equals/hashCode don't consider this // for example. final protected String originalText; protected ConfigNumber(ConfigOrigin origin, String originalText) { super(origin); this.originalText = originalText; } @Override public abstract Number unwrapped(); @Override String transformToString() { return originalText; } int intValueRangeChecked(String path) { long l = longValue(); if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) { throw new ConfigException.WrongType(origin(), path, "32-bit integer", "out-of-range value " + l); } return (int) l; } protected abstract long longValue(); protected abstract double doubleValue(); private boolean isWhole() { long asLong = longValue(); return asLong == doubleValue(); } @Override protected boolean canEqual(Object other) { return other instanceof ConfigNumber; } @Override public boolean equals(Object other) { // note that "origin" is deliberately NOT part of equality if (other instanceof ConfigNumber && canEqual(other)) { ConfigNumber n = (ConfigNumber) other; if (isWhole()) { return n.isWhole() && this.longValue() == n.longValue(); } else { return (!n.isWhole()) && this.doubleValue() == n.doubleValue(); } } else { return false; } } @Override public int hashCode() { // note that "origin" is deliberately NOT part of equality // this matches what standard Long.hashCode and Double.hashCode // do, though I don't think it really matters. long asLong; if (isWhole()) { asLong = longValue(); } else { asLong = Double.doubleToLongBits(doubleValue()); } return (int) (asLong ^ (asLong >>> 32)); } static ConfigNumber newNumber(ConfigOrigin origin, long number, String originalText) { if (number <= Integer.MAX_VALUE && number >= Integer.MIN_VALUE) return new ConfigInt(origin, (int) number, originalText); else return new ConfigLong(origin, number, originalText); } static ConfigNumber newNumber(ConfigOrigin origin, double number, String originalText) { long asLong = (long) number; if (asLong == number) { return newNumber(origin, asLong, originalText); } else { return new ConfigDouble(origin, number, originalText); } } // serialization all goes through SerializedConfigValue private Object writeReplace() throws ObjectStreamException { return new SerializedConfigValue(this); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigParser.java000066400000000000000000000457531277147274600267520ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import com.typesafe.config.*; final class ConfigParser { static AbstractConfigValue parse(ConfigNodeRoot document, ConfigOrigin origin, ConfigParseOptions options, ConfigIncludeContext includeContext) { ParseContext context = new ParseContext(options.getSyntax(), origin, document, SimpleIncluder.makeFull(options.getIncluder()), includeContext); return context.parse(); } static private final class ParseContext { private int lineNumber; final private ConfigNodeRoot document; final private FullIncluder includer; final private ConfigIncludeContext includeContext; final private ConfigSyntax flavor; final private ConfigOrigin baseOrigin; final private LinkedList pathStack; // the number of lists we are inside; this is used to detect the "cannot // generate a reference to a list element" problem, and once we fix that // problem we should be able to get rid of this variable. int arrayCount; ParseContext(ConfigSyntax flavor, ConfigOrigin origin, ConfigNodeRoot document, FullIncluder includer, ConfigIncludeContext includeContext) { lineNumber = 1; this.document = document; this.flavor = flavor; this.baseOrigin = origin; this.includer = includer; this.includeContext = includeContext; this.pathStack = new LinkedList(); this.arrayCount = 0; } // merge a bunch of adjacent values into one // value; change unquoted text into a string // value. private AbstractConfigValue parseConcatenation(ConfigNodeConcatenation n) { // this trick is not done in JSON if (flavor == ConfigSyntax.JSON) throw new ConfigException.BugOrBroken("Found a concatenation node in JSON"); List values = new ArrayList(); for (AbstractConfigNode node : n.children()) { AbstractConfigValue v = null; if (node instanceof AbstractConfigNodeValue) { v = parseValue((AbstractConfigNodeValue)node, null); values.add(v); } } return ConfigConcatenation.concatenate(values); } private SimpleConfigOrigin lineOrigin() { return ((SimpleConfigOrigin) baseOrigin).withLineNumber(lineNumber); } private ConfigException parseError(String message) { return parseError(message, null); } private ConfigException parseError(String message, Throwable cause) { return new ConfigException.Parse(lineOrigin(), message, cause); } private Path fullCurrentPath() { // pathStack has top of stack at front if (pathStack.isEmpty()) throw new ConfigException.BugOrBroken("Bug in parser; tried to get current path when at root"); else return new Path(pathStack.descendingIterator()); } private AbstractConfigValue parseValue(AbstractConfigNodeValue n, List comments) { AbstractConfigValue v; int startingArrayCount = arrayCount; if (n instanceof ConfigNodeSimpleValue) { v = ((ConfigNodeSimpleValue) n).value(); } else if (n instanceof ConfigNodeObject) { v = parseObject((ConfigNodeObject)n); } else if (n instanceof ConfigNodeArray) { v = parseArray((ConfigNodeArray)n); } else if (n instanceof ConfigNodeConcatenation) { v = parseConcatenation((ConfigNodeConcatenation)n); } else { throw parseError("Expecting a value but got wrong node type: " + n.getClass()); } if (comments != null && !comments.isEmpty()) { v = v.withOrigin(v.origin().prependComments(new ArrayList(comments))); comments.clear(); } if (arrayCount != startingArrayCount) throw new ConfigException.BugOrBroken("Bug in config parser: unbalanced array count"); return v; } private static AbstractConfigObject createValueUnderPath(Path path, AbstractConfigValue value) { // for path foo.bar, we are creating // { "foo" : { "bar" : value } } List keys = new ArrayList(); String key = path.first(); Path remaining = path.remainder(); while (key != null) { keys.add(key); if (remaining == null) { break; } else { key = remaining.first(); remaining = remaining.remainder(); } } // the withComments(null) is to ensure comments are only // on the exact leaf node they apply to. // a comment before "foo.bar" applies to the full setting // "foo.bar" not also to "foo" ListIterator i = keys.listIterator(keys.size()); String deepest = i.previous(); AbstractConfigObject o = new SimpleConfigObject(value.origin().withComments(null), Collections. singletonMap( deepest, value)); while (i.hasPrevious()) { Map m = Collections. singletonMap( i.previous(), o); o = new SimpleConfigObject(value.origin().withComments(null), m); } return o; } private void parseInclude(Map values, ConfigNodeInclude n) { boolean isRequired = n.isRequired(); ConfigIncludeContext cic = includeContext.setParseOptions(includeContext.parseOptions().setAllowMissing(!isRequired)); AbstractConfigObject obj; switch (n.kind()) { case URL: URL url; try { url = new URL(n.name()); } catch (MalformedURLException e) { throw parseError("include url() specifies an invalid URL: " + n.name(), e); } obj = (AbstractConfigObject) includer.includeURL(cic, url); break; case FILE: obj = (AbstractConfigObject) includer.includeFile(cic, new File(n.name())); break; case CLASSPATH: obj = (AbstractConfigObject) includer.includeResources(cic, n.name()); break; case HEURISTIC: obj = (AbstractConfigObject) includer .include(cic, n.name()); break; default: throw new ConfigException.BugOrBroken("should not be reached"); } // we really should make this work, but for now throwing an // exception is better than producing an incorrect result. // See https://github.com/typesafehub/config/issues/160 if (arrayCount > 0 && obj.resolveStatus() != ResolveStatus.RESOLVED) throw parseError("Due to current limitations of the config parser, when an include statement is nested inside a list value, " + "${} substitutions inside the included file cannot be resolved correctly. Either move the include outside of the list value or " + "remove the ${} statements from the included file."); if (!pathStack.isEmpty()) { Path prefix = fullCurrentPath(); obj = obj.relativized(prefix); } for (String key : obj.keySet()) { AbstractConfigValue v = obj.get(key); AbstractConfigValue existing = values.get(key); if (existing != null) { values.put(key, v.withFallback(existing)); } else { values.put(key, v); } } } private AbstractConfigObject parseObject(ConfigNodeObject n) { Map values = new HashMap(); SimpleConfigOrigin objectOrigin = lineOrigin(); boolean lastWasNewline = false; ArrayList nodes = new ArrayList(n.children()); List comments = new ArrayList(); for (int i = 0; i < nodes.size(); i++) { AbstractConfigNode node = nodes.get(i); if (node instanceof ConfigNodeComment) { lastWasNewline = false; comments.add(((ConfigNodeComment) node).commentText()); } else if (node instanceof ConfigNodeSingleToken && Tokens.isNewline(((ConfigNodeSingleToken) node).token())) { lineNumber++; if (lastWasNewline) { // Drop all comments if there was a blank line and start a new comment block comments.clear(); } lastWasNewline = true; } else if (flavor != ConfigSyntax.JSON && node instanceof ConfigNodeInclude) { parseInclude(values, (ConfigNodeInclude)node); lastWasNewline = false; } else if (node instanceof ConfigNodeField) { lastWasNewline = false; Path path = ((ConfigNodeField) node).path().value(); comments.addAll(((ConfigNodeField) node).comments()); // path must be on-stack while we parse the value pathStack.push(path); if (((ConfigNodeField) node).separator() == Tokens.PLUS_EQUALS) { // we really should make this work, but for now throwing // an exception is better than producing an incorrect // result. See // https://github.com/typesafehub/config/issues/160 if (arrayCount > 0) throw parseError("Due to current limitations of the config parser, += does not work nested inside a list. " + "+= expands to a ${} substitution and the path in ${} cannot currently refer to list elements. " + "You might be able to move the += outside of the list and then refer to it from inside the list with ${}."); // because we will put it in an array after the fact so // we want this to be incremented during the parseValue // below in order to throw the above exception. arrayCount += 1; } AbstractConfigNodeValue valueNode; AbstractConfigValue newValue; valueNode = ((ConfigNodeField) node).value(); // comments from the key token go to the value token newValue = parseValue(valueNode, comments); if (((ConfigNodeField) node).separator() == Tokens.PLUS_EQUALS) { arrayCount -= 1; List concat = new ArrayList(2); AbstractConfigValue previousRef = new ConfigReference(newValue.origin(), new SubstitutionExpression(fullCurrentPath(), true /* optional */)); AbstractConfigValue list = new SimpleConfigList(newValue.origin(), Collections.singletonList(newValue)); concat.add(previousRef); concat.add(list); newValue = ConfigConcatenation.concatenate(concat); } // Grab any trailing comments on the same line if (i < nodes.size() - 1) { i++; while (i < nodes.size()) { if (nodes.get(i) instanceof ConfigNodeComment) { ConfigNodeComment comment = (ConfigNodeComment) nodes.get(i); newValue = newValue.withOrigin(newValue.origin().appendComments( Collections.singletonList(comment.commentText()))); break; } else if (nodes.get(i) instanceof ConfigNodeSingleToken) { ConfigNodeSingleToken curr = (ConfigNodeSingleToken) nodes.get(i); if (curr.token() == Tokens.COMMA || Tokens.isIgnoredWhitespace(curr.token())) { // keep searching, as there could still be a comment } else { i--; break; } } else { i--; break; } i++; } } pathStack.pop(); String key = path.first(); Path remaining = path.remainder(); if (remaining == null) { AbstractConfigValue existing = values.get(key); if (existing != null) { // In strict JSON, dups should be an error; while in // our custom config language, they should be merged // if the value is an object (or substitution that // could become an object). if (flavor == ConfigSyntax.JSON) { throw parseError("JSON does not allow duplicate fields: '" + key + "' was already seen at " + existing.origin().description()); } else { newValue = newValue.withFallback(existing); } } values.put(key, newValue); } else { if (flavor == ConfigSyntax.JSON) { throw new ConfigException.BugOrBroken( "somehow got multi-element path in JSON mode"); } AbstractConfigObject obj = createValueUnderPath( remaining, newValue); AbstractConfigValue existing = values.get(key); if (existing != null) { obj = obj.withFallback(existing); } values.put(key, obj); } } } return new SimpleConfigObject(objectOrigin, values); } private SimpleConfigList parseArray(ConfigNodeArray n) { arrayCount += 1; SimpleConfigOrigin arrayOrigin = lineOrigin(); List values = new ArrayList(); boolean lastWasNewLine = false; List comments = new ArrayList(); AbstractConfigValue v = null; for (AbstractConfigNode node : n.children()) { if (node instanceof ConfigNodeComment) { comments.add(((ConfigNodeComment) node).commentText()); lastWasNewLine = false; } else if (node instanceof ConfigNodeSingleToken && Tokens.isNewline(((ConfigNodeSingleToken) node).token())) { lineNumber++; if (lastWasNewLine && v == null) { comments.clear(); } else if (v != null) { values.add(v.withOrigin(v.origin().appendComments(new ArrayList(comments)))); comments.clear(); v = null; } lastWasNewLine = true; } else if (node instanceof AbstractConfigNodeValue) { lastWasNewLine = false; if (v != null) { values.add(v.withOrigin(v.origin().appendComments(new ArrayList(comments)))); comments.clear(); } v = parseValue((AbstractConfigNodeValue)node, comments); } } // There shouldn't be any comments at this point, but add them just in case if (v != null) { values.add(v.withOrigin(v.origin().appendComments(new ArrayList(comments)))); } arrayCount -= 1; return new SimpleConfigList(arrayOrigin, values); } AbstractConfigValue parse() { AbstractConfigValue result = null; ArrayList comments = new ArrayList(); boolean lastWasNewLine = false; for (AbstractConfigNode node : document.children()) { if (node instanceof ConfigNodeComment) { comments.add(((ConfigNodeComment) node).commentText()); lastWasNewLine = false; } else if (node instanceof ConfigNodeSingleToken) { Token t = ((ConfigNodeSingleToken) node).token(); if (Tokens.isNewline(t)) { lineNumber++; if (lastWasNewLine && result == null) { comments.clear(); } else if (result != null) { result = result.withOrigin(result.origin().appendComments(new ArrayList(comments))); comments.clear(); break; } lastWasNewLine = true; } } else if (node instanceof ConfigNodeComplexValue) { result = parseValue((ConfigNodeComplexValue)node, comments); lastWasNewLine = false; } } return result; } } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigReference.java000066400000000000000000000132501277147274600273770ustar00rootroot00000000000000package com.typesafe.config.impl; import java.util.Collection; import java.util.Collections; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValueType; /** * ConfigReference replaces ConfigReference (the older class kept for back * compat) and represents the ${} substitution syntax. It can resolve to any * kind of value. */ final class ConfigReference extends AbstractConfigValue implements Unmergeable { final private SubstitutionExpression expr; // the length of any prefixes added with relativized() final private int prefixLength; ConfigReference(ConfigOrigin origin, SubstitutionExpression expr) { this(origin, expr, 0); } private ConfigReference(ConfigOrigin origin, SubstitutionExpression expr, int prefixLength) { super(origin); this.expr = expr; this.prefixLength = prefixLength; } private ConfigException.NotResolved notResolved() { return new ConfigException.NotResolved( "need to Config#resolve(), see the API docs for Config#resolve(); substitution not resolved: " + this); } @Override public ConfigValueType valueType() { throw notResolved(); } @Override public Object unwrapped() { throw notResolved(); } @Override protected ConfigReference newCopy(ConfigOrigin newOrigin) { return new ConfigReference(newOrigin, expr, prefixLength); } @Override protected boolean ignoresFallbacks() { return false; } @Override public Collection unmergedValues() { return Collections.singleton(this); } // ConfigReference should be a firewall against NotPossibleToResolve going // further up the stack; it should convert everything to ConfigException. // This way it's impossible for NotPossibleToResolve to "escape" since // any failure to resolve has to start with a ConfigReference. @Override ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) { ResolveContext newContext = context.addCycleMarker(this); AbstractConfigValue v; try { ResolveSource.ResultWithPath resultWithPath = source.lookupSubst(newContext, expr, prefixLength); newContext = resultWithPath.result.context; if (resultWithPath.result.value != null) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(newContext.depth(), "recursively resolving " + resultWithPath + " which was the resolution of " + expr + " against " + source); ResolveSource recursiveResolveSource = (new ResolveSource( (AbstractConfigObject) resultWithPath.pathFromRoot.last(), resultWithPath.pathFromRoot)); if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(newContext.depth(), "will recursively resolve against " + recursiveResolveSource); ResolveResult result = newContext.resolve(resultWithPath.result.value, recursiveResolveSource); v = result.value; newContext = result.context; } else { v = null; } } catch (NotPossibleToResolve e) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(newContext.depth(), "not possible to resolve " + expr + ", cycle involved: " + e.traceString()); if (expr.optional()) v = null; else throw new ConfigException.UnresolvedSubstitution(origin(), expr + " was part of a cycle of substitutions involving " + e.traceString(), e); } if (v == null && !expr.optional()) { if (newContext.options().getAllowUnresolved()) return ResolveResult.make(newContext.removeCycleMarker(this), this); else throw new ConfigException.UnresolvedSubstitution(origin(), expr.toString()); } else { return ResolveResult.make(newContext.removeCycleMarker(this), v); } } @Override ResolveStatus resolveStatus() { return ResolveStatus.UNRESOLVED; } // when you graft a substitution into another object, // you have to prefix it with the location in that object // where you grafted it; but save prefixLength so // system property and env variable lookups don't get // broken. @Override ConfigReference relativized(Path prefix) { SubstitutionExpression newExpr = expr.changePath(expr.path().prepend(prefix)); return new ConfigReference(origin(), newExpr, prefixLength + prefix.length()); } @Override protected boolean canEqual(Object other) { return other instanceof ConfigReference; } @Override public boolean equals(Object other) { // note that "origin" is deliberately NOT part of equality if (other instanceof ConfigReference) { return canEqual(other) && this.expr.equals(((ConfigReference) other).expr); } else { return false; } } @Override public int hashCode() { // note that "origin" is deliberately NOT part of equality return expr.hashCode(); } @Override protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { sb.append(expr.toString()); } SubstitutionExpression expression() { return expr; } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ConfigString.java000066400000000000000000000053751277147274600267600ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.ObjectStreamException; import java.io.Serializable; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValueType; abstract class ConfigString extends AbstractConfigValue implements Serializable { private static final long serialVersionUID = 2L; final protected String value; protected ConfigString(ConfigOrigin origin, String value) { super(origin); this.value = value; } final static class Quoted extends ConfigString { Quoted(ConfigOrigin origin, String value) { super(origin, value); } @Override protected Quoted newCopy(ConfigOrigin origin) { return new Quoted(origin, value); } // serialization all goes through SerializedConfigValue private Object writeReplace() throws ObjectStreamException { return new SerializedConfigValue(this); } } // this is sort of a hack; we want to preserve whether whitespace // was quoted until we process substitutions, so we can ignore // unquoted whitespace when concatenating lists or objects. // We dump this distinction when serializing and deserializing, // but that's OK because it isn't in equals/hashCode, and we // don't allow serializing unresolved objects which is where // quoted-ness matters. If we later make ConfigOrigin point // to the original token range, we could use that to implement // wasQuoted() final static class Unquoted extends ConfigString { Unquoted(ConfigOrigin origin, String value) { super(origin, value); } @Override protected Unquoted newCopy(ConfigOrigin origin) { return new Unquoted(origin, value); } // serialization all goes through SerializedConfigValue private Object writeReplace() throws ObjectStreamException { return new SerializedConfigValue(this); } } boolean wasQuoted() { return (this instanceof Quoted); } @Override public ConfigValueType valueType() { return ConfigValueType.STRING; } @Override public String unwrapped() { return value; } @Override String transformToString() { return value; } @Override protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { String rendered; if (options.getJson()) rendered = ConfigImplUtil.renderJsonString(value); else rendered = ConfigImplUtil.renderStringUnquotedIfPossible(value); sb.append(rendered); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/Container.java000066400000000000000000000016031277147274600262740ustar00rootroot00000000000000/** * Copyright (C) 2014 Typesafe Inc. */ package com.typesafe.config.impl; /** * An AbstractConfigValue which contains other values. Java has no way to * express "this has to be an AbstractConfigValue also" other than making * AbstractConfigValue an interface which would be aggravating. But we can say * we are a ConfigValue. */ interface Container extends com.typesafe.config.ConfigValue { /** * Replace a child of this value. CAUTION if replacement is null, delete the * child, which may also delete the parent, or make the parent into a * non-container. */ AbstractConfigValue replaceChild(AbstractConfigValue child, AbstractConfigValue replacement); /** * Super-expensive full traversal to see if descendant is anywhere * underneath this container. */ boolean hasDescendant(AbstractConfigValue descendant); } config-1.3.1/config/src/main/java/com/typesafe/config/impl/DefaultTransformer.java000066400000000000000000000121461277147274600301650ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import com.typesafe.config.ConfigValueType; /** * Default automatic type transformations. */ final class DefaultTransformer { static AbstractConfigValue transform(AbstractConfigValue value, ConfigValueType requested) { if (value.valueType() == ConfigValueType.STRING) { String s = (String) value.unwrapped(); switch (requested) { case NUMBER: try { Long v = Long.parseLong(s); return new ConfigLong(value.origin(), v, s); } catch (NumberFormatException e) { // try Double } try { Double v = Double.parseDouble(s); return new ConfigDouble(value.origin(), v, s); } catch (NumberFormatException e) { // oh well. } break; case NULL: if (s.equals("null")) return new ConfigNull(value.origin()); break; case BOOLEAN: if (s.equals("true") || s.equals("yes") || s.equals("on")) { return new ConfigBoolean(value.origin(), true); } else if (s.equals("false") || s.equals("no") || s.equals("off")) { return new ConfigBoolean(value.origin(), false); } break; case LIST: // can't go STRING to LIST automatically break; case OBJECT: // can't go STRING to OBJECT automatically break; case STRING: // no-op STRING to STRING break; } } else if (requested == ConfigValueType.STRING) { // if we converted null to string here, then you wouldn't properly // get a missing-value error if you tried to get a null value // as a string. switch (value.valueType()) { case NUMBER: // FALL THROUGH case BOOLEAN: return new ConfigString.Quoted(value.origin(), value.transformToString()); case NULL: // want to be sure this throws instead of returning "null" as a // string break; case OBJECT: // no OBJECT to STRING automatically break; case LIST: // no LIST to STRING automatically break; case STRING: // no-op STRING to STRING break; } } else if (requested == ConfigValueType.LIST && value.valueType() == ConfigValueType.OBJECT) { // attempt to convert an array-like (numeric indices) object to a // list. This would be used with .properties syntax for example: // -Dfoo.0=bar -Dfoo.1=baz // To ensure we still throw type errors for objects treated // as lists in most cases, we'll refuse to convert if the object // does not contain any numeric keys. This means we don't allow // empty objects here though :-/ AbstractConfigObject o = (AbstractConfigObject) value; Map values = new HashMap(); for (String key : o.keySet()) { int i; try { i = Integer.parseInt(key, 10); if (i < 0) continue; values.put(i, o.get(key)); } catch (NumberFormatException e) { continue; } } if (!values.isEmpty()) { ArrayList> entryList = new ArrayList>( values.entrySet()); // sort by numeric index Collections.sort(entryList, new Comparator>() { @Override public int compare(Map.Entry a, Map.Entry b) { return Integer.compare(a.getKey(), b.getKey()); } }); // drop the indices (we allow gaps in the indices, for better or // worse) ArrayList list = new ArrayList(); for (Map.Entry entry : entryList) { list.add(entry.getValue()); } return new SimpleConfigList(value.origin(), list); } } return value; } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/FromMapMode.java000066400000000000000000000002431277147274600265170ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; enum FromMapMode { KEYS_ARE_PATHS, KEYS_ARE_KEYS } config-1.3.1/config/src/main/java/com/typesafe/config/impl/FullIncluder.java000066400000000000000000000006501277147274600267430ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import com.typesafe.config.ConfigIncluder; import com.typesafe.config.ConfigIncluderClasspath; import com.typesafe.config.ConfigIncluderFile; import com.typesafe.config.ConfigIncluderURL; interface FullIncluder extends ConfigIncluder, ConfigIncluderFile, ConfigIncluderURL, ConfigIncluderClasspath { } config-1.3.1/config/src/main/java/com/typesafe/config/impl/MemoKey.java000066400000000000000000000026271277147274600257270ustar00rootroot00000000000000package com.typesafe.config.impl; /** The key used to memoize already-traversed nodes when resolving substitutions */ final class MemoKey { MemoKey(AbstractConfigValue value, Path restrictToChildOrNull) { this.value = value; this.restrictToChildOrNull = restrictToChildOrNull; } final private AbstractConfigValue value; final private Path restrictToChildOrNull; @Override public final int hashCode() { int h = System.identityHashCode(value); if (restrictToChildOrNull != null) { return h + 41 * (41 + restrictToChildOrNull.hashCode()); } else { return h; } } @Override public final boolean equals(Object other) { if (other instanceof MemoKey) { MemoKey o = (MemoKey) other; if (o.value != this.value) return false; else if (o.restrictToChildOrNull == this.restrictToChildOrNull) return true; else if (o.restrictToChildOrNull == null || this.restrictToChildOrNull == null) return false; else return o.restrictToChildOrNull.equals(this.restrictToChildOrNull); } else { return false; } } @Override public final String toString() { return "MemoKey(" + value + "@" + System.identityHashCode(value) + "," + restrictToChildOrNull + ")"; } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/MergeableValue.java000066400000000000000000000004301277147274600272270ustar00rootroot00000000000000package com.typesafe.config.impl; import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigValue; interface MergeableValue extends ConfigMergeable { // converts a Config to its root object and a ConfigValue to itself ConfigValue toFallbackValue(); } config-1.3.1/config/src/main/java/com/typesafe/config/impl/OriginType.java000066400000000000000000000002171277147274600264430ustar00rootroot00000000000000package com.typesafe.config.impl; // caution: ordinals used in serialization enum OriginType { GENERIC, FILE, URL, RESOURCE } config-1.3.1/config/src/main/java/com/typesafe/config/impl/Parseable.java000066400000000000000000000764211277147274600262620ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FilterReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.util.*; import com.typesafe.config.*; import com.typesafe.config.parser.*; /** * Internal implementation detail, not ABI stable, do not touch. * For use only by the {@link com.typesafe.config} package. * The point of this class is to avoid "propagating" each * overload on "thing which can be parsed" through multiple * interfaces. Most interfaces can have just one overload that * takes a Parseable. Also it's used as an abstract "resource * handle" in the ConfigIncluder interface. */ public abstract class Parseable implements ConfigParseable { private ConfigIncludeContext includeContext; private ConfigParseOptions initialOptions; private ConfigOrigin initialOrigin; /** * Internal implementation detail, not ABI stable, do not touch. */ protected interface Relativizer { ConfigParseable relativeTo(String filename); } private static final ThreadLocal> parseStack = new ThreadLocal>() { @Override protected LinkedList initialValue() { return new LinkedList(); } }; private static final int MAX_INCLUDE_DEPTH = 50; protected Parseable() { } private ConfigParseOptions fixupOptions(ConfigParseOptions baseOptions) { ConfigSyntax syntax = baseOptions.getSyntax(); if (syntax == null) { syntax = guessSyntax(); } if (syntax == null) { syntax = ConfigSyntax.CONF; } ConfigParseOptions modified = baseOptions.setSyntax(syntax); // make sure the app-provided includer falls back to default modified = modified.appendIncluder(ConfigImpl.defaultIncluder()); // make sure the app-provided includer is complete modified = modified.setIncluder(SimpleIncluder.makeFull(modified.getIncluder())); return modified; } protected void postConstruct(ConfigParseOptions baseOptions) { this.initialOptions = fixupOptions(baseOptions); this.includeContext = new SimpleIncludeContext(this); if (initialOptions.getOriginDescription() != null) initialOrigin = SimpleConfigOrigin.newSimple(initialOptions.getOriginDescription()); else initialOrigin = createOrigin(); } // the general idea is that any work should be in here, not in the // constructor, so that exceptions are thrown from the public parse() // function and not from the creation of the Parseable. // Essentially this is a lazy field. The parser should close the // reader when it's done with it. // ALSO, IMPORTANT: if the file or URL is not found, this must throw. // to support the "allow missing" feature. protected abstract Reader reader() throws IOException; protected Reader reader(ConfigParseOptions options) throws IOException { return reader(); } protected static void trace(String message) { if (ConfigImpl.traceLoadsEnabled()) { ConfigImpl.trace(message); } } ConfigSyntax guessSyntax() { return null; } ConfigSyntax contentType() { return null; } ConfigParseable relativeTo(String filename) { // fall back to classpath; we treat the "filename" as absolute // (don't add a package name in front), // if it starts with "/" then remove the "/", for consistency // with ParseableResources.relativeTo String resource = filename; if (filename.startsWith("/")) resource = filename.substring(1); return newResources(resource, options().setOriginDescription(null)); } ConfigIncludeContext includeContext() { return includeContext; } static AbstractConfigObject forceParsedToObject(ConfigValue value) { if (value instanceof AbstractConfigObject) { return (AbstractConfigObject) value; } else { throw new ConfigException.WrongType(value.origin(), "", "object at file root", value .valueType().name()); } } @Override public ConfigObject parse(ConfigParseOptions baseOptions) { LinkedList stack = parseStack.get(); if (stack.size() >= MAX_INCLUDE_DEPTH) { throw new ConfigException.Parse(initialOrigin, "include statements nested more than " + MAX_INCLUDE_DEPTH + " times, you probably have a cycle in your includes. Trace: " + stack); } stack.addFirst(this); try { return forceParsedToObject(parseValue(baseOptions)); } finally { stack.removeFirst(); if (stack.isEmpty()) { parseStack.remove(); } } } final AbstractConfigValue parseValue(ConfigParseOptions baseOptions) { // note that we are NOT using our "initialOptions", // but using the ones from the passed-in options. The idea is that // callers can get our original options and then parse with different // ones if they want. ConfigParseOptions options = fixupOptions(baseOptions); // passed-in options can override origin ConfigOrigin origin; if (options.getOriginDescription() != null) origin = SimpleConfigOrigin.newSimple(options.getOriginDescription()); else origin = initialOrigin; return parseValue(origin, options); } final private AbstractConfigValue parseValue(ConfigOrigin origin, ConfigParseOptions finalOptions) { try { return rawParseValue(origin, finalOptions); } catch (IOException e) { if (finalOptions.getAllowMissing()) { return SimpleConfigObject.emptyMissing(origin); } else { trace("exception loading " + origin.description() + ": " + e.getClass().getName() + ": " + e.getMessage()); throw new ConfigException.IO(origin, e.getClass().getName() + ": " + e.getMessage(), e); } } } final ConfigDocument parseDocument(ConfigParseOptions baseOptions) { // note that we are NOT using our "initialOptions", // but using the ones from the passed-in options. The idea is that // callers can get our original options and then parse with different // ones if they want. ConfigParseOptions options = fixupOptions(baseOptions); // passed-in options can override origin ConfigOrigin origin; if (options.getOriginDescription() != null) origin = SimpleConfigOrigin.newSimple(options.getOriginDescription()); else origin = initialOrigin; return parseDocument(origin, options); } final private ConfigDocument parseDocument(ConfigOrigin origin, ConfigParseOptions finalOptions) { try { return rawParseDocument(origin, finalOptions); } catch (IOException e) { if (finalOptions.getAllowMissing()) { ArrayList children = new ArrayList(); children.add(new ConfigNodeObject(new ArrayList())); return new SimpleConfigDocument(new ConfigNodeRoot(children, origin), finalOptions); } else { trace("exception loading " + origin.description() + ": " + e.getClass().getName() + ": " + e.getMessage()); throw new ConfigException.IO(origin, e.getClass().getName() + ": " + e.getMessage(), e); } } } // this is parseValue without post-processing the IOException or handling // options.getAllowMissing() protected AbstractConfigValue rawParseValue(ConfigOrigin origin, ConfigParseOptions finalOptions) throws IOException { Reader reader = reader(finalOptions); // after reader() we will have loaded the Content-Type. ConfigSyntax contentType = contentType(); ConfigParseOptions optionsWithContentType; if (contentType != null) { if (ConfigImpl.traceLoadsEnabled() && finalOptions.getSyntax() != null) trace("Overriding syntax " + finalOptions.getSyntax() + " with Content-Type which specified " + contentType); optionsWithContentType = finalOptions.setSyntax(contentType); } else { optionsWithContentType = finalOptions; } try { return rawParseValue(reader, origin, optionsWithContentType); } finally { reader.close(); } } private AbstractConfigValue rawParseValue(Reader reader, ConfigOrigin origin, ConfigParseOptions finalOptions) throws IOException { if (finalOptions.getSyntax() == ConfigSyntax.PROPERTIES) { return PropertiesParser.parse(reader, origin); } else { Iterator tokens = Tokenizer.tokenize(origin, reader, finalOptions.getSyntax()); ConfigNodeRoot document = ConfigDocumentParser.parse(tokens, origin, finalOptions); return ConfigParser.parse(document, origin, finalOptions, includeContext()); } } // this is parseDocument without post-processing the IOException or handling // options.getAllowMissing() protected ConfigDocument rawParseDocument(ConfigOrigin origin, ConfigParseOptions finalOptions) throws IOException { Reader reader = reader(finalOptions); // after reader() we will have loaded the Content-Type. ConfigSyntax contentType = contentType(); ConfigParseOptions optionsWithContentType; if (contentType != null) { if (ConfigImpl.traceLoadsEnabled() && finalOptions.getSyntax() != null) trace("Overriding syntax " + finalOptions.getSyntax() + " with Content-Type which specified " + contentType); optionsWithContentType = finalOptions.setSyntax(contentType); } else { optionsWithContentType = finalOptions; } try { return rawParseDocument(reader, origin, optionsWithContentType); } finally { reader.close(); } } private ConfigDocument rawParseDocument(Reader reader, ConfigOrigin origin, ConfigParseOptions finalOptions) throws IOException { Iterator tokens = Tokenizer.tokenize(origin, reader, finalOptions.getSyntax()); return new SimpleConfigDocument(ConfigDocumentParser.parse(tokens, origin, finalOptions), finalOptions); } public ConfigObject parse() { return forceParsedToObject(parseValue(options())); } public ConfigDocument parseConfigDocument() { return parseDocument(options()); } AbstractConfigValue parseValue() { return parseValue(options()); } @Override public final ConfigOrigin origin() { return initialOrigin; } protected abstract ConfigOrigin createOrigin(); @Override public ConfigParseOptions options() { return initialOptions; } @Override public String toString() { return getClass().getSimpleName(); } private static ConfigSyntax syntaxFromExtension(String name) { if (name.endsWith(".json")) return ConfigSyntax.JSON; else if (name.endsWith(".conf")) return ConfigSyntax.CONF; else if (name.endsWith(".properties")) return ConfigSyntax.PROPERTIES; else return null; } private static Reader readerFromStream(InputStream input) { return readerFromStream(input, "UTF-8"); } private static Reader readerFromStream(InputStream input, String encoding) { try { // well, this is messed up. If we aren't going to close // the passed-in InputStream then we have no way to // close these readers. So maybe we should not have an // InputStream version, only a Reader version. Reader reader = new InputStreamReader(input, encoding); return new BufferedReader(reader); } catch (UnsupportedEncodingException e) { throw new ConfigException.BugOrBroken("Java runtime does not support UTF-8", e); } } private static Reader doNotClose(Reader input) { return new FilterReader(input) { @Override public void close() { // NOTHING. } }; } static URL relativeTo(URL url, String filename) { // I'm guessing this completely fails on Windows, help wanted if (new File(filename).isAbsolute()) return null; try { URI siblingURI = url.toURI(); URI relative = new URI(filename); // this seems wrong, but it's documented that the last // element of the path in siblingURI gets stripped out, // so to get something in the same directory as // siblingURI we just call resolve(). URL resolved = siblingURI.resolve(relative).toURL(); return resolved; } catch (MalformedURLException e) { return null; } catch (URISyntaxException e) { return null; } catch (IllegalArgumentException e) { return null; } } static File relativeTo(File file, String filename) { File child = new File(filename); if (child.isAbsolute()) return null; File parent = file.getParentFile(); if (parent == null) return null; else return new File(parent, filename); } // this is a parseable that doesn't exist and just throws when you try to // parse it private final static class ParseableNotFound extends Parseable { final private String what; final private String message; ParseableNotFound(String what, String message, ConfigParseOptions options) { this.what = what; this.message = message; postConstruct(options); } @Override protected Reader reader() throws IOException { throw new FileNotFoundException(message); } @Override protected ConfigOrigin createOrigin() { return SimpleConfigOrigin.newSimple(what); } } public static Parseable newNotFound(String whatNotFound, String message, ConfigParseOptions options) { return new ParseableNotFound(whatNotFound, message, options); } private final static class ParseableReader extends Parseable { final private Reader reader; ParseableReader(Reader reader, ConfigParseOptions options) { this.reader = reader; postConstruct(options); } @Override protected Reader reader() { if (ConfigImpl.traceLoadsEnabled()) trace("Loading config from reader " + reader); return reader; } @Override protected ConfigOrigin createOrigin() { return SimpleConfigOrigin.newSimple("Reader"); } } // note that we will never close this reader; you have to do it when parsing // is complete. public static Parseable newReader(Reader reader, ConfigParseOptions options) { return new ParseableReader(doNotClose(reader), options); } private final static class ParseableString extends Parseable { final private String input; ParseableString(String input, ConfigParseOptions options) { this.input = input; postConstruct(options); } @Override protected Reader reader() { if (ConfigImpl.traceLoadsEnabled()) trace("Loading config from a String " + input); return new StringReader(input); } @Override protected ConfigOrigin createOrigin() { return SimpleConfigOrigin.newSimple("String"); } @Override public String toString() { return getClass().getSimpleName() + "(" + input + ")"; } } public static Parseable newString(String input, ConfigParseOptions options) { return new ParseableString(input, options); } private static final String jsonContentType = "application/json"; private static final String propertiesContentType = "text/x-java-properties"; private static final String hoconContentType = "application/hocon"; private static class ParseableURL extends Parseable { final protected URL input; private String contentType = null; protected ParseableURL(URL input) { this.input = input; // does not postConstruct (subclass does it) } ParseableURL(URL input, ConfigParseOptions options) { this(input); postConstruct(options); } @Override protected Reader reader() throws IOException { throw new ConfigException.BugOrBroken("reader() without options should not be called on ParseableURL"); } private static String acceptContentType(ConfigParseOptions options) { if (options.getSyntax() == null) return null; switch (options.getSyntax()) { case JSON: return jsonContentType; case CONF: return hoconContentType; case PROPERTIES: return propertiesContentType; } // not sure this is reachable but javac thinks it is return null; } @Override protected Reader reader(ConfigParseOptions options) throws IOException { try { if (ConfigImpl.traceLoadsEnabled()) trace("Loading config from a URL: " + input.toExternalForm()); URLConnection connection = input.openConnection(); // allow server to serve multiple types from one URL String acceptContent = acceptContentType(options); if (acceptContent != null) { connection.setRequestProperty("Accept", acceptContent); } connection.connect(); // save content type for later contentType = connection.getContentType(); if (contentType != null) { if (ConfigImpl.traceLoadsEnabled()) trace("URL sets Content-Type: '" + contentType + "'"); contentType = contentType.trim(); int semi = contentType.indexOf(';'); if (semi >= 0) contentType = contentType.substring(0, semi); } InputStream stream = connection.getInputStream(); return readerFromStream(stream); } catch (FileNotFoundException fnf) { // If the resource is not found (HTTP response // code 404 or something alike), then it's fine to // treat it according to the allowMissing setting // and "include" spec. But if we have something // like HTTP 503 it seems to be better to fail // early, because this may be a sign of broken // environment. Java throws FileNotFoundException // if it sees 404 or 410. throw fnf; } catch (IOException e) { throw new ConfigException.BugOrBroken("Cannot load config from URL: " + input.toExternalForm(), e); } } @Override ConfigSyntax guessSyntax() { return syntaxFromExtension(input.getPath()); } @Override ConfigSyntax contentType() { if (contentType != null) { if (contentType.equals(jsonContentType)) return ConfigSyntax.JSON; else if (contentType.equals(propertiesContentType)) return ConfigSyntax.PROPERTIES; else if (contentType.equals(hoconContentType)) return ConfigSyntax.CONF; else { if (ConfigImpl.traceLoadsEnabled()) trace("'" + contentType + "' isn't a known content type"); return null; } } else { return null; } } @Override ConfigParseable relativeTo(String filename) { URL url = relativeTo(input, filename); if (url == null) return null; return newURL(url, options().setOriginDescription(null)); } @Override protected ConfigOrigin createOrigin() { return SimpleConfigOrigin.newURL(input); } @Override public String toString() { return getClass().getSimpleName() + "(" + input.toExternalForm() + ")"; } } public static Parseable newURL(URL input, ConfigParseOptions options) { // we want file: URLs and files to always behave the same, so switch // to a file if it's a file: URL if (input.getProtocol().equals("file")) { return newFile(ConfigImplUtil.urlToFile(input), options); } else { return new ParseableURL(input, options); } } private final static class ParseableFile extends Parseable { final private File input; ParseableFile(File input, ConfigParseOptions options) { this.input = input; postConstruct(options); } @Override protected Reader reader() throws IOException { if (ConfigImpl.traceLoadsEnabled()) trace("Loading config from a file: " + input); InputStream stream = new FileInputStream(input); return readerFromStream(stream); } @Override ConfigSyntax guessSyntax() { return syntaxFromExtension(input.getName()); } @Override ConfigParseable relativeTo(String filename) { File sibling; if ((new File(filename)).isAbsolute()) { sibling = new File(filename); } else { // this may return null sibling = relativeTo(input, filename); } if (sibling == null) return null; if (sibling.exists()) { trace(sibling + " exists, so loading it as a file"); return newFile(sibling, options().setOriginDescription(null)); } else { trace(sibling + " does not exist, so trying it as a classpath resource"); return super.relativeTo(filename); } } @Override protected ConfigOrigin createOrigin() { return SimpleConfigOrigin.newFile(input.getPath()); } @Override public String toString() { return getClass().getSimpleName() + "(" + input.getPath() + ")"; } } public static Parseable newFile(File input, ConfigParseOptions options) { return new ParseableFile(input, options); } private final static class ParseableResourceURL extends ParseableURL { private final Relativizer relativizer; private final String resource; ParseableResourceURL(URL input, ConfigParseOptions options, String resource, Relativizer relativizer) { super(input); this.relativizer = relativizer; this.resource = resource; postConstruct(options); } @Override protected ConfigOrigin createOrigin() { return SimpleConfigOrigin.newResource(resource, input); } @Override ConfigParseable relativeTo(String filename) { return relativizer.relativeTo(filename); } } private static Parseable newResourceURL(URL input, ConfigParseOptions options, String resource, Relativizer relativizer) { return new ParseableResourceURL(input, options, resource, relativizer); } private final static class ParseableResources extends Parseable implements Relativizer { final private String resource; ParseableResources(String resource, ConfigParseOptions options) { this.resource = resource; postConstruct(options); } @Override protected Reader reader() throws IOException { throw new ConfigException.BugOrBroken("reader() should not be called on resources"); } @Override protected AbstractConfigObject rawParseValue(ConfigOrigin origin, ConfigParseOptions finalOptions) throws IOException { ClassLoader loader = finalOptions.getClassLoader(); if (loader == null) throw new ConfigException.BugOrBroken( "null class loader; pass in a class loader or use Thread.currentThread().setContextClassLoader()"); Enumeration e = loader.getResources(resource); if (!e.hasMoreElements()) { if (ConfigImpl.traceLoadsEnabled()) trace("Loading config from class loader " + loader + " but there were no resources called " + resource); throw new IOException("resource not found on classpath: " + resource); } AbstractConfigObject merged = SimpleConfigObject.empty(origin); while (e.hasMoreElements()) { URL url = e.nextElement(); if (ConfigImpl.traceLoadsEnabled()) trace("Loading config from resource '" + resource + "' URL " + url.toExternalForm() + " from class loader " + loader); Parseable element = newResourceURL(url, finalOptions, resource, this); AbstractConfigValue v = element.parseValue(); merged = merged.withFallback(v); } return merged; } @Override ConfigSyntax guessSyntax() { return syntaxFromExtension(resource); } static String parent(String resource) { // the "resource" is not supposed to begin with a "/" // because it's supposed to be the raw resource // (ClassLoader#getResource), not the // resource "syntax" (Class#getResource) int i = resource.lastIndexOf('/'); if (i < 0) { return null; } else { return resource.substring(0, i); } } @Override public ConfigParseable relativeTo(String sibling) { if (sibling.startsWith("/")) { // if it starts with "/" then don't make it relative to // the including resource return newResources(sibling.substring(1), options().setOriginDescription(null)); } else { // here we want to build a new resource name and let // the class loader have it, rather than getting the // url with getResource() and relativizing to that url. // This is needed in case the class loader is going to // search a classpath. String parent = parent(resource); if (parent == null) return newResources(sibling, options().setOriginDescription(null)); else return newResources(parent + "/" + sibling, options() .setOriginDescription(null)); } } @Override protected ConfigOrigin createOrigin() { return SimpleConfigOrigin.newResource(resource); } @Override public String toString() { return getClass().getSimpleName() + "(" + resource + ")"; } } public static Parseable newResources(Class klass, String resource, ConfigParseOptions options) { return newResources(convertResourceName(klass, resource), options.setClassLoader(klass.getClassLoader())); } // this function is supposed to emulate the difference // between Class.getResource and ClassLoader.getResource // (unfortunately there doesn't seem to be public API for it). // We're using it because the Class API is more limited, // for example it lacks getResources(). So we want to be able to // use ClassLoader directly. private static String convertResourceName(Class klass, String resource) { if (resource.startsWith("/")) { // "absolute" resource, chop the slash return resource.substring(1); } else { String className = klass.getName(); int i = className.lastIndexOf('.'); if (i < 0) { // no package return resource; } else { // need to be relative to the package String packageName = className.substring(0, i); String packagePath = packageName.replace('.', '/'); return packagePath + "/" + resource; } } } public static Parseable newResources(String resource, ConfigParseOptions options) { if (options.getClassLoader() == null) throw new ConfigException.BugOrBroken( "null class loader; pass in a class loader or use Thread.currentThread().setContextClassLoader()"); return new ParseableResources(resource, options); } private final static class ParseableProperties extends Parseable { final private Properties props; ParseableProperties(Properties props, ConfigParseOptions options) { this.props = props; postConstruct(options); } @Override protected Reader reader() throws IOException { throw new ConfigException.BugOrBroken("reader() should not be called on props"); } @Override protected AbstractConfigObject rawParseValue(ConfigOrigin origin, ConfigParseOptions finalOptions) { if (ConfigImpl.traceLoadsEnabled()) trace("Loading config from properties " + props); return PropertiesParser.fromProperties(origin, props); } @Override ConfigSyntax guessSyntax() { return ConfigSyntax.PROPERTIES; } @Override protected ConfigOrigin createOrigin() { return SimpleConfigOrigin.newSimple("properties"); } @Override public String toString() { return getClass().getSimpleName() + "(" + props.size() + " props)"; } } public static Parseable newProperties(Properties properties, ConfigParseOptions options) { return new ParseableProperties(properties, options); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/Path.java000066400000000000000000000136751277147274600252620ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.util.*; import com.typesafe.config.ConfigException; final class Path { final private String first; final private Path remainder; Path(String first, Path remainder) { this.first = first; this.remainder = remainder; } Path(String... elements) { if (elements.length == 0) throw new ConfigException.BugOrBroken("empty path"); this.first = elements[0]; if (elements.length > 1) { PathBuilder pb = new PathBuilder(); for (int i = 1; i < elements.length; ++i) { pb.appendKey(elements[i]); } this.remainder = pb.result(); } else { this.remainder = null; } } // append all the paths in the list together into one path Path(List pathsToConcat) { this(pathsToConcat.iterator()); } // append all the paths in the iterator together into one path Path(Iterator i) { if (!i.hasNext()) throw new ConfigException.BugOrBroken("empty path"); Path firstPath = i.next(); this.first = firstPath.first; PathBuilder pb = new PathBuilder(); if (firstPath.remainder != null) { pb.appendPath(firstPath.remainder); } while (i.hasNext()) { pb.appendPath(i.next()); } this.remainder = pb.result(); } String first() { return first; } /** * * @return path minus the first element or null if no more elements */ Path remainder() { return remainder; } /** * * @return path minus the last element or null if we have just one element */ Path parent() { if (remainder == null) return null; PathBuilder pb = new PathBuilder(); Path p = this; while (p.remainder != null) { pb.appendKey(p.first); p = p.remainder; } return pb.result(); } /** * * @return last element in the path */ String last() { Path p = this; while (p.remainder != null) { p = p.remainder; } return p.first; } Path prepend(Path toPrepend) { PathBuilder pb = new PathBuilder(); pb.appendPath(toPrepend); pb.appendPath(this); return pb.result(); } int length() { int count = 1; Path p = remainder; while (p != null) { count += 1; p = p.remainder; } return count; } Path subPath(int removeFromFront) { int count = removeFromFront; Path p = this; while (p != null && count > 0) { count -= 1; p = p.remainder; } return p; } Path subPath(int firstIndex, int lastIndex) { if (lastIndex < firstIndex) throw new ConfigException.BugOrBroken("bad call to subPath"); Path from = subPath(firstIndex); PathBuilder pb = new PathBuilder(); int count = lastIndex - firstIndex; while (count > 0) { count -= 1; pb.appendKey(from.first()); from = from.remainder(); if (from == null) throw new ConfigException.BugOrBroken("subPath lastIndex out of range " + lastIndex); } return pb.result(); } boolean startsWith(Path other) { Path myRemainder = this; Path otherRemainder = other; if (otherRemainder.length() <= myRemainder.length()) { while(otherRemainder != null) { if (!otherRemainder.first().equals(myRemainder.first())) return false; myRemainder = myRemainder.remainder(); otherRemainder = otherRemainder.remainder(); } return true; } return false; } @Override public boolean equals(Object other) { if (other instanceof Path) { Path that = (Path) other; return this.first.equals(that.first) && ConfigImplUtil.equalsHandlingNull(this.remainder, that.remainder); } else { return false; } } @Override public int hashCode() { return 41 * (41 + first.hashCode()) + (remainder == null ? 0 : remainder.hashCode()); } // this doesn't have a very precise meaning, just to reduce // noise from quotes in the rendered path for average cases static boolean hasFunkyChars(String s) { int length = s.length(); if (length == 0) return false; for (int i = 0; i < length; ++i) { char c = s.charAt(i); if (Character.isLetterOrDigit(c) || c == '-' || c == '_') continue; else return true; } return false; } private void appendToStringBuilder(StringBuilder sb) { if (hasFunkyChars(first) || first.isEmpty()) sb.append(ConfigImplUtil.renderJsonString(first)); else sb.append(first); if (remainder != null) { sb.append("."); remainder.appendToStringBuilder(sb); } } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Path("); appendToStringBuilder(sb); sb.append(")"); return sb.toString(); } /** * toString() is a debugging-oriented version while this is an * error-message-oriented human-readable one. */ String render() { StringBuilder sb = new StringBuilder(); appendToStringBuilder(sb); return sb.toString(); } static Path newKey(String key) { return new Path(key, null); } static Path newPath(String path) { return PathParser.parsePath(path); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/PathBuilder.java000066400000000000000000000027071277147274600265630ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.util.Stack; import com.typesafe.config.ConfigException; final class PathBuilder { // the keys are kept "backward" (top of stack is end of path) final private Stack keys; private Path result; PathBuilder() { keys = new Stack(); } private void checkCanAppend() { if (result != null) throw new ConfigException.BugOrBroken( "Adding to PathBuilder after getting result"); } void appendKey(String key) { checkCanAppend(); keys.push(key); } void appendPath(Path path) { checkCanAppend(); String first = path.first(); Path remainder = path.remainder(); while (true) { keys.push(first); if (remainder != null) { first = remainder.first(); remainder = remainder.remainder(); } else { break; } } } Path result() { // note: if keys is empty, we want to return null, which is a valid // empty path if (result == null) { Path remainder = null; while (!keys.isEmpty()) { String key = keys.pop(); remainder = new Path(key, remainder); } result = remainder; } return result; } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/PathParser.java000066400000000000000000000257241277147274600264350ustar00rootroot00000000000000/** * Copyright (C) 2015 Typesafe Inc. */ package com.typesafe.config.impl; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigSyntax; import com.typesafe.config.ConfigValueType; import java.io.StringReader; import java.util.*; final class PathParser { static class Element { StringBuilder sb; // an element can be empty if it has a quoted empty string "" in it boolean canBeEmpty; Element(String initial, boolean canBeEmpty) { this.canBeEmpty = canBeEmpty; this.sb = new StringBuilder(initial); } @Override public String toString() { return "Element(" + sb.toString() + "," + canBeEmpty + ")"; } } static ConfigOrigin apiOrigin = SimpleConfigOrigin.newSimple("path parameter"); static ConfigNodePath parsePathNode(String path) { return parsePathNode(path, ConfigSyntax.CONF); } static ConfigNodePath parsePathNode(String path, ConfigSyntax flavor) { StringReader reader = new StringReader(path); try { Iterator tokens = Tokenizer.tokenize(apiOrigin, reader, flavor); tokens.next(); // drop START return parsePathNodeExpression(tokens, apiOrigin, path, flavor); } finally { reader.close(); } } static Path parsePath(String path) { Path speculated = speculativeFastParsePath(path); if (speculated != null) return speculated; StringReader reader = new StringReader(path); try { Iterator tokens = Tokenizer.tokenize(apiOrigin, reader, ConfigSyntax.CONF); tokens.next(); // drop START return parsePathExpression(tokens, apiOrigin, path); } finally { reader.close(); } } protected static Path parsePathExpression(Iterator expression, ConfigOrigin origin) { return parsePathExpression(expression, origin, null, null, ConfigSyntax.CONF); } protected static Path parsePathExpression(Iterator expression, ConfigOrigin origin, String originalText) { return parsePathExpression(expression, origin, originalText, null, ConfigSyntax.CONF); } protected static ConfigNodePath parsePathNodeExpression(Iterator expression, ConfigOrigin origin) { return parsePathNodeExpression(expression, origin, null, ConfigSyntax.CONF); } protected static ConfigNodePath parsePathNodeExpression(Iterator expression, ConfigOrigin origin, String originalText, ConfigSyntax flavor) { ArrayList pathTokens = new ArrayList(); Path path = parsePathExpression(expression, origin, originalText, pathTokens, flavor); return new ConfigNodePath(path, pathTokens); } // originalText may be null if not available protected static Path parsePathExpression(Iterator expression, ConfigOrigin origin, String originalText, ArrayList pathTokens, ConfigSyntax flavor) { // each builder in "buf" is an element in the path. List buf = new ArrayList(); buf.add(new Element("", false)); if (!expression.hasNext()) { throw new ConfigException.BadPath(origin, originalText, "Expecting a field name or path here, but got nothing"); } while (expression.hasNext()) { Token t = expression.next(); if (pathTokens != null) pathTokens.add(t); // Ignore all IgnoredWhitespace tokens if (Tokens.isIgnoredWhitespace(t)) continue; if (Tokens.isValueWithType(t, ConfigValueType.STRING)) { AbstractConfigValue v = Tokens.getValue(t); // this is a quoted string; so any periods // in here don't count as path separators String s = v.transformToString(); addPathText(buf, true, s); } else if (t == Tokens.END) { // ignore this; when parsing a file, it should not happen // since we're parsing a token list rather than the main // token iterator, and when parsing a path expression from the // API, it's expected to have an END. } else { // any periods outside of a quoted string count as // separators String text; if (Tokens.isValue(t)) { // appending a number here may add // a period, but we _do_ count those as path // separators, because we basically want // "foo 3.0bar" to parse as a string even // though there's a number in it. The fact that // we tokenize non-string values is largely an // implementation detail. AbstractConfigValue v = Tokens.getValue(t); // We need to split the tokens on a . so that we can get sub-paths but still preserve // the original path text when doing an insertion if (pathTokens != null) { pathTokens.remove(pathTokens.size() - 1); pathTokens.addAll(splitTokenOnPeriod(t, flavor)); } text = v.transformToString(); } else if (Tokens.isUnquotedText(t)) { // We need to split the tokens on a . so that we can get sub-paths but still preserve // the original path text when doing an insertion on ConfigNodeObjects if (pathTokens != null) { pathTokens.remove(pathTokens.size() - 1); pathTokens.addAll(splitTokenOnPeriod(t, flavor)); } text = Tokens.getUnquotedText(t); } else { throw new ConfigException.BadPath( origin, originalText, "Token not allowed in path expression: " + t + " (you can double-quote this token if you really want it here)"); } addPathText(buf, false, text); } } PathBuilder pb = new PathBuilder(); for (Element e : buf) { if (e.sb.length() == 0 && !e.canBeEmpty) { throw new ConfigException.BadPath( origin, originalText, "path has a leading, trailing, or two adjacent period '.' (use quoted \"\" empty string if you want an empty element)"); } else { pb.appendKey(e.sb.toString()); } } return pb.result(); } private static Collection splitTokenOnPeriod(Token t, ConfigSyntax flavor) { String tokenText = t.tokenText(); if (tokenText.equals(".")) { return Collections.singletonList(t); } String[] splitToken = tokenText.split("\\."); ArrayList splitTokens = new ArrayList(); for (String s : splitToken) { if (flavor == ConfigSyntax.CONF) splitTokens.add(Tokens.newUnquotedText(t.origin(), s)); else splitTokens.add(Tokens.newString(t.origin(), s, "\"" + s + "\"")); splitTokens.add(Tokens.newUnquotedText(t.origin(), ".")); } if (tokenText.charAt(tokenText.length() - 1) != '.') splitTokens.remove(splitTokens.size() - 1); return splitTokens; } private static void addPathText(List buf, boolean wasQuoted, String newText) { int i = wasQuoted ? -1 : newText.indexOf('.'); Element current = buf.get(buf.size() - 1); if (i < 0) { // add to current path element current.sb.append(newText); // any empty quoted string means this element can // now be empty. if (wasQuoted && current.sb.length() == 0) current.canBeEmpty = true; } else { // "buf" plus up to the period is an element current.sb.append(newText.substring(0, i)); // then start a new element buf.add(new Element("", false)); // recurse to consume remainder of newText addPathText(buf, false, newText.substring(i + 1)); } } // the idea is to see if the string has any chars or features // that might require the full parser to deal with. private static boolean looksUnsafeForFastParser(String s) { boolean lastWasDot = true; // start of path is also a "dot" int len = s.length(); if (s.isEmpty()) return true; if (s.charAt(0) == '.') return true; if (s.charAt(len - 1) == '.') return true; for (int i = 0; i < len; ++i) { char c = s.charAt(i); if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') { lastWasDot = false; continue; } else if (c == '.') { if (lastWasDot) return true; // ".." means we need to throw an error lastWasDot = true; } else if (c == '-') { if (lastWasDot) return true; continue; } else { return true; } } if (lastWasDot) return true; return false; } private static Path fastPathBuild(Path tail, String s, int end) { // lastIndexOf takes last index it should look at, end - 1 not end int splitAt = s.lastIndexOf('.', end - 1); ArrayList tokens = new ArrayList(); tokens.add(Tokens.newUnquotedText(null, s)); // this works even if splitAt is -1; then we start the substring at 0 Path withOneMoreElement = new Path(s.substring(splitAt + 1, end), tail); if (splitAt < 0) { return withOneMoreElement; } else { return fastPathBuild(withOneMoreElement, s, splitAt); } } // do something much faster than the full parser if // we just have something like "foo" or "foo.bar" private static Path speculativeFastParsePath(String path) { String s = ConfigImplUtil.unicodeTrim(path); if (looksUnsafeForFastParser(s)) return null; return fastPathBuild(null, s, s.length()); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/PropertiesParser.java000066400000000000000000000162271277147274600276730ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; final class PropertiesParser { static AbstractConfigObject parse(Reader reader, ConfigOrigin origin) throws IOException { Properties props = new Properties(); props.load(reader); return fromProperties(origin, props); } static String lastElement(String path) { int i = path.lastIndexOf('.'); if (i < 0) return path; else return path.substring(i + 1); } static String exceptLastElement(String path) { int i = path.lastIndexOf('.'); if (i < 0) return null; else return path.substring(0, i); } static Path pathFromPropertyKey(String key) { String last = lastElement(key); String exceptLast = exceptLastElement(key); Path path = new Path(last, null); while (exceptLast != null) { last = lastElement(exceptLast); exceptLast = exceptLastElement(exceptLast); path = new Path(last, path); } return path; } static AbstractConfigObject fromProperties(ConfigOrigin origin, Properties props) { Map pathMap = new HashMap(); for (Map.Entry entry : props.entrySet()) { Object key = entry.getKey(); if (key instanceof String) { Path path = pathFromPropertyKey((String) key); pathMap.put(path, entry.getValue()); } } return fromPathMap(origin, pathMap, true /* from properties */); } static AbstractConfigObject fromPathMap(ConfigOrigin origin, Map pathExpressionMap) { Map pathMap = new HashMap(); for (Map.Entry entry : pathExpressionMap.entrySet()) { Object keyObj = entry.getKey(); if (!(keyObj instanceof String)) { throw new ConfigException.BugOrBroken( "Map has a non-string as a key, expecting a path expression as a String"); } Path path = Path.newPath((String) keyObj); pathMap.put(path, entry.getValue()); } return fromPathMap(origin, pathMap, false /* from properties */); } private static AbstractConfigObject fromPathMap(ConfigOrigin origin, Map pathMap, boolean convertedFromProperties) { /* * First, build a list of paths that will have values, either string or * object values. */ Set scopePaths = new HashSet(); Set valuePaths = new HashSet(); for (Path path : pathMap.keySet()) { // add value's path valuePaths.add(path); // all parent paths are objects Path next = path.parent(); while (next != null) { scopePaths.add(next); next = next.parent(); } } if (convertedFromProperties) { /* * If any string values are also objects containing other values, * drop those string values - objects "win". */ valuePaths.removeAll(scopePaths); } else { /* If we didn't start out as properties, then this is an error. */ for (Path path : valuePaths) { if (scopePaths.contains(path)) { throw new ConfigException.BugOrBroken( "In the map, path '" + path.render() + "' occurs as both the parent object of a value and as a value. " + "Because Map has no defined ordering, this is a broken situation."); } } } /* * Create maps for the object-valued values. */ Map root = new HashMap(); Map> scopes = new HashMap>(); for (Path path : scopePaths) { Map scope = new HashMap(); scopes.put(path, scope); } /* Store string values in the associated scope maps */ for (Path path : valuePaths) { Path parentPath = path.parent(); Map parent = parentPath != null ? scopes .get(parentPath) : root; String last = path.last(); Object rawValue = pathMap.get(path); AbstractConfigValue value; if (convertedFromProperties) { if (rawValue instanceof String) { value = new ConfigString.Quoted(origin, (String) rawValue); } else { // silently ignore non-string values in Properties value = null; } } else { value = ConfigImpl.fromAnyRef(pathMap.get(path), origin, FromMapMode.KEYS_ARE_PATHS); } if (value != null) parent.put(last, value); } /* * Make a list of scope paths from longest to shortest, so children go * before parents. */ List sortedScopePaths = new ArrayList(); sortedScopePaths.addAll(scopePaths); // sort descending by length Collections.sort(sortedScopePaths, new Comparator() { @Override public int compare(Path a, Path b) { // Path.length() is O(n) so in theory this sucks // but in practice we can make Path precompute length // if it ever matters. return b.length() - a.length(); } }); /* * Create ConfigObject for each scope map, working from children to * parents to avoid modifying any already-created ConfigObject. This is * where we need the sorted list. */ for (Path scopePath : sortedScopePaths) { Map scope = scopes.get(scopePath); Path parentPath = scopePath.parent(); Map parent = parentPath != null ? scopes .get(parentPath) : root; AbstractConfigObject o = new SimpleConfigObject(origin, scope, ResolveStatus.RESOLVED, false /* ignoresFallbacks */); parent.put(scopePath.last(), o); } // return root config object return new SimpleConfigObject(origin, root, ResolveStatus.RESOLVED, false /* ignoresFallbacks */); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ReplaceableMergeStack.java000066400000000000000000000007721277147274600305250ustar00rootroot00000000000000package com.typesafe.config.impl; /** * Implemented by a merge stack (ConfigDelayedMerge, ConfigDelayedMergeObject) * that replaces itself during substitution resolution in order to implement * "look backwards only" semantics. */ interface ReplaceableMergeStack extends Container { /** * Make a replacement for this object skipping the given number of elements * which are lower in merge priority. */ AbstractConfigValue makeReplacement(ResolveContext context, int skipping); } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ResolveContext.java000066400000000000000000000243151277147274600273430ustar00rootroot00000000000000package com.typesafe.config.impl; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.ArrayList; import java.util.Set; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.impl.AbstractConfigValue.NotPossibleToResolve; final class ResolveContext { final private ResolveMemos memos; final private ConfigResolveOptions options; // the current path restriction, used to ensure lazy // resolution and avoid gratuitous cycles. without this, // any sibling of an object we're traversing could // cause a cycle "by side effect" // CAN BE NULL for a full resolve. final private Path restrictToChild; // This is used for tracing and debugging and nice error messages; // contains every node as we call resolve on it. final private List resolveStack; final private Set cycleMarkers; ResolveContext(ResolveMemos memos, ConfigResolveOptions options, Path restrictToChild, List resolveStack, Set cycleMarkers) { this.memos = memos; this.options = options; this.restrictToChild = restrictToChild; this.resolveStack = Collections.unmodifiableList(resolveStack); this.cycleMarkers = Collections.unmodifiableSet(cycleMarkers); } private static Set newCycleMarkers() { return Collections.newSetFromMap(new IdentityHashMap()); } ResolveContext(ConfigResolveOptions options, Path restrictToChild) { // LinkedHashSet keeps the traversal order which is at least useful // in error messages if nothing else this(new ResolveMemos(), options, restrictToChild, new ArrayList(), newCycleMarkers()); if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "ResolveContext restrict to child " + restrictToChild); } ResolveContext addCycleMarker(AbstractConfigValue value) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "++ Cycle marker " + value + "@" + System.identityHashCode(value)); if (cycleMarkers.contains(value)) throw new ConfigException.BugOrBroken("Added cycle marker twice " + value); Set copy = newCycleMarkers(); copy.addAll(cycleMarkers); copy.add(value); return new ResolveContext(memos, options, restrictToChild, resolveStack, copy); } ResolveContext removeCycleMarker(AbstractConfigValue value) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "-- Cycle marker " + value + "@" + System.identityHashCode(value)); Set copy = newCycleMarkers(); copy.addAll(cycleMarkers); copy.remove(value); return new ResolveContext(memos, options, restrictToChild, resolveStack, copy); } private ResolveContext memoize(MemoKey key, AbstractConfigValue value) { ResolveMemos changed = memos.put(key, value); return new ResolveContext(changed, options, restrictToChild, resolveStack, cycleMarkers); } ConfigResolveOptions options() { return options; } boolean isRestrictedToChild() { return restrictToChild != null; } Path restrictToChild() { return restrictToChild; } // restrictTo may be null to unrestrict ResolveContext restrict(Path restrictTo) { if (restrictTo == restrictToChild) return this; else return new ResolveContext(memos, options, restrictTo, resolveStack, cycleMarkers); } ResolveContext unrestricted() { return restrict(null); } String traceString() { String separator = ", "; StringBuilder sb = new StringBuilder(); for (AbstractConfigValue value : resolveStack) { if (value instanceof ConfigReference) { sb.append(((ConfigReference) value).expression().toString()); sb.append(separator); } } if (sb.length() > 0) sb.setLength(sb.length() - separator.length()); return sb.toString(); } private ResolveContext pushTrace(AbstractConfigValue value) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "pushing trace " + value); List copy = new ArrayList(resolveStack); copy.add(value); return new ResolveContext(memos, options, restrictToChild, copy, cycleMarkers); } ResolveContext popTrace() { List copy = new ArrayList(resolveStack); AbstractConfigValue old = copy.remove(resolveStack.size() - 1); if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth() - 1, "popped trace " + old); return new ResolveContext(memos, options, restrictToChild, copy, cycleMarkers); } int depth() { if (resolveStack.size() > 30) throw new ConfigException.BugOrBroken("resolve getting too deep"); return resolveStack.size(); } ResolveResult resolve(AbstractConfigValue original, ResolveSource source) throws NotPossibleToResolve { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl .trace(depth(), "resolving " + original + " restrictToChild=" + restrictToChild + " in " + source); return pushTrace(original).realResolve(original, source).popTrace(); } private ResolveResult realResolve(AbstractConfigValue original, ResolveSource source) throws NotPossibleToResolve { // a fully-resolved (no restrictToChild) object can satisfy a // request for a restricted object, so always check that first. final MemoKey fullKey = new MemoKey(original, null); MemoKey restrictedKey = null; AbstractConfigValue cached = memos.get(fullKey); // but if there was no fully-resolved object cached, we'll only // compute the restrictToChild object so use a more limited // memo key if (cached == null && isRestrictedToChild()) { restrictedKey = new MemoKey(original, restrictToChild()); cached = memos.get(restrictedKey); } if (cached != null) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "using cached resolution " + cached + " for " + original + " restrictToChild " + restrictToChild()); return ResolveResult.make(this, cached); } else { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "not found in cache, resolving " + original + "@" + System.identityHashCode(original)); if (cycleMarkers.contains(original)) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "Cycle detected, can't resolve; " + original + "@" + System.identityHashCode(original)); throw new NotPossibleToResolve(this); } ResolveResult result = original.resolveSubstitutions(this, source); AbstractConfigValue resolved = result.value; if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "resolved to " + resolved + "@" + System.identityHashCode(resolved) + " from " + original + "@" + System.identityHashCode(resolved)); ResolveContext withMemo = result.context; if (resolved == null || resolved.resolveStatus() == ResolveStatus.RESOLVED) { // if the resolved object is fully resolved by resolving // only the restrictToChildOrNull, then it can be cached // under fullKey since the child we were restricted to // turned out to be the only unresolved thing. if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "caching " + fullKey + " result " + resolved); withMemo = withMemo.memoize(fullKey, resolved); } else { // if we have an unresolved object then either we did a // partial resolve restricted to a certain child, or we are // allowing incomplete resolution, or it's a bug. if (isRestrictedToChild()) { if (restrictedKey == null) { throw new ConfigException.BugOrBroken( "restrictedKey should not be null here"); } if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "caching " + restrictedKey + " result " + resolved); withMemo = withMemo.memoize(restrictedKey, resolved); } else if (options().getAllowUnresolved()) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(depth(), "caching " + fullKey + " result " + resolved); withMemo = withMemo.memoize(fullKey, resolved); } else { throw new ConfigException.BugOrBroken( "resolveSubstitutions() did not give us a resolved object"); } } return ResolveResult.make(withMemo, resolved); } } static AbstractConfigValue resolve(AbstractConfigValue value, AbstractConfigObject root, ConfigResolveOptions options) { ResolveSource source = new ResolveSource(root); ResolveContext context = new ResolveContext(options, null /* restrictToChild */); try { return context.resolve(value, source).value; } catch (NotPossibleToResolve e) { // ConfigReference was supposed to catch NotPossibleToResolve throw new ConfigException.BugOrBroken( "NotPossibleToResolve was thrown from an outermost resolve", e); } } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ResolveMemos.java000066400000000000000000000022471277147274600267770ustar00rootroot00000000000000package com.typesafe.config.impl; import java.util.HashMap; import java.util.Map; /** * This exists because we have to memoize resolved substitutions as we go * through the config tree; otherwise we could end up creating multiple copies * of values or whole trees of values as we follow chains of substitutions. */ final class ResolveMemos { // note that we can resolve things to undefined (represented as Java null, // rather than ConfigNull) so this map can have null values. final private Map memos; private ResolveMemos(Map memos) { this.memos = memos; } ResolveMemos() { this(new HashMap()); } AbstractConfigValue get(MemoKey key) { return memos.get(key); } ResolveMemos put(MemoKey key, AbstractConfigValue value) { // completely inefficient, but so far nobody cares about resolve() // performance, we can clean it up someday... Map copy = new HashMap(memos); copy.put(key, value); return new ResolveMemos(copy); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ResolveResult.java000066400000000000000000000024721277147274600271750ustar00rootroot00000000000000package com.typesafe.config.impl; import com.typesafe.config.ConfigException; // value is allowed to be null final class ResolveResult { public final ResolveContext context; public final V value; private ResolveResult(ResolveContext context, V value) { this.context = context; this.value = value; } static ResolveResult make(ResolveContext context, V value) { return new ResolveResult(context, value); } // better option? we don't have variance @SuppressWarnings("unchecked") ResolveResult asObjectResult() { if (!(value instanceof AbstractConfigObject)) throw new ConfigException.BugOrBroken("Expecting a resolve result to be an object, but it was " + value); Object o = this; return (ResolveResult) o; } // better option? we don't have variance @SuppressWarnings("unchecked") ResolveResult asValueResult() { Object o = this; return (ResolveResult) o; } ResolveResult popTrace() { return make(context.popTrace(), value); } @Override public String toString() { return "ResolveResult(" + value + ")"; } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ResolveSource.java000066400000000000000000000341511277147274600271560ustar00rootroot00000000000000package com.typesafe.config.impl; import com.typesafe.config.ConfigException; import com.typesafe.config.impl.AbstractConfigValue.NotPossibleToResolve; /** * This class is the source for values for a substitution like ${foo}. */ final class ResolveSource { final AbstractConfigObject root; // This is used for knowing the chain of parents we used to get here. // null if we should assume we are not a descendant of the root. // the root itself should be a node in this if non-null. final Node pathFromRoot; ResolveSource(AbstractConfigObject root, Node pathFromRoot) { this.root = root; this.pathFromRoot = pathFromRoot; } ResolveSource(AbstractConfigObject root) { this.root = root; this.pathFromRoot = null; } // if we replace the root with a non-object, use an empty // object with nothing in it instead. private AbstractConfigObject rootMustBeObj(Container value) { if (value instanceof AbstractConfigObject) { return (AbstractConfigObject) value; } else { return SimpleConfigObject.empty(); } } // as a side effect, findInObject() will have to resolve all parents of the // child being peeked, but NOT the child itself. Caller has to resolve // the child itself if needed. ValueWithPath.value can be null but // the ValueWithPath instance itself should not be. static private ResultWithPath findInObject(AbstractConfigObject obj, ResolveContext context, Path path) throws NotPossibleToResolve { // resolve ONLY portions of the object which are along our path if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace("*** finding '" + path + "' in " + obj); Path restriction = context.restrictToChild(); ResolveResult partiallyResolved = context.restrict(path).resolve(obj, new ResolveSource(obj)); ResolveContext newContext = partiallyResolved.context.restrict(restriction); if (partiallyResolved.value instanceof AbstractConfigObject) { ValueWithPath pair = findInObject((AbstractConfigObject) partiallyResolved.value, path); return new ResultWithPath(ResolveResult.make(newContext, pair.value), pair.pathFromRoot); } else { throw new ConfigException.BugOrBroken("resolved object to non-object " + obj + " to " + partiallyResolved); } } static private ValueWithPath findInObject(AbstractConfigObject obj, Path path) { try { // we'll fail if anything along the path can't // be looked at without resolving. return findInObject(obj, path, null); } catch (ConfigException.NotResolved e) { throw ConfigImpl.improveNotResolved(path, e); } } static private ValueWithPath findInObject(AbstractConfigObject obj, Path path, Node parents) { String key = path.first(); Path next = path.remainder(); if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace("*** looking up '" + key + "' in " + obj); AbstractConfigValue v = obj.attemptPeekWithPartialResolve(key); Node newParents = parents == null ? new Node(obj) : parents.prepend(obj); if (next == null) { return new ValueWithPath(v, newParents); } else { if (v instanceof AbstractConfigObject) { return findInObject((AbstractConfigObject) v, next, newParents); } else { return new ValueWithPath(null, newParents); } } } ResultWithPath lookupSubst(ResolveContext context, SubstitutionExpression subst, int prefixLength) throws NotPossibleToResolve { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(context.depth(), "searching for " + subst); if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(context.depth(), subst + " - looking up relative to file it occurred in"); // First we look up the full path, which means relative to the // included file if we were not a root file ResultWithPath result = findInObject(root, context, subst.path()); if (result.result.value == null) { // Then we want to check relative to the root file. We don't // want the prefix we were included at to be used when looking // up env variables either. Path unprefixed = subst.path().subPath(prefixLength); if (prefixLength > 0) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(result.result.context.depth(), unprefixed + " - looking up relative to parent file"); result = findInObject(root, result.result.context, unprefixed); } if (result.result.value == null && result.result.context.options().getUseSystemEnvironment()) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(result.result.context.depth(), unprefixed + " - looking up in system environment"); result = findInObject(ConfigImpl.envVariablesAsConfigObject(), context, unprefixed); } } if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace(result.result.context.depth(), "resolved to " + result); return result; } ResolveSource pushParent(Container parent) { if (parent == null) throw new ConfigException.BugOrBroken("can't push null parent"); if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace("pushing parent " + parent + " ==root " + (parent == root) + " onto " + this); if (pathFromRoot == null) { if (parent == root) { return new ResolveSource(root, new Node(parent)); } else { if (ConfigImpl.traceSubstitutionsEnabled()) { // this hasDescendant check is super-expensive so it's a // trace message rather than an assertion if (root.hasDescendant((AbstractConfigValue) parent)) ConfigImpl.trace("***** BUG ***** tried to push parent " + parent + " without having a path to it in " + this); } // ignore parents if we aren't proceeding from the // root return this; } } else { Container parentParent = pathFromRoot.head(); if (ConfigImpl.traceSubstitutionsEnabled()) { // this hasDescendant check is super-expensive so it's a // trace message rather than an assertion if (parentParent != null && !parentParent.hasDescendant((AbstractConfigValue) parent)) ConfigImpl.trace("***** BUG ***** trying to push non-child of " + parentParent + ", non-child was " + parent); } return new ResolveSource(root, pathFromRoot.prepend(parent)); } } ResolveSource resetParents() { if (pathFromRoot == null) return this; else return new ResolveSource(root); } // returns null if the replacement results in deleting all the nodes. private static Node replace(Node list, Container old, AbstractConfigValue replacement) { Container child = list.head(); if (child != old) throw new ConfigException.BugOrBroken("Can only replace() the top node we're resolving; had " + child + " on top and tried to replace " + old + " overall list was " + list); Container parent = list.tail() == null ? null : list.tail().head(); if (replacement == null || !(replacement instanceof Container)) { if (parent == null) { return null; } else { /* * we are deleting the child from the stack of containers * because it's either going away or not a container */ AbstractConfigValue newParent = parent.replaceChild((AbstractConfigValue) old, null); return replace(list.tail(), parent, newParent); } } else { /* we replaced the container with another container */ if (parent == null) { return new Node((Container) replacement); } else { AbstractConfigValue newParent = parent.replaceChild((AbstractConfigValue) old, replacement); Node newTail = replace(list.tail(), parent, newParent); if (newTail != null) return newTail.prepend((Container) replacement); else return new Node((Container) replacement); } } } ResolveSource replaceCurrentParent(Container old, Container replacement) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace("replaceCurrentParent old " + old + "@" + System.identityHashCode(old) + " replacement " + replacement + "@" + System.identityHashCode(old) + " in " + this); if (old == replacement) { return this; } else if (pathFromRoot != null) { Node newPath = replace(pathFromRoot, old, (AbstractConfigValue) replacement); if (ConfigImpl.traceSubstitutionsEnabled()) { ConfigImpl.trace("replaced " + old + " with " + replacement + " in " + this); ConfigImpl.trace("path was: " + pathFromRoot + " is now " + newPath); } // if we end up nuking the root object itself, we replace it with an // empty root if (newPath != null) return new ResolveSource((AbstractConfigObject) newPath.last(), newPath); else return new ResolveSource(SimpleConfigObject.empty()); } else { if (old == root) { return new ResolveSource(rootMustBeObj(replacement)); } else { throw new ConfigException.BugOrBroken("attempt to replace root " + root + " with " + replacement); // return this; } } } // replacement may be null to delete ResolveSource replaceWithinCurrentParent(AbstractConfigValue old, AbstractConfigValue replacement) { if (ConfigImpl.traceSubstitutionsEnabled()) ConfigImpl.trace("replaceWithinCurrentParent old " + old + "@" + System.identityHashCode(old) + " replacement " + replacement + "@" + System.identityHashCode(old) + " in " + this); if (old == replacement) { return this; } else if (pathFromRoot != null) { Container parent = pathFromRoot.head(); AbstractConfigValue newParent = parent.replaceChild(old, replacement); return replaceCurrentParent(parent, (newParent instanceof Container) ? (Container) newParent : null); } else { if (old == root && replacement instanceof Container) { return new ResolveSource(rootMustBeObj((Container) replacement)); } else { throw new ConfigException.BugOrBroken("replace in parent not possible " + old + " with " + replacement + " in " + this); // return this; } } } @Override public String toString() { return "ResolveSource(root=" + root + ", pathFromRoot=" + pathFromRoot + ")"; } // a persistent list static final class Node { final T value; final Node next; Node(T value, Node next) { this.value = value; this.next = next; } Node(T value) { this(value, null); } Node prepend(T value) { return new Node(value, this); } T head() { return value; } Node tail() { return next; } T last() { Node i = this; while (i.next != null) i = i.next; return i.value; } Node reverse() { if (next == null) { return this; } else { Node reversed = new Node(value); Node i = next; while (i != null) { reversed = reversed.prepend(i.value); i = i.next; } return reversed; } } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("["); Node toAppendValue = this.reverse(); while (toAppendValue != null) { sb.append(toAppendValue.value.toString()); if (toAppendValue.next != null) sb.append(" <= "); toAppendValue = toAppendValue.next; } sb.append("]"); return sb.toString(); } } // value is allowed to be null static final class ValueWithPath { final AbstractConfigValue value; final Node pathFromRoot; ValueWithPath(AbstractConfigValue value, Node pathFromRoot) { this.value = value; this.pathFromRoot = pathFromRoot; } @Override public String toString() { return "ValueWithPath(value=" + value + ", pathFromRoot=" + pathFromRoot + ")"; } } static final class ResultWithPath { final ResolveResult result; final Node pathFromRoot; ResultWithPath(ResolveResult result, Node pathFromRoot) { this.result = result; this.pathFromRoot = pathFromRoot; } @Override public String toString() { return "ResultWithPath(result=" + result + ", pathFromRoot=" + pathFromRoot + ")"; } } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/ResolveStatus.java000066400000000000000000000012761277147274600272030ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.util.Collection; /** * Status of substitution resolution. */ enum ResolveStatus { UNRESOLVED, RESOLVED; final static ResolveStatus fromValues( Collection values) { for (AbstractConfigValue v : values) { if (v.resolveStatus() == ResolveStatus.UNRESOLVED) return ResolveStatus.UNRESOLVED; } return ResolveStatus.RESOLVED; } final static ResolveStatus fromBoolean(boolean resolved) { return resolved ? ResolveStatus.RESOLVED : ResolveStatus.UNRESOLVED; } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/SerializedConfigValue.java000066400000000000000000000441641277147274600306010ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.ByteArrayOutputStream; import java.io.DataInput; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.Externalizable; import java.io.IOException; import java.io.NotSerializableException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.ObjectStreamException; import java.util.ArrayList; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; import com.typesafe.config.Config; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; /** * Deliberately shoving all the serialization code into this class instead of * doing it OO-style with each subclass. Seems better to have it all in one * place. This class implements a lame serialization format that supports * skipping unknown fields, so it's moderately more extensible than the default * Java serialization format. */ class SerializedConfigValue extends AbstractConfigValue implements Externalizable { // this is the version used by Java serialization, if it increments it's // essentially an ABI break and bad private static final long serialVersionUID = 1L; // this is how we try to be extensible static enum SerializedField { // represents a field code we didn't recognize UNKNOWN, // end of a list of fields END_MARKER, // Fields at the root ROOT_VALUE, ROOT_WAS_CONFIG, // Fields that make up a value VALUE_DATA, VALUE_ORIGIN, // Fields that make up an origin ORIGIN_DESCRIPTION, ORIGIN_LINE_NUMBER, ORIGIN_END_LINE_NUMBER, ORIGIN_TYPE, ORIGIN_URL, ORIGIN_COMMENTS, ORIGIN_NULL_URL, ORIGIN_NULL_COMMENTS, ORIGIN_RESOURCE, ORIGIN_NULL_RESOURCE; static SerializedField forInt(int b) { if (b < values().length) return values()[b]; else return UNKNOWN; } }; private static enum SerializedValueType { // the ordinals here are in the wire format, caution NULL(ConfigValueType.NULL), BOOLEAN(ConfigValueType.BOOLEAN), INT(ConfigValueType.NUMBER), LONG(ConfigValueType.NUMBER), DOUBLE(ConfigValueType.NUMBER), STRING(ConfigValueType.STRING), LIST(ConfigValueType.LIST), OBJECT(ConfigValueType.OBJECT); ConfigValueType configType; SerializedValueType(ConfigValueType configType) { this.configType = configType; } static SerializedValueType forInt(int b) { if (b < values().length) return values()[b]; else return null; } static SerializedValueType forValue(ConfigValue value) { ConfigValueType t = value.valueType(); if (t == ConfigValueType.NUMBER) { if (value instanceof ConfigInt) return INT; else if (value instanceof ConfigLong) return LONG; else if (value instanceof ConfigDouble) return DOUBLE; } else { for (SerializedValueType st : values()) { if (st.configType == t) return st; } } throw new ConfigException.BugOrBroken("don't know how to serialize " + value); } }; private ConfigValue value; private boolean wasConfig; // this has to be public for the Java deserializer public SerializedConfigValue() { super(null); } SerializedConfigValue(ConfigValue value) { this(); this.value = value; this.wasConfig = false; } SerializedConfigValue(Config conf) { this(conf.root()); this.wasConfig = true; } // when Java deserializer reads this object, return the contained // object instead. private Object readResolve() throws ObjectStreamException { if (wasConfig) return ((ConfigObject) value).toConfig(); else return value; } private static class FieldOut { final SerializedField code; final ByteArrayOutputStream bytes; final DataOutput data; FieldOut(SerializedField code) { this.code = code; this.bytes = new ByteArrayOutputStream(); this.data = new DataOutputStream(bytes); } } // this is a separate function to prevent bugs writing to the // outer stream instead of field.data private static void writeOriginField(DataOutput out, SerializedField code, Object v) throws IOException { switch (code) { case ORIGIN_DESCRIPTION: out.writeUTF((String) v); break; case ORIGIN_LINE_NUMBER: out.writeInt((Integer) v); break; case ORIGIN_END_LINE_NUMBER: out.writeInt((Integer) v); break; case ORIGIN_TYPE: out.writeByte((Integer) v); break; case ORIGIN_URL: out.writeUTF((String) v); break; case ORIGIN_RESOURCE: out.writeUTF((String) v); break; case ORIGIN_COMMENTS: @SuppressWarnings("unchecked") List list = (List) v; int size = list.size(); out.writeInt(size); for (String s : list) { out.writeUTF(s); } break; case ORIGIN_NULL_URL: // FALL THRU case ORIGIN_NULL_RESOURCE: // FALL THRU case ORIGIN_NULL_COMMENTS: // nothing to write out besides code and length break; default: throw new IOException("Unhandled field from origin: " + code); } } // not private because we use it to serialize ConfigException static void writeOrigin(DataOutput out, SimpleConfigOrigin origin, SimpleConfigOrigin baseOrigin) throws IOException { Map m; // to serialize a null origin, we write out no fields at all if (origin != null) m = origin.toFieldsDelta(baseOrigin); else m = Collections.emptyMap(); for (Map.Entry e : m.entrySet()) { FieldOut field = new FieldOut(e.getKey()); Object v = e.getValue(); writeOriginField(field.data, field.code, v); writeField(out, field); } writeEndMarker(out); } // not private because we use it to deserialize ConfigException static SimpleConfigOrigin readOrigin(DataInput in, SimpleConfigOrigin baseOrigin) throws IOException { Map m = new EnumMap(SerializedField.class); while (true) { Object v = null; SerializedField field = readCode(in); switch (field) { case END_MARKER: return SimpleConfigOrigin.fromBase(baseOrigin, m); case ORIGIN_DESCRIPTION: in.readInt(); // discard length v = in.readUTF(); break; case ORIGIN_LINE_NUMBER: in.readInt(); // discard length v = in.readInt(); break; case ORIGIN_END_LINE_NUMBER: in.readInt(); // discard length v = in.readInt(); break; case ORIGIN_TYPE: in.readInt(); // discard length v = in.readUnsignedByte(); break; case ORIGIN_URL: in.readInt(); // discard length v = in.readUTF(); break; case ORIGIN_RESOURCE: in.readInt(); // discard length v = in.readUTF(); break; case ORIGIN_COMMENTS: in.readInt(); // discard length int size = in.readInt(); List list = new ArrayList(size); for (int i = 0; i < size; ++i) { list.add(in.readUTF()); } v = list; break; case ORIGIN_NULL_URL: // FALL THRU case ORIGIN_NULL_RESOURCE: // FALL THRU case ORIGIN_NULL_COMMENTS: // nothing to read besides code and length in.readInt(); // discard length v = ""; // just something non-null to put in the map break; case ROOT_VALUE: case ROOT_WAS_CONFIG: case VALUE_DATA: case VALUE_ORIGIN: throw new IOException("Not expecting this field here: " + field); case UNKNOWN: // skip unknown field skipField(in); break; } if (v != null) m.put(field, v); } } private static void writeValueData(DataOutput out, ConfigValue value) throws IOException { SerializedValueType st = SerializedValueType.forValue(value); out.writeByte(st.ordinal()); switch (st) { case BOOLEAN: out.writeBoolean(((ConfigBoolean) value).unwrapped()); break; case NULL: break; case INT: // saving numbers as both string and binary is redundant but easy out.writeInt(((ConfigInt) value).unwrapped()); out.writeUTF(((ConfigNumber) value).transformToString()); break; case LONG: out.writeLong(((ConfigLong) value).unwrapped()); out.writeUTF(((ConfigNumber) value).transformToString()); break; case DOUBLE: out.writeDouble(((ConfigDouble) value).unwrapped()); out.writeUTF(((ConfigNumber) value).transformToString()); break; case STRING: out.writeUTF(((ConfigString) value).unwrapped()); break; case LIST: ConfigList list = (ConfigList) value; out.writeInt(list.size()); for (ConfigValue v : list) { writeValue(out, v, (SimpleConfigOrigin) list.origin()); } break; case OBJECT: ConfigObject obj = (ConfigObject) value; out.writeInt(obj.size()); for (Map.Entry e : obj.entrySet()) { out.writeUTF(e.getKey()); writeValue(out, e.getValue(), (SimpleConfigOrigin) obj.origin()); } break; } } private static AbstractConfigValue readValueData(DataInput in, SimpleConfigOrigin origin) throws IOException { int stb = in.readUnsignedByte(); SerializedValueType st = SerializedValueType.forInt(stb); if (st == null) throw new IOException("Unknown serialized value type: " + stb); switch (st) { case BOOLEAN: return new ConfigBoolean(origin, in.readBoolean()); case NULL: return new ConfigNull(origin); case INT: int vi = in.readInt(); String si = in.readUTF(); return new ConfigInt(origin, vi, si); case LONG: long vl = in.readLong(); String sl = in.readUTF(); return new ConfigLong(origin, vl, sl); case DOUBLE: double vd = in.readDouble(); String sd = in.readUTF(); return new ConfigDouble(origin, vd, sd); case STRING: return new ConfigString.Quoted(origin, in.readUTF()); case LIST: int listSize = in.readInt(); List list = new ArrayList(listSize); for (int i = 0; i < listSize; ++i) { AbstractConfigValue v = readValue(in, origin); list.add(v); } return new SimpleConfigList(origin, list); case OBJECT: int mapSize = in.readInt(); Map map = new HashMap(mapSize); for (int i = 0; i < mapSize; ++i) { String key = in.readUTF(); AbstractConfigValue v = readValue(in, origin); map.put(key, v); } return new SimpleConfigObject(origin, map); } throw new IOException("Unhandled serialized value type: " + st); } private static void writeValue(DataOutput out, ConfigValue value, SimpleConfigOrigin baseOrigin) throws IOException { FieldOut origin = new FieldOut(SerializedField.VALUE_ORIGIN); writeOrigin(origin.data, (SimpleConfigOrigin) value.origin(), baseOrigin); writeField(out, origin); FieldOut data = new FieldOut(SerializedField.VALUE_DATA); writeValueData(data.data, value); writeField(out, data); writeEndMarker(out); } private static AbstractConfigValue readValue(DataInput in, SimpleConfigOrigin baseOrigin) throws IOException { AbstractConfigValue value = null; SimpleConfigOrigin origin = null; while (true) { SerializedField code = readCode(in); if (code == SerializedField.END_MARKER) { if (value == null) throw new IOException("No value data found in serialization of value"); return value; } else if (code == SerializedField.VALUE_DATA) { if (origin == null) throw new IOException("Origin must be stored before value data"); in.readInt(); // discard length value = readValueData(in, origin); } else if (code == SerializedField.VALUE_ORIGIN) { in.readInt(); // discard length origin = readOrigin(in, baseOrigin); } else { // ignore unknown field skipField(in); } } } private static void writeField(DataOutput out, FieldOut field) throws IOException { byte[] bytes = field.bytes.toByteArray(); out.writeByte(field.code.ordinal()); out.writeInt(bytes.length); out.write(bytes); } private static void writeEndMarker(DataOutput out) throws IOException { out.writeByte(SerializedField.END_MARKER.ordinal()); } private static SerializedField readCode(DataInput in) throws IOException { int c = in.readUnsignedByte(); if (c == SerializedField.UNKNOWN.ordinal()) throw new IOException("field code " + c + " is not supposed to be on the wire"); return SerializedField.forInt(c); } private static void skipField(DataInput in) throws IOException { int len = in.readInt(); // skipBytes doesn't have to block int skipped = in.skipBytes(len); if (skipped < len) { // wastefully use readFully() if skipBytes didn't work byte[] bytes = new byte[(len - skipped)]; in.readFully(bytes); } } @Override public void writeExternal(ObjectOutput out) throws IOException { if (((AbstractConfigValue) value).resolveStatus() != ResolveStatus.RESOLVED) throw new NotSerializableException( "tried to serialize a value with unresolved substitutions, need to Config#resolve() first, see API docs"); FieldOut field = new FieldOut(SerializedField.ROOT_VALUE); writeValue(field.data, value, null /* baseOrigin */); writeField(out, field); field = new FieldOut(SerializedField.ROOT_WAS_CONFIG); field.data.writeBoolean(wasConfig); writeField(out, field); writeEndMarker(out); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { while (true) { SerializedField code = readCode(in); if (code == SerializedField.END_MARKER) { return; } else if (code == SerializedField.ROOT_VALUE) { in.readInt(); // discard length this.value = readValue(in, null /* baseOrigin */); } else if (code == SerializedField.ROOT_WAS_CONFIG) { in.readInt(); // discard length this.wasConfig = in.readBoolean(); } else { // ignore unknown field skipField(in); } } } private static ConfigException shouldNotBeUsed() { return new ConfigException.BugOrBroken(SerializedConfigValue.class.getName() + " should not exist outside of serialization"); } @Override public ConfigValueType valueType() { throw shouldNotBeUsed(); } @Override public Object unwrapped() { throw shouldNotBeUsed(); } @Override protected SerializedConfigValue newCopy(ConfigOrigin origin) { throw shouldNotBeUsed(); } @Override public final String toString() { return getClass().getSimpleName() + "(value=" + value + ",wasConfig=" + wasConfig + ")"; } @Override public boolean equals(Object other) { // there's no reason we will ever call this equals(), but // the one in AbstractConfigValue would explode due to // calling unwrapped() above, so we just give some // safe-to-call implementation to avoid breaking the // contract of java.lang.Object if (other instanceof SerializedConfigValue) { return canEqual(other) && (this.wasConfig == ((SerializedConfigValue) other).wasConfig) && (this.value.equals(((SerializedConfigValue) other).value)); } else { return false; } } @Override public int hashCode() { int h = 41 * (41 + value.hashCode()); h = 41 * (h + (wasConfig ? 1 : 0)); return h; } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/SimpleConfig.java000066400000000000000000001162571277147274600267450ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.ObjectStreamException; import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; import java.time.Duration; import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import com.typesafe.config.Config; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigMemorySize; import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigResolveOptions; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; /** * One thing to keep in mind in the future: as Collection-like APIs are added * here, including iterators or size() or anything, they should be consistent * with a one-level java.util.Map from paths to non-null values. Null values are * not "in" the map. */ final class SimpleConfig implements Config, MergeableValue, Serializable { private static final long serialVersionUID = 1L; final private AbstractConfigObject object; SimpleConfig(AbstractConfigObject object) { this.object = object; } @Override public AbstractConfigObject root() { return object; } @Override public ConfigOrigin origin() { return object.origin(); } @Override public SimpleConfig resolve() { return resolve(ConfigResolveOptions.defaults()); } @Override public SimpleConfig resolve(ConfigResolveOptions options) { return resolveWith(this, options); } @Override public SimpleConfig resolveWith(Config source) { return resolveWith(source, ConfigResolveOptions.defaults()); } @Override public SimpleConfig resolveWith(Config source, ConfigResolveOptions options) { AbstractConfigValue resolved = ResolveContext.resolve(object, ((SimpleConfig) source).object, options); if (resolved == object) return this; else return new SimpleConfig((AbstractConfigObject) resolved); } private ConfigValue hasPathPeek(String pathExpression) { Path path = Path.newPath(pathExpression); ConfigValue peeked; try { peeked = object.peekPath(path); } catch (ConfigException.NotResolved e) { throw ConfigImpl.improveNotResolved(path, e); } return peeked; } @Override public boolean hasPath(String pathExpression) { ConfigValue peeked = hasPathPeek(pathExpression); return peeked != null && peeked.valueType() != ConfigValueType.NULL; } @Override public boolean hasPathOrNull(String path) { ConfigValue peeked = hasPathPeek(path); return peeked != null; } @Override public boolean isEmpty() { return object.isEmpty(); } private static void findPaths(Set> entries, Path parent, AbstractConfigObject obj) { for (Map.Entry entry : obj.entrySet()) { String elem = entry.getKey(); ConfigValue v = entry.getValue(); Path path = Path.newKey(elem); if (parent != null) path = path.prepend(parent); if (v instanceof AbstractConfigObject) { findPaths(entries, path, (AbstractConfigObject) v); } else if (v instanceof ConfigNull) { // nothing; nulls are conceptually not in a Config } else { entries.add(new AbstractMap.SimpleImmutableEntry(path.render(), v)); } } } @Override public Set> entrySet() { Set> entries = new HashSet>(); findPaths(entries, null, object); return entries; } static private AbstractConfigValue throwIfNull(AbstractConfigValue v, ConfigValueType expected, Path originalPath) { if (v.valueType() == ConfigValueType.NULL) throw new ConfigException.Null(v.origin(), originalPath.render(), expected != null ? expected.name() : null); else return v; } static private AbstractConfigValue findKey(AbstractConfigObject self, String key, ConfigValueType expected, Path originalPath) { return throwIfNull(findKeyOrNull(self, key, expected, originalPath), expected, originalPath); } static private AbstractConfigValue findKeyOrNull(AbstractConfigObject self, String key, ConfigValueType expected, Path originalPath) { AbstractConfigValue v = self.peekAssumingResolved(key, originalPath); if (v == null) throw new ConfigException.Missing(originalPath.render()); if (expected != null) v = DefaultTransformer.transform(v, expected); if (expected != null && (v.valueType() != expected && v.valueType() != ConfigValueType.NULL)) throw new ConfigException.WrongType(v.origin(), originalPath.render(), expected.name(), v.valueType().name()); else return v; } static private AbstractConfigValue findOrNull(AbstractConfigObject self, Path path, ConfigValueType expected, Path originalPath) { try { String key = path.first(); Path next = path.remainder(); if (next == null) { return findKeyOrNull(self, key, expected, originalPath); } else { AbstractConfigObject o = (AbstractConfigObject) findKey(self, key, ConfigValueType.OBJECT, originalPath.subPath(0, originalPath.length() - next.length())); assert (o != null); // missing was supposed to throw return findOrNull(o, next, expected, originalPath); } } catch (ConfigException.NotResolved e) { throw ConfigImpl.improveNotResolved(path, e); } } AbstractConfigValue find(Path pathExpression, ConfigValueType expected, Path originalPath) { return throwIfNull(findOrNull(object, pathExpression, expected, originalPath), expected, originalPath); } AbstractConfigValue find(String pathExpression, ConfigValueType expected) { Path path = Path.newPath(pathExpression); return find(path, expected, path); } private AbstractConfigValue findOrNull(Path pathExpression, ConfigValueType expected, Path originalPath) { return findOrNull(object, pathExpression, expected, originalPath); } private AbstractConfigValue findOrNull(String pathExpression, ConfigValueType expected) { Path path = Path.newPath(pathExpression); return findOrNull(path, expected, path); } @Override public AbstractConfigValue getValue(String path) { return find(path, null); } @Override public boolean getIsNull(String path) { AbstractConfigValue v = findOrNull(path, null); return (v.valueType() == ConfigValueType.NULL); } @Override public boolean getBoolean(String path) { ConfigValue v = find(path, ConfigValueType.BOOLEAN); return (Boolean) v.unwrapped(); } private ConfigNumber getConfigNumber(String path) { ConfigValue v = find(path, ConfigValueType.NUMBER); return (ConfigNumber) v; } @Override public Number getNumber(String path) { return getConfigNumber(path).unwrapped(); } @Override public int getInt(String path) { ConfigNumber n = getConfigNumber(path); return n.intValueRangeChecked(path); } @Override public long getLong(String path) { return getNumber(path).longValue(); } @Override public double getDouble(String path) { return getNumber(path).doubleValue(); } @Override public String getString(String path) { ConfigValue v = find(path, ConfigValueType.STRING); return (String) v.unwrapped(); } @Override public > T getEnum(Class enumClass, String path) { ConfigValue v = find(path, ConfigValueType.STRING); return getEnumValue(path, enumClass, v); } @Override public ConfigList getList(String path) { AbstractConfigValue v = find(path, ConfigValueType.LIST); return (ConfigList) v; } @Override public AbstractConfigObject getObject(String path) { AbstractConfigObject obj = (AbstractConfigObject) find(path, ConfigValueType.OBJECT); return obj; } @Override public SimpleConfig getConfig(String path) { return getObject(path).toConfig(); } @Override public Object getAnyRef(String path) { ConfigValue v = find(path, null); return v.unwrapped(); } @Override public Long getBytes(String path) { Long size = null; try { size = getLong(path); } catch (ConfigException.WrongType e) { ConfigValue v = find(path, ConfigValueType.STRING); size = parseBytes((String) v.unwrapped(), v.origin(), path); } return size; } @Override public ConfigMemorySize getMemorySize(String path) { return ConfigMemorySize.ofBytes(getBytes(path)); } @Deprecated @Override public Long getMilliseconds(String path) { return getDuration(path, TimeUnit.MILLISECONDS); } @Deprecated @Override public Long getNanoseconds(String path) { return getDuration(path, TimeUnit.NANOSECONDS); } @Override public long getDuration(String path, TimeUnit unit) { ConfigValue v = find(path, ConfigValueType.STRING); long result = unit.convert( parseDuration((String) v.unwrapped(), v.origin(), path), TimeUnit.NANOSECONDS); return result; } @Override public Duration getDuration(String path) { ConfigValue v = find(path, ConfigValueType.STRING); long nanos = parseDuration((String) v.unwrapped(), v.origin(), path); return Duration.ofNanos(nanos); } @SuppressWarnings("unchecked") private List getHomogeneousUnwrappedList(String path, ConfigValueType expected) { List l = new ArrayList(); List list = getList(path); for (ConfigValue cv : list) { // variance would be nice, but stupid cast will do AbstractConfigValue v = (AbstractConfigValue) cv; if (expected != null) { v = DefaultTransformer.transform(v, expected); } if (v.valueType() != expected) throw new ConfigException.WrongType(v.origin(), path, "list of " + expected.name(), "list of " + v.valueType().name()); l.add((T) v.unwrapped()); } return l; } @Override public List getBooleanList(String path) { return getHomogeneousUnwrappedList(path, ConfigValueType.BOOLEAN); } @Override public List getNumberList(String path) { return getHomogeneousUnwrappedList(path, ConfigValueType.NUMBER); } @Override public List getIntList(String path) { List l = new ArrayList(); List numbers = getHomogeneousWrappedList(path, ConfigValueType.NUMBER); for (AbstractConfigValue v : numbers) { l.add(((ConfigNumber) v).intValueRangeChecked(path)); } return l; } @Override public List getLongList(String path) { List l = new ArrayList(); List numbers = getNumberList(path); for (Number n : numbers) { l.add(n.longValue()); } return l; } @Override public List getDoubleList(String path) { List l = new ArrayList(); List numbers = getNumberList(path); for (Number n : numbers) { l.add(n.doubleValue()); } return l; } @Override public List getStringList(String path) { return getHomogeneousUnwrappedList(path, ConfigValueType.STRING); } @Override public > List getEnumList(Class enumClass, String path) { List enumNames = getHomogeneousWrappedList(path, ConfigValueType.STRING); List enumList = new ArrayList(); for (ConfigString enumName : enumNames) { enumList.add(getEnumValue(path, enumClass, enumName)); } return enumList; } private > T getEnumValue(String path, Class enumClass, ConfigValue enumConfigValue) { String enumName = (String) enumConfigValue.unwrapped(); try { return Enum.valueOf(enumClass, enumName); } catch (IllegalArgumentException e) { List enumNames = new ArrayList(); Enum[] enumConstants = enumClass.getEnumConstants(); if (enumConstants != null) { for (Enum enumConstant : enumConstants) { enumNames.add(enumConstant.name()); } } throw new ConfigException.BadValue( enumConfigValue.origin(), path, String.format("The enum class %s has no constant of the name '%s' (should be one of %s.)", enumClass.getSimpleName(), enumName, enumNames)); } } @SuppressWarnings("unchecked") private List getHomogeneousWrappedList( String path, ConfigValueType expected) { List l = new ArrayList(); List list = getList(path); for (ConfigValue cv : list) { // variance would be nice, but stupid cast will do AbstractConfigValue v = (AbstractConfigValue) cv; if (expected != null) { v = DefaultTransformer.transform(v, expected); } if (v.valueType() != expected) throw new ConfigException.WrongType(v.origin(), path, "list of " + expected.name(), "list of " + v.valueType().name()); l.add((T) v); } return l; } @Override public List getObjectList(String path) { return getHomogeneousWrappedList(path, ConfigValueType.OBJECT); } @Override public List getConfigList(String path) { List objects = getObjectList(path); List l = new ArrayList(); for (ConfigObject o : objects) { l.add(o.toConfig()); } return l; } @Override public List getAnyRefList(String path) { List l = new ArrayList(); List list = getList(path); for (ConfigValue v : list) { l.add(v.unwrapped()); } return l; } @Override public List getBytesList(String path) { List l = new ArrayList(); List list = getList(path); for (ConfigValue v : list) { if (v.valueType() == ConfigValueType.NUMBER) { l.add(((Number) v.unwrapped()).longValue()); } else if (v.valueType() == ConfigValueType.STRING) { String s = (String) v.unwrapped(); Long n = parseBytes(s, v.origin(), path); l.add(n); } else { throw new ConfigException.WrongType(v.origin(), path, "memory size string or number of bytes", v.valueType() .name()); } } return l; } @Override public List getMemorySizeList(String path) { List list = getBytesList(path); List builder = new ArrayList(); for (Long v : list) { builder.add(ConfigMemorySize.ofBytes(v)); } return builder; } @Override public List getDurationList(String path, TimeUnit unit) { List l = new ArrayList(); List list = getList(path); for (ConfigValue v : list) { if (v.valueType() == ConfigValueType.NUMBER) { Long n = unit.convert( ((Number) v.unwrapped()).longValue(), TimeUnit.MILLISECONDS); l.add(n); } else if (v.valueType() == ConfigValueType.STRING) { String s = (String) v.unwrapped(); Long n = unit.convert( parseDuration(s, v.origin(), path), TimeUnit.NANOSECONDS); l.add(n); } else { throw new ConfigException.WrongType(v.origin(), path, "duration string or number of milliseconds", v.valueType().name()); } } return l; } @Override public List getDurationList(String path) { List l = getDurationList(path, TimeUnit.NANOSECONDS); List builder = new ArrayList(l.size()); for (Long value : l) { builder.add(Duration.ofNanos(value)); } return builder; } @Deprecated @Override public List getMillisecondsList(String path) { return getDurationList(path, TimeUnit.MILLISECONDS); } @Deprecated @Override public List getNanosecondsList(String path) { return getDurationList(path, TimeUnit.NANOSECONDS); } @Override public AbstractConfigObject toFallbackValue() { return object; } @Override public SimpleConfig withFallback(ConfigMergeable other) { // this can return "this" if the withFallback doesn't need a new // ConfigObject return object.withFallback(other).toConfig(); } @Override public final boolean equals(Object other) { if (other instanceof SimpleConfig) { return object.equals(((SimpleConfig) other).object); } else { return false; } } @Override public final int hashCode() { // we do the "41*" just so our hash code won't match that of the // underlying object. there's no real reason it can't match, but // making it not match might catch some kinds of bug. return 41 * object.hashCode(); } @Override public String toString() { return "Config(" + object.toString() + ")"; } private static String getUnits(String s) { int i = s.length() - 1; while (i >= 0) { char c = s.charAt(i); if (!Character.isLetter(c)) break; i -= 1; } return s.substring(i + 1); } /** * Parses a duration string. If no units are specified in the string, it is * assumed to be in milliseconds. The returned duration is in nanoseconds. * The purpose of this function is to implement the duration-related methods * in the ConfigObject interface. * * @param input * the string to parse * @param originForException * origin of the value being parsed * @param pathForException * path to include in exceptions * @return duration in nanoseconds * @throws ConfigException * if string is invalid */ public static long parseDuration(String input, ConfigOrigin originForException, String pathForException) { String s = ConfigImplUtil.unicodeTrim(input); String originalUnitString = getUnits(s); String unitString = originalUnitString; String numberString = ConfigImplUtil.unicodeTrim(s.substring(0, s.length() - unitString.length())); TimeUnit units = null; // this would be caught later anyway, but the error message // is more helpful if we check it here. if (numberString.length() == 0) throw new ConfigException.BadValue(originForException, pathForException, "No number in duration value '" + input + "'"); if (unitString.length() > 2 && !unitString.endsWith("s")) unitString = unitString + "s"; // note that this is deliberately case-sensitive if (unitString.equals("") || unitString.equals("ms") || unitString.equals("millis") || unitString.equals("milliseconds")) { units = TimeUnit.MILLISECONDS; } else if (unitString.equals("us") || unitString.equals("micros") || unitString.equals("microseconds")) { units = TimeUnit.MICROSECONDS; } else if (unitString.equals("ns") || unitString.equals("nanos") || unitString.equals("nanoseconds")) { units = TimeUnit.NANOSECONDS; } else if (unitString.equals("d") || unitString.equals("days")) { units = TimeUnit.DAYS; } else if (unitString.equals("h") || unitString.equals("hours")) { units = TimeUnit.HOURS; } else if (unitString.equals("s") || unitString.equals("seconds")) { units = TimeUnit.SECONDS; } else if (unitString.equals("m") || unitString.equals("minutes")) { units = TimeUnit.MINUTES; } else { throw new ConfigException.BadValue(originForException, pathForException, "Could not parse time unit '" + originalUnitString + "' (try ns, us, ms, s, m, h, d)"); } try { // if the string is purely digits, parse as an integer to avoid // possible precision loss; // otherwise as a double. if (numberString.matches("[+-]?[0-9]+")) { return units.toNanos(Long.parseLong(numberString)); } else { long nanosInUnit = units.toNanos(1); return (long) (Double.parseDouble(numberString) * nanosInUnit); } } catch (NumberFormatException e) { throw new ConfigException.BadValue(originForException, pathForException, "Could not parse duration number '" + numberString + "'"); } } private static enum MemoryUnit { BYTES("", 1024, 0), KILOBYTES("kilo", 1000, 1), MEGABYTES("mega", 1000, 2), GIGABYTES("giga", 1000, 3), TERABYTES("tera", 1000, 4), PETABYTES("peta", 1000, 5), EXABYTES("exa", 1000, 6), ZETTABYTES("zetta", 1000, 7), YOTTABYTES("yotta", 1000, 8), KIBIBYTES("kibi", 1024, 1), MEBIBYTES("mebi", 1024, 2), GIBIBYTES("gibi", 1024, 3), TEBIBYTES("tebi", 1024, 4), PEBIBYTES("pebi", 1024, 5), EXBIBYTES("exbi", 1024, 6), ZEBIBYTES("zebi", 1024, 7), YOBIBYTES("yobi", 1024, 8); final String prefix; final int powerOf; final int power; final BigInteger bytes; MemoryUnit(String prefix, int powerOf, int power) { this.prefix = prefix; this.powerOf = powerOf; this.power = power; this.bytes = BigInteger.valueOf(powerOf).pow(power); } private static Map makeUnitsMap() { Map map = new HashMap(); for (MemoryUnit unit : MemoryUnit.values()) { map.put(unit.prefix + "byte", unit); map.put(unit.prefix + "bytes", unit); if (unit.prefix.length() == 0) { map.put("b", unit); map.put("B", unit); map.put("", unit); // no unit specified means bytes } else { String first = unit.prefix.substring(0, 1); String firstUpper = first.toUpperCase(); if (unit.powerOf == 1024) { map.put(first, unit); // 512m map.put(firstUpper, unit); // 512M map.put(firstUpper + "i", unit); // 512Mi map.put(firstUpper + "iB", unit); // 512MiB } else if (unit.powerOf == 1000) { if (unit.power == 1) { map.put(first + "B", unit); // 512kB } else { map.put(firstUpper + "B", unit); // 512MB } } else { throw new RuntimeException("broken MemoryUnit enum"); } } } return map; } private static Map unitsMap = makeUnitsMap(); static MemoryUnit parseUnit(String unit) { return unitsMap.get(unit); } } /** * Parses a size-in-bytes string. If no units are specified in the string, * it is assumed to be in bytes. The returned value is in bytes. The purpose * of this function is to implement the size-in-bytes-related methods in the * Config interface. * * @param input * the string to parse * @param originForException * origin of the value being parsed * @param pathForException * path to include in exceptions * @return size in bytes * @throws ConfigException * if string is invalid */ public static long parseBytes(String input, ConfigOrigin originForException, String pathForException) { String s = ConfigImplUtil.unicodeTrim(input); String unitString = getUnits(s); String numberString = ConfigImplUtil.unicodeTrim(s.substring(0, s.length() - unitString.length())); // this would be caught later anyway, but the error message // is more helpful if we check it here. if (numberString.length() == 0) throw new ConfigException.BadValue(originForException, pathForException, "No number in size-in-bytes value '" + input + "'"); MemoryUnit units = MemoryUnit.parseUnit(unitString); if (units == null) { throw new ConfigException.BadValue(originForException, pathForException, "Could not parse size-in-bytes unit '" + unitString + "' (try k, K, kB, KiB, kilobytes, kibibytes)"); } try { BigInteger result; // if the string is purely digits, parse as an integer to avoid // possible precision loss; otherwise as a double. if (numberString.matches("[0-9]+")) { result = units.bytes.multiply(new BigInteger(numberString)); } else { BigDecimal resultDecimal = (new BigDecimal(units.bytes)).multiply(new BigDecimal(numberString)); result = resultDecimal.toBigInteger(); } if (result.bitLength() < 64) return result.longValue(); else throw new ConfigException.BadValue(originForException, pathForException, "size-in-bytes value is out of range for a 64-bit long: '" + input + "'"); } catch (NumberFormatException e) { throw new ConfigException.BadValue(originForException, pathForException, "Could not parse size-in-bytes number '" + numberString + "'"); } } private AbstractConfigValue peekPath(Path path) { return root().peekPath(path); } private static void addProblem(List accumulator, Path path, ConfigOrigin origin, String problem) { accumulator.add(new ConfigException.ValidationProblem(path.render(), origin, problem)); } private static String getDesc(ConfigValueType type) { return type.name().toLowerCase(); } private static String getDesc(ConfigValue refValue) { if (refValue instanceof AbstractConfigObject) { AbstractConfigObject obj = (AbstractConfigObject) refValue; if (!obj.isEmpty()) return "object with keys " + obj.keySet(); else return getDesc(refValue.valueType()); } else { return getDesc(refValue.valueType()); } } private static void addMissing(List accumulator, String refDesc, Path path, ConfigOrigin origin) { addProblem(accumulator, path, origin, "No setting at '" + path.render() + "', expecting: " + refDesc); } private static void addMissing(List accumulator, ConfigValue refValue, Path path, ConfigOrigin origin) { addMissing(accumulator, getDesc(refValue), path, origin); } // JavaBean stuff uses this static void addMissing(List accumulator, ConfigValueType refType, Path path, ConfigOrigin origin) { addMissing(accumulator, getDesc(refType), path, origin); } private static void addWrongType(List accumulator, String refDesc, AbstractConfigValue actual, Path path) { addProblem(accumulator, path, actual.origin(), "Wrong value type at '" + path.render() + "', expecting: " + refDesc + " but got: " + getDesc(actual)); } private static void addWrongType(List accumulator, ConfigValue refValue, AbstractConfigValue actual, Path path) { addWrongType(accumulator, getDesc(refValue), actual, path); } private static void addWrongType(List accumulator, ConfigValueType refType, AbstractConfigValue actual, Path path) { addWrongType(accumulator, getDesc(refType), actual, path); } private static boolean couldBeNull(AbstractConfigValue v) { return DefaultTransformer.transform(v, ConfigValueType.NULL) .valueType() == ConfigValueType.NULL; } private static boolean haveCompatibleTypes(ConfigValue reference, AbstractConfigValue value) { if (couldBeNull((AbstractConfigValue) reference)) { // we allow any setting to be null return true; } else { return haveCompatibleTypes(reference.valueType(), value); } } private static boolean haveCompatibleTypes(ConfigValueType referenceType, AbstractConfigValue value) { if (referenceType == ConfigValueType.NULL || couldBeNull(value)) { // we allow any setting to be null return true; } else if (referenceType == ConfigValueType.OBJECT) { if (value instanceof AbstractConfigObject) { return true; } else { return false; } } else if (referenceType == ConfigValueType.LIST) { // objects may be convertible to lists if they have numeric keys if (value instanceof SimpleConfigList || value instanceof SimpleConfigObject) { return true; } else { return false; } } else if (referenceType == ConfigValueType.STRING) { // assume a string could be gotten as any non-collection type; // allows things like getMilliseconds including domain-specific // interpretations of strings return true; } else if (value instanceof ConfigString) { // assume a string could be gotten as any non-collection type return true; } else { if (referenceType == value.valueType()) { return true; } else { return false; } } } // path is null if we're at the root private static void checkValidObject(Path path, AbstractConfigObject reference, AbstractConfigObject value, List accumulator) { for (Map.Entry entry : reference.entrySet()) { String key = entry.getKey(); Path childPath; if (path != null) childPath = Path.newKey(key).prepend(path); else childPath = Path.newKey(key); AbstractConfigValue v = value.get(key); if (v == null) { addMissing(accumulator, entry.getValue(), childPath, value.origin()); } else { checkValid(childPath, entry.getValue(), v, accumulator); } } } private static void checkListCompatibility(Path path, SimpleConfigList listRef, SimpleConfigList listValue, List accumulator) { if (listRef.isEmpty() || listValue.isEmpty()) { // can't verify type, leave alone } else { AbstractConfigValue refElement = listRef.get(0); for (ConfigValue elem : listValue) { AbstractConfigValue e = (AbstractConfigValue) elem; if (!haveCompatibleTypes(refElement, e)) { addProblem(accumulator, path, e.origin(), "List at '" + path.render() + "' contains wrong value type, expecting list of " + getDesc(refElement) + " but got element of type " + getDesc(e)); // don't add a problem for every last array element break; } } } } // Used by the JavaBean-based validator static void checkValid(Path path, ConfigValueType referenceType, AbstractConfigValue value, List accumulator) { if (haveCompatibleTypes(referenceType, value)) { if (referenceType == ConfigValueType.LIST && value instanceof SimpleConfigObject) { // attempt conversion of indexed object to list AbstractConfigValue listValue = DefaultTransformer.transform(value, ConfigValueType.LIST); if (!(listValue instanceof SimpleConfigList)) addWrongType(accumulator, referenceType, value, path); } } else { addWrongType(accumulator, referenceType, value, path); } } private static void checkValid(Path path, ConfigValue reference, AbstractConfigValue value, List accumulator) { // Unmergeable is supposed to be impossible to encounter in here // because we check for resolve status up front. if (haveCompatibleTypes(reference, value)) { if (reference instanceof AbstractConfigObject && value instanceof AbstractConfigObject) { checkValidObject(path, (AbstractConfigObject) reference, (AbstractConfigObject) value, accumulator); } else if (reference instanceof SimpleConfigList && value instanceof SimpleConfigList) { SimpleConfigList listRef = (SimpleConfigList) reference; SimpleConfigList listValue = (SimpleConfigList) value; checkListCompatibility(path, listRef, listValue, accumulator); } else if (reference instanceof SimpleConfigList && value instanceof SimpleConfigObject) { // attempt conversion of indexed object to list SimpleConfigList listRef = (SimpleConfigList) reference; AbstractConfigValue listValue = DefaultTransformer.transform(value, ConfigValueType.LIST); if (listValue instanceof SimpleConfigList) checkListCompatibility(path, listRef, (SimpleConfigList) listValue, accumulator); else addWrongType(accumulator, reference, value, path); } } else { addWrongType(accumulator, reference, value, path); } } @Override public boolean isResolved() { return root().resolveStatus() == ResolveStatus.RESOLVED; } @Override public void checkValid(Config reference, String... restrictToPaths) { SimpleConfig ref = (SimpleConfig) reference; // unresolved reference config is a bug in the caller of checkValid if (ref.root().resolveStatus() != ResolveStatus.RESOLVED) throw new ConfigException.BugOrBroken( "do not call checkValid() with an unresolved reference config, call Config#resolve(), see Config#resolve() API docs"); // unresolved config under validation is a bug in something, // NotResolved is a more specific subclass of BugOrBroken if (root().resolveStatus() != ResolveStatus.RESOLVED) throw new ConfigException.NotResolved( "need to Config#resolve() each config before using it, see the API docs for Config#resolve()"); // Now we know that both reference and this config are resolved List problems = new ArrayList(); if (restrictToPaths.length == 0) { checkValidObject(null, ref.root(), root(), problems); } else { for (String p : restrictToPaths) { Path path = Path.newPath(p); AbstractConfigValue refValue = ref.peekPath(path); if (refValue != null) { AbstractConfigValue child = peekPath(path); if (child != null) { checkValid(path, refValue, child, problems); } else { addMissing(problems, refValue, path, origin()); } } } } if (!problems.isEmpty()) { throw new ConfigException.ValidationFailed(problems); } } @Override public SimpleConfig withOnlyPath(String pathExpression) { Path path = Path.newPath(pathExpression); return new SimpleConfig(root().withOnlyPath(path)); } @Override public SimpleConfig withoutPath(String pathExpression) { Path path = Path.newPath(pathExpression); return new SimpleConfig(root().withoutPath(path)); } @Override public SimpleConfig withValue(String pathExpression, ConfigValue v) { Path path = Path.newPath(pathExpression); return new SimpleConfig(root().withValue(path, v)); } SimpleConfig atKey(ConfigOrigin origin, String key) { return root().atKey(origin, key); } @Override public SimpleConfig atKey(String key) { return root().atKey(key); } @Override public Config atPath(String path) { return root().atPath(path); } // serialization all goes through SerializedConfigValue private Object writeReplace() throws ObjectStreamException { return new SerializedConfigValue(this); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/SimpleConfigDocument.java000066400000000000000000000043401277147274600304310ustar00rootroot00000000000000package com.typesafe.config.impl; import com.typesafe.config.*; import com.typesafe.config.parser.ConfigDocument; import java.io.StringReader; import java.util.Iterator; final class SimpleConfigDocument implements ConfigDocument { private ConfigNodeRoot configNodeTree; private ConfigParseOptions parseOptions; SimpleConfigDocument(ConfigNodeRoot parsedNode, ConfigParseOptions parseOptions) { configNodeTree = parsedNode; this.parseOptions = parseOptions; } @Override public ConfigDocument withValueText(String path, String newValue) { if (newValue == null) throw new ConfigException.BugOrBroken("null value for " + path + " passed to withValueText"); SimpleConfigOrigin origin = SimpleConfigOrigin.newSimple("single value parsing"); StringReader reader = new StringReader(newValue); Iterator tokens = Tokenizer.tokenize(origin, reader, parseOptions.getSyntax()); AbstractConfigNodeValue parsedValue = ConfigDocumentParser.parseValue(tokens, origin, parseOptions); reader.close(); return new SimpleConfigDocument(configNodeTree.setValue(path, parsedValue, parseOptions.getSyntax()), parseOptions); } @Override public ConfigDocument withValue(String path, ConfigValue newValue) { if (newValue == null) throw new ConfigException.BugOrBroken("null value for " + path + " passed to withValue"); ConfigRenderOptions options = ConfigRenderOptions.defaults(); options = options.setOriginComments(false); return withValueText(path, newValue.render(options).trim()); } @Override public ConfigDocument withoutPath(String path) { return new SimpleConfigDocument(configNodeTree.setValue(path, null, parseOptions.getSyntax()), parseOptions); } @Override public boolean hasPath(String path) { return configNodeTree.hasValue(path); } public String render() { return configNodeTree.render(); } @Override public boolean equals(Object other) { return other instanceof ConfigDocument && render().equals(((ConfigDocument) other).render()); } @Override public int hashCode() { return render().hashCode(); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java000066400000000000000000000334661277147274600276010ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; final class SimpleConfigList extends AbstractConfigValue implements ConfigList, Container, Serializable { private static final long serialVersionUID = 2L; final private List value; final private boolean resolved; SimpleConfigList(ConfigOrigin origin, List value) { this(origin, value, ResolveStatus .fromValues(value)); } SimpleConfigList(ConfigOrigin origin, List value, ResolveStatus status) { super(origin); this.value = value; this.resolved = status == ResolveStatus.RESOLVED; // kind of an expensive debug check (makes this constructor pointless) if (status != ResolveStatus.fromValues(value)) throw new ConfigException.BugOrBroken( "SimpleConfigList created with wrong resolve status: " + this); } @Override public ConfigValueType valueType() { return ConfigValueType.LIST; } @Override public List unwrapped() { List list = new ArrayList(); for (AbstractConfigValue v : value) { list.add(v.unwrapped()); } return list; } @Override ResolveStatus resolveStatus() { return ResolveStatus.fromBoolean(resolved); } @Override public SimpleConfigList replaceChild(AbstractConfigValue child, AbstractConfigValue replacement) { List newList = replaceChildInList(value, child, replacement); if (newList == null) { return null; } else { // we use the constructor flavor that will recompute the resolve // status return new SimpleConfigList(origin(), newList); } } @Override public boolean hasDescendant(AbstractConfigValue descendant) { return hasDescendantInList(value, descendant); } private SimpleConfigList modify(NoExceptionsModifier modifier, ResolveStatus newResolveStatus) { try { return modifyMayThrow(modifier, newResolveStatus); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new ConfigException.BugOrBroken("unexpected checked exception", e); } } private SimpleConfigList modifyMayThrow(Modifier modifier, ResolveStatus newResolveStatus) throws Exception { // lazy-create for optimization List changed = null; int i = 0; for (AbstractConfigValue v : value) { AbstractConfigValue modified = modifier.modifyChildMayThrow(null /* key */, v); // lazy-create the new list if required if (changed == null && modified != v) { changed = new ArrayList(); for (int j = 0; j < i; ++j) { changed.add(value.get(j)); } } // once the new list is created, all elements // have to go in it. if modifyChild returned // null, we drop that element. if (changed != null && modified != null) { changed.add(modified); } i += 1; } if (changed != null) { if (newResolveStatus != null) { return new SimpleConfigList(origin(), changed, newResolveStatus); } else { return new SimpleConfigList(origin(), changed); } } else { return this; } } private static class ResolveModifier implements Modifier { ResolveContext context; final ResolveSource source; ResolveModifier(ResolveContext context, ResolveSource source) { this.context = context; this.source = source; } @Override public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v) throws NotPossibleToResolve { ResolveResult result = context.resolve(v, source); context = result.context; return result.value; } } @Override ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve { if (resolved) return ResolveResult.make(context, this); if (context.isRestrictedToChild()) { // if a list restricts to a child path, then it has no child paths, // so nothing to do. return ResolveResult.make(context, this); } else { try { ResolveModifier modifier = new ResolveModifier(context, source.pushParent(this)); SimpleConfigList value = modifyMayThrow(modifier, context.options().getAllowUnresolved() ? null : ResolveStatus.RESOLVED); return ResolveResult.make(modifier.context, value); } catch (NotPossibleToResolve e) { throw e; } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new ConfigException.BugOrBroken("unexpected checked exception", e); } } } @Override SimpleConfigList relativized(final Path prefix) { return modify(new NoExceptionsModifier() { @Override public AbstractConfigValue modifyChild(String key, AbstractConfigValue v) { return v.relativized(prefix); } }, resolveStatus()); } @Override protected boolean canEqual(Object other) { return other instanceof SimpleConfigList; } @Override public boolean equals(Object other) { // note that "origin" is deliberately NOT part of equality if (other instanceof SimpleConfigList) { // optimization to avoid unwrapped() for two ConfigList return canEqual(other) && (value == ((SimpleConfigList) other).value || value.equals(((SimpleConfigList) other).value)); } else { return false; } } @Override public int hashCode() { // note that "origin" is deliberately NOT part of equality return value.hashCode(); } @Override protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { if (value.isEmpty()) { sb.append("[]"); } else { sb.append("["); if (options.getFormatted()) sb.append('\n'); for (AbstractConfigValue v : value) { if (options.getOriginComments()) { String[] lines = v.origin().description().split("\n"); for (String l : lines) { indent(sb, indent + 1, options); sb.append('#'); if (!l.isEmpty()) sb.append(' '); sb.append(l); sb.append("\n"); } } if (options.getComments()) { for (String comment : v.origin().comments()) { indent(sb, indent + 1, options); sb.append("# "); sb.append(comment); sb.append("\n"); } } indent(sb, indent + 1, options); v.render(sb, indent + 1, atRoot, options); sb.append(","); if (options.getFormatted()) sb.append('\n'); } sb.setLength(sb.length() - 1); // chop or newline if (options.getFormatted()) { sb.setLength(sb.length() - 1); // also chop comma sb.append('\n'); indent(sb, indent, options); } sb.append("]"); } } @Override public boolean contains(Object o) { return value.contains(o); } @Override public boolean containsAll(Collection c) { return value.containsAll(c); } @Override public AbstractConfigValue get(int index) { return value.get(index); } @Override public int indexOf(Object o) { return value.indexOf(o); } @Override public boolean isEmpty() { return value.isEmpty(); } @Override public Iterator iterator() { final Iterator i = value.iterator(); return new Iterator() { @Override public boolean hasNext() { return i.hasNext(); } @Override public ConfigValue next() { return i.next(); } @Override public void remove() { throw weAreImmutable("iterator().remove"); } }; } @Override public int lastIndexOf(Object o) { return value.lastIndexOf(o); } private static ListIterator wrapListIterator( final ListIterator i) { return new ListIterator() { @Override public boolean hasNext() { return i.hasNext(); } @Override public ConfigValue next() { return i.next(); } @Override public void remove() { throw weAreImmutable("listIterator().remove"); } @Override public void add(ConfigValue arg0) { throw weAreImmutable("listIterator().add"); } @Override public boolean hasPrevious() { return i.hasPrevious(); } @Override public int nextIndex() { return i.nextIndex(); } @Override public ConfigValue previous() { return i.previous(); } @Override public int previousIndex() { return i.previousIndex(); } @Override public void set(ConfigValue arg0) { throw weAreImmutable("listIterator().set"); } }; } @Override public ListIterator listIterator() { return wrapListIterator(value.listIterator()); } @Override public ListIterator listIterator(int index) { return wrapListIterator(value.listIterator(index)); } @Override public int size() { return value.size(); } @Override public List subList(int fromIndex, int toIndex) { List list = new ArrayList(); // yay bloat caused by lack of type variance for (AbstractConfigValue v : value.subList(fromIndex, toIndex)) { list.add(v); } return list; } @Override public Object[] toArray() { return value.toArray(); } @Override public T[] toArray(T[] a) { return value.toArray(a); } private static UnsupportedOperationException weAreImmutable(String method) { return new UnsupportedOperationException( "ConfigList is immutable, you can't call List.'" + method + "'"); } @Override public boolean add(ConfigValue e) { throw weAreImmutable("add"); } @Override public void add(int index, ConfigValue element) { throw weAreImmutable("add"); } @Override public boolean addAll(Collection c) { throw weAreImmutable("addAll"); } @Override public boolean addAll(int index, Collection c) { throw weAreImmutable("addAll"); } @Override public void clear() { throw weAreImmutable("clear"); } @Override public boolean remove(Object o) { throw weAreImmutable("remove"); } @Override public ConfigValue remove(int index) { throw weAreImmutable("remove"); } @Override public boolean removeAll(Collection c) { throw weAreImmutable("removeAll"); } @Override public boolean retainAll(Collection c) { throw weAreImmutable("retainAll"); } @Override public ConfigValue set(int index, ConfigValue element) { throw weAreImmutable("set"); } @Override protected SimpleConfigList newCopy(ConfigOrigin newOrigin) { return new SimpleConfigList(newOrigin, value); } final SimpleConfigList concatenate(SimpleConfigList other) { ConfigOrigin combinedOrigin = SimpleConfigOrigin.mergeOrigins(origin(), other.origin()); List combined = new ArrayList(value.size() + other.value.size()); combined.addAll(value); combined.addAll(other.value); return new SimpleConfigList(combinedOrigin, combined); } // serialization all goes through SerializedConfigValue private Object writeReplace() throws ObjectStreamException { return new SerializedConfigValue(this); } @Override public SimpleConfigList withOrigin(ConfigOrigin origin) { return (SimpleConfigList) super.withOrigin(origin); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java000066400000000000000000000566621277147274600300770ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValue; final class SimpleConfigObject extends AbstractConfigObject implements Serializable { private static final long serialVersionUID = 2L; // this map should never be modified - assume immutable final private Map value; final private boolean resolved; final private boolean ignoresFallbacks; SimpleConfigObject(ConfigOrigin origin, Map value, ResolveStatus status, boolean ignoresFallbacks) { super(origin); if (value == null) throw new ConfigException.BugOrBroken( "creating config object with null map"); this.value = value; this.resolved = status == ResolveStatus.RESOLVED; this.ignoresFallbacks = ignoresFallbacks; // Kind of an expensive debug check. Comment out? if (status != ResolveStatus.fromValues(value.values())) throw new ConfigException.BugOrBroken("Wrong resolved status on " + this); } SimpleConfigObject(ConfigOrigin origin, Map value) { this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */); } @Override public SimpleConfigObject withOnlyKey(String key) { return withOnlyPath(Path.newKey(key)); } @Override public SimpleConfigObject withoutKey(String key) { return withoutPath(Path.newKey(key)); } // gets the object with only the path if the path // exists, otherwise null if it doesn't. this ensures // that if we have { a : { b : 42 } } and do // withOnlyPath("a.b.c") that we don't keep an empty // "a" object. @Override protected SimpleConfigObject withOnlyPathOrNull(Path path) { String key = path.first(); Path next = path.remainder(); AbstractConfigValue v = value.get(key); if (next != null) { if (v != null && (v instanceof AbstractConfigObject)) { v = ((AbstractConfigObject) v).withOnlyPathOrNull(next); } else { // if the path has more elements but we don't have an object, // then the rest of the path does not exist. v = null; } } if (v == null) { return null; } else { return new SimpleConfigObject(origin(), Collections.singletonMap(key, v), v.resolveStatus(), ignoresFallbacks); } } @Override SimpleConfigObject withOnlyPath(Path path) { SimpleConfigObject o = withOnlyPathOrNull(path); if (o == null) { return new SimpleConfigObject(origin(), Collections. emptyMap(), ResolveStatus.RESOLVED, ignoresFallbacks); } else { return o; } } @Override SimpleConfigObject withoutPath(Path path) { String key = path.first(); Path next = path.remainder(); AbstractConfigValue v = value.get(key); if (v != null && next != null && v instanceof AbstractConfigObject) { v = ((AbstractConfigObject) v).withoutPath(next); Map updated = new HashMap( value); updated.put(key, v); return new SimpleConfigObject(origin(), updated, ResolveStatus.fromValues(updated .values()), ignoresFallbacks); } else if (next != null || v == null) { // can't descend, nothing to remove return this; } else { Map smaller = new HashMap( value.size() - 1); for (Map.Entry old : value.entrySet()) { if (!old.getKey().equals(key)) smaller.put(old.getKey(), old.getValue()); } return new SimpleConfigObject(origin(), smaller, ResolveStatus.fromValues(smaller .values()), ignoresFallbacks); } } @Override public SimpleConfigObject withValue(String key, ConfigValue v) { if (v == null) throw new ConfigException.BugOrBroken( "Trying to store null ConfigValue in a ConfigObject"); Map newMap; if (value.isEmpty()) { newMap = Collections.singletonMap(key, (AbstractConfigValue) v); } else { newMap = new HashMap(value); newMap.put(key, (AbstractConfigValue) v); } return new SimpleConfigObject(origin(), newMap, ResolveStatus.fromValues(newMap.values()), ignoresFallbacks); } @Override SimpleConfigObject withValue(Path path, ConfigValue v) { String key = path.first(); Path next = path.remainder(); if (next == null) { return withValue(key, v); } else { AbstractConfigValue child = value.get(key); if (child != null && child instanceof AbstractConfigObject) { // if we have an object, add to it return withValue(key, ((AbstractConfigObject) child).withValue(next, v)); } else { // as soon as we have a non-object, replace it entirely SimpleConfig subtree = ((AbstractConfigValue) v).atPath( SimpleConfigOrigin.newSimple("withValue(" + next.render() + ")"), next); return withValue(key, subtree.root()); } } } @Override protected AbstractConfigValue attemptPeekWithPartialResolve(String key) { return value.get(key); } private SimpleConfigObject newCopy(ResolveStatus newStatus, ConfigOrigin newOrigin, boolean newIgnoresFallbacks) { return new SimpleConfigObject(newOrigin, value, newStatus, newIgnoresFallbacks); } @Override protected SimpleConfigObject newCopy(ResolveStatus newStatus, ConfigOrigin newOrigin) { return newCopy(newStatus, newOrigin, ignoresFallbacks); } @Override protected SimpleConfigObject withFallbacksIgnored() { if (ignoresFallbacks) return this; else return newCopy(resolveStatus(), origin(), true /* ignoresFallbacks */); } @Override ResolveStatus resolveStatus() { return ResolveStatus.fromBoolean(resolved); } @Override public SimpleConfigObject replaceChild(AbstractConfigValue child, AbstractConfigValue replacement) { HashMap newChildren = new HashMap(value); for (Map.Entry old : newChildren.entrySet()) { if (old.getValue() == child) { if (replacement != null) old.setValue(replacement); else newChildren.remove(old.getKey()); return new SimpleConfigObject(origin(), newChildren, ResolveStatus.fromValues(newChildren.values()), ignoresFallbacks); } } throw new ConfigException.BugOrBroken("SimpleConfigObject.replaceChild did not find " + child + " in " + this); } @Override public boolean hasDescendant(AbstractConfigValue descendant) { for (AbstractConfigValue child : value.values()) { if (child == descendant) return true; } // now do the expensive search for (AbstractConfigValue child : value.values()) { if (child instanceof Container && ((Container) child).hasDescendant(descendant)) return true; } return false; } @Override protected boolean ignoresFallbacks() { return ignoresFallbacks; } @Override public Map unwrapped() { Map m = new HashMap(); for (Map.Entry e : value.entrySet()) { m.put(e.getKey(), e.getValue().unwrapped()); } return m; } @Override protected SimpleConfigObject mergedWithObject(AbstractConfigObject abstractFallback) { requireNotIgnoringFallbacks(); if (!(abstractFallback instanceof SimpleConfigObject)) { throw new ConfigException.BugOrBroken( "should not be reached (merging non-SimpleConfigObject)"); } SimpleConfigObject fallback = (SimpleConfigObject) abstractFallback; boolean changed = false; boolean allResolved = true; Map merged = new HashMap(); Set allKeys = new HashSet(); allKeys.addAll(this.keySet()); allKeys.addAll(fallback.keySet()); for (String key : allKeys) { AbstractConfigValue first = this.value.get(key); AbstractConfigValue second = fallback.value.get(key); AbstractConfigValue kept; if (first == null) kept = second; else if (second == null) kept = first; else kept = first.withFallback(second); merged.put(key, kept); if (first != kept) changed = true; if (kept.resolveStatus() == ResolveStatus.UNRESOLVED) allResolved = false; } ResolveStatus newResolveStatus = ResolveStatus.fromBoolean(allResolved); boolean newIgnoresFallbacks = fallback.ignoresFallbacks(); if (changed) return new SimpleConfigObject(mergeOrigins(this, fallback), merged, newResolveStatus, newIgnoresFallbacks); else if (newResolveStatus != resolveStatus() || newIgnoresFallbacks != ignoresFallbacks()) return newCopy(newResolveStatus, origin(), newIgnoresFallbacks); else return this; } private SimpleConfigObject modify(NoExceptionsModifier modifier) { try { return modifyMayThrow(modifier); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new ConfigException.BugOrBroken("unexpected checked exception", e); } } private SimpleConfigObject modifyMayThrow(Modifier modifier) throws Exception { Map changes = null; for (String k : keySet()) { AbstractConfigValue v = value.get(k); // "modified" may be null, which means remove the child; // to do that we put null in the "changes" map. AbstractConfigValue modified = modifier.modifyChildMayThrow(k, v); if (modified != v) { if (changes == null) changes = new HashMap(); changes.put(k, modified); } } if (changes == null) { return this; } else { Map modified = new HashMap(); boolean sawUnresolved = false; for (String k : keySet()) { if (changes.containsKey(k)) { AbstractConfigValue newValue = changes.get(k); if (newValue != null) { modified.put(k, newValue); if (newValue.resolveStatus() == ResolveStatus.UNRESOLVED) sawUnresolved = true; } else { // remove this child; don't put it in the new map. } } else { AbstractConfigValue newValue = value.get(k); modified.put(k, newValue); if (newValue.resolveStatus() == ResolveStatus.UNRESOLVED) sawUnresolved = true; } } return new SimpleConfigObject(origin(), modified, sawUnresolved ? ResolveStatus.UNRESOLVED : ResolveStatus.RESOLVED, ignoresFallbacks()); } } private static final class ResolveModifier implements Modifier { final Path originalRestrict; ResolveContext context; final ResolveSource source; ResolveModifier(ResolveContext context, ResolveSource source) { this.context = context; this.source = source; originalRestrict = context.restrictToChild(); } @Override public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v) throws NotPossibleToResolve { if (context.isRestrictedToChild()) { if (key.equals(context.restrictToChild().first())) { Path remainder = context.restrictToChild().remainder(); if (remainder != null) { ResolveResult result = context.restrict(remainder).resolve(v, source); context = result.context.unrestricted().restrict(originalRestrict); return result.value; } else { // we don't want to resolve the leaf child. return v; } } else { // not in the restrictToChild path return v; } } else { // no restrictToChild, resolve everything ResolveResult result = context.unrestricted().resolve(v, source); context = result.context.unrestricted().restrict(originalRestrict); return result.value; } } } @Override ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) throws NotPossibleToResolve { if (resolveStatus() == ResolveStatus.RESOLVED) return ResolveResult.make(context, this); final ResolveSource sourceWithParent = source.pushParent(this); try { ResolveModifier modifier = new ResolveModifier(context, sourceWithParent); AbstractConfigValue value = modifyMayThrow(modifier); return ResolveResult.make(modifier.context, value).asObjectResult(); } catch (NotPossibleToResolve e) { throw e; } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new ConfigException.BugOrBroken("unexpected checked exception", e); } } @Override SimpleConfigObject relativized(final Path prefix) { return modify(new NoExceptionsModifier() { @Override public AbstractConfigValue modifyChild(String key, AbstractConfigValue v) { return v.relativized(prefix); } }); } // this is only Serializable to chill out a findbugs warning static final private class RenderComparator implements java.util.Comparator, Serializable { private static final long serialVersionUID = 1L; private static boolean isAllDigits(String s) { int length = s.length(); // empty string doesn't count as a number if (length == 0) return false; for (int i = 0; i < length; ++i) { char c = s.charAt(i); if (Character.isDigit(c)) continue; else return false; } return true; } // This is supposed to sort numbers before strings, // and sort the numbers numerically. The point is // to make objects which are really list-like // (numeric indices) appear in order. @Override public int compare(String a, String b) { boolean aDigits = isAllDigits(a); boolean bDigits = isAllDigits(b); if (aDigits && bDigits) { return Integer.compare(Integer.parseInt(a), Integer.parseInt(b)); } else if (aDigits) { return -1; } else if (bDigits) { return 1; } else { return a.compareTo(b); } } } @Override protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { if (isEmpty()) { sb.append("{}"); } else { boolean outerBraces = options.getJson() || !atRoot; int innerIndent; if (outerBraces) { innerIndent = indent + 1; sb.append("{"); if (options.getFormatted()) sb.append('\n'); } else { innerIndent = indent; } int separatorCount = 0; String[] keys = keySet().toArray(new String[size()]); Arrays.sort(keys, new RenderComparator()); for (String k : keys) { AbstractConfigValue v; v = value.get(k); if (options.getOriginComments()) { String[] lines = v.origin().description().split("\n"); for (String l : lines) { indent(sb, indent + 1, options); sb.append('#'); if (!l.isEmpty()) sb.append(' '); sb.append(l); sb.append("\n"); } } if (options.getComments()) { for (String comment : v.origin().comments()) { indent(sb, innerIndent, options); sb.append("#"); if (!comment.startsWith(" ")) sb.append(' '); sb.append(comment); sb.append("\n"); } } indent(sb, innerIndent, options); v.render(sb, innerIndent, false /* atRoot */, k, options); if (options.getFormatted()) { if (options.getJson()) { sb.append(","); separatorCount = 2; } else { separatorCount = 1; } sb.append('\n'); } else { sb.append(","); separatorCount = 1; } } // chop last commas/newlines sb.setLength(sb.length() - separatorCount); if (outerBraces) { if (options.getFormatted()) { sb.append('\n'); // put a newline back if (outerBraces) indent(sb, indent, options); } sb.append("}"); } } if (atRoot && options.getFormatted()) sb.append('\n'); } @Override public AbstractConfigValue get(Object key) { return value.get(key); } private static boolean mapEquals(Map a, Map b) { if (a == b) return true; Set aKeys = a.keySet(); Set bKeys = b.keySet(); if (!aKeys.equals(bKeys)) return false; for (String key : aKeys) { if (!a.get(key).equals(b.get(key))) return false; } return true; } private static int mapHash(Map m) { // the keys have to be sorted, otherwise we could be equal // to another map but have a different hashcode. List keys = new ArrayList(); keys.addAll(m.keySet()); Collections.sort(keys); int valuesHash = 0; for (String k : keys) { valuesHash += m.get(k).hashCode(); } return 41 * (41 + keys.hashCode()) + valuesHash; } @Override protected boolean canEqual(Object other) { return other instanceof ConfigObject; } @Override public boolean equals(Object other) { // note that "origin" is deliberately NOT part of equality. // neither are other "extras" like ignoresFallbacks or resolve status. if (other instanceof ConfigObject) { // optimization to avoid unwrapped() for two ConfigObject, // which is what AbstractConfigValue does. return canEqual(other) && mapEquals(this, ((ConfigObject) other)); } else { return false; } } @Override public int hashCode() { // note that "origin" is deliberately NOT part of equality // neither are other "extras" like ignoresFallbacks or resolve status. return mapHash(this); } @Override public boolean containsKey(Object key) { return value.containsKey(key); } @Override public Set keySet() { return value.keySet(); } @Override public boolean containsValue(Object v) { return value.containsValue(v); } @Override public Set> entrySet() { // total bloat just to work around lack of type variance HashSet> entries = new HashSet>(); for (Map.Entry e : value.entrySet()) { entries.add(new AbstractMap.SimpleImmutableEntry( e.getKey(), e .getValue())); } return entries; } @Override public boolean isEmpty() { return value.isEmpty(); } @Override public int size() { return value.size(); } @Override public Collection values() { return new HashSet(value.values()); } final private static String EMPTY_NAME = "empty config"; final private static SimpleConfigObject emptyInstance = empty(SimpleConfigOrigin .newSimple(EMPTY_NAME)); final static SimpleConfigObject empty() { return emptyInstance; } final static SimpleConfigObject empty(ConfigOrigin origin) { if (origin == null) return empty(); else return new SimpleConfigObject(origin, Collections. emptyMap()); } final static SimpleConfigObject emptyMissing(ConfigOrigin baseOrigin) { return new SimpleConfigObject(SimpleConfigOrigin.newSimple( baseOrigin.description() + " (not found)"), Collections. emptyMap()); } // serialization all goes through SerializedConfigValue private Object writeReplace() throws ObjectStreamException { return new SerializedConfigValue(this); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/SimpleConfigOrigin.java000066400000000000000000000537351277147274600301160ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.Iterator; import java.util.List; import java.util.Map; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.impl.SerializedConfigValue.SerializedField; // it would be cleaner to have a class hierarchy for various origin types, // but was hoping this would be enough simpler to be a little messy. eh. final class SimpleConfigOrigin implements ConfigOrigin { final private String description; final private int lineNumber; final private int endLineNumber; final private OriginType originType; final private String urlOrNull; final private String resourceOrNull; final private List commentsOrNull; protected SimpleConfigOrigin(String description, int lineNumber, int endLineNumber, OriginType originType, String urlOrNull, String resourceOrNull, List commentsOrNull) { if (description == null) throw new ConfigException.BugOrBroken("description may not be null"); this.description = description; this.lineNumber = lineNumber; this.endLineNumber = endLineNumber; this.originType = originType; this.urlOrNull = urlOrNull; this.resourceOrNull = resourceOrNull; this.commentsOrNull = commentsOrNull; } static SimpleConfigOrigin newSimple(String description) { return new SimpleConfigOrigin(description, -1, -1, OriginType.GENERIC, null, null, null); } static SimpleConfigOrigin newFile(String filename) { String url; try { url = (new File(filename)).toURI().toURL().toExternalForm(); } catch (MalformedURLException e) { url = null; } return new SimpleConfigOrigin(filename, -1, -1, OriginType.FILE, url, null, null); } static SimpleConfigOrigin newURL(URL url) { String u = url.toExternalForm(); return new SimpleConfigOrigin(u, -1, -1, OriginType.URL, u, null, null); } static SimpleConfigOrigin newResource(String resource, URL url) { String desc; if (url != null) desc = resource + " @ " + url.toExternalForm(); else desc = resource; return new SimpleConfigOrigin(desc, -1, -1, OriginType.RESOURCE, url != null ? url.toExternalForm() : null, resource, null); } static SimpleConfigOrigin newResource(String resource) { return newResource(resource, null); } @Override public SimpleConfigOrigin withLineNumber(int lineNumber) { if (lineNumber == this.lineNumber && lineNumber == this.endLineNumber) { return this; } else { return new SimpleConfigOrigin(this.description, lineNumber, lineNumber, this.originType, this.urlOrNull, this.resourceOrNull, this.commentsOrNull); } } SimpleConfigOrigin addURL(URL url) { return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber, this.originType, url != null ? url.toExternalForm() : null, this.resourceOrNull, this.commentsOrNull); } @Override public SimpleConfigOrigin withComments(List comments) { if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull)) { return this; } else { return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber, this.originType, this.urlOrNull, this.resourceOrNull, comments); } } SimpleConfigOrigin prependComments(List comments) { if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) { return this; } else if (this.commentsOrNull == null) { return withComments(comments); } else { List merged = new ArrayList(comments.size() + this.commentsOrNull.size()); merged.addAll(comments); merged.addAll(this.commentsOrNull); return withComments(merged); } } SimpleConfigOrigin appendComments(List comments) { if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) { return this; } else if (this.commentsOrNull == null) { return withComments(comments); } else { List merged = new ArrayList(comments.size() + this.commentsOrNull.size()); merged.addAll(this.commentsOrNull); merged.addAll(comments); return withComments(merged); } } @Override public String description() { if (lineNumber < 0) { return description; } else if (endLineNumber == lineNumber) { return description + ": " + lineNumber; } else { return description + ": " + lineNumber + "-" + endLineNumber; } } @Override public boolean equals(Object other) { if (other instanceof SimpleConfigOrigin) { SimpleConfigOrigin otherOrigin = (SimpleConfigOrigin) other; return this.description.equals(otherOrigin.description) && this.lineNumber == otherOrigin.lineNumber && this.endLineNumber == otherOrigin.endLineNumber && this.originType == otherOrigin.originType && ConfigImplUtil.equalsHandlingNull(this.urlOrNull, otherOrigin.urlOrNull) && ConfigImplUtil.equalsHandlingNull(this.resourceOrNull, otherOrigin.resourceOrNull); } else { return false; } } @Override public int hashCode() { int h = 41 * (41 + description.hashCode()); h = 41 * (h + lineNumber); h = 41 * (h + endLineNumber); h = 41 * (h + originType.hashCode()); if (urlOrNull != null) h = 41 * (h + urlOrNull.hashCode()); if (resourceOrNull != null) h = 41 * (h + resourceOrNull.hashCode()); return h; } @Override public String toString() { return "ConfigOrigin(" + description + ")"; } @Override public String filename() { if (originType == OriginType.FILE) { return description; } else if (urlOrNull != null) { URL url; try { url = new URL(urlOrNull); } catch (MalformedURLException e) { return null; } if (url.getProtocol().equals("file")) { return url.getFile(); } else { return null; } } else { return null; } } @Override public URL url() { if (urlOrNull == null) { return null; } else { try { return new URL(urlOrNull); } catch (MalformedURLException e) { return null; } } } @Override public String resource() { return resourceOrNull; } @Override public int lineNumber() { return lineNumber; } @Override public List comments() { if (commentsOrNull != null) { return Collections.unmodifiableList(commentsOrNull); } else { return Collections.emptyList(); } } static final String MERGE_OF_PREFIX = "merge of "; private static SimpleConfigOrigin mergeTwo(SimpleConfigOrigin a, SimpleConfigOrigin b) { String mergedDesc; int mergedStartLine; int mergedEndLine; List mergedComments; OriginType mergedType; if (a.originType == b.originType) { mergedType = a.originType; } else { mergedType = OriginType.GENERIC; } // first use the "description" field which has no line numbers // cluttering it. String aDesc = a.description; String bDesc = b.description; if (aDesc.startsWith(MERGE_OF_PREFIX)) aDesc = aDesc.substring(MERGE_OF_PREFIX.length()); if (bDesc.startsWith(MERGE_OF_PREFIX)) bDesc = bDesc.substring(MERGE_OF_PREFIX.length()); if (aDesc.equals(bDesc)) { mergedDesc = aDesc; if (a.lineNumber < 0) mergedStartLine = b.lineNumber; else if (b.lineNumber < 0) mergedStartLine = a.lineNumber; else mergedStartLine = Math.min(a.lineNumber, b.lineNumber); mergedEndLine = Math.max(a.endLineNumber, b.endLineNumber); } else { // this whole merge song-and-dance was intended to avoid this case // whenever possible, but we've lost. Now we have to lose some // structured information and cram into a string. // description() method includes line numbers, so use it instead // of description field. String aFull = a.description(); String bFull = b.description(); if (aFull.startsWith(MERGE_OF_PREFIX)) aFull = aFull.substring(MERGE_OF_PREFIX.length()); if (bFull.startsWith(MERGE_OF_PREFIX)) bFull = bFull.substring(MERGE_OF_PREFIX.length()); mergedDesc = MERGE_OF_PREFIX + aFull + "," + bFull; mergedStartLine = -1; mergedEndLine = -1; } String mergedURL; if (ConfigImplUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull)) { mergedURL = a.urlOrNull; } else { mergedURL = null; } String mergedResource; if (ConfigImplUtil.equalsHandlingNull(a.resourceOrNull, b.resourceOrNull)) { mergedResource = a.resourceOrNull; } else { mergedResource = null; } if (ConfigImplUtil.equalsHandlingNull(a.commentsOrNull, b.commentsOrNull)) { mergedComments = a.commentsOrNull; } else { mergedComments = new ArrayList(); if (a.commentsOrNull != null) mergedComments.addAll(a.commentsOrNull); if (b.commentsOrNull != null) mergedComments.addAll(b.commentsOrNull); } return new SimpleConfigOrigin(mergedDesc, mergedStartLine, mergedEndLine, mergedType, mergedURL, mergedResource, mergedComments); } private static int similarity(SimpleConfigOrigin a, SimpleConfigOrigin b) { int count = 0; if (a.originType == b.originType) count += 1; if (a.description.equals(b.description)) { count += 1; // only count these if the description field (which is the file // or resource name) also matches. if (a.lineNumber == b.lineNumber) count += 1; if (a.endLineNumber == b.endLineNumber) count += 1; if (ConfigImplUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull)) count += 1; if (ConfigImplUtil.equalsHandlingNull(a.resourceOrNull, b.resourceOrNull)) count += 1; } return count; } // this picks the best pair to merge, because the pair has the most in // common. we want to merge two lines in the same file rather than something // else with one of the lines; because two lines in the same file can be // better consolidated. private static SimpleConfigOrigin mergeThree(SimpleConfigOrigin a, SimpleConfigOrigin b, SimpleConfigOrigin c) { if (similarity(a, b) >= similarity(b, c)) { return mergeTwo(mergeTwo(a, b), c); } else { return mergeTwo(a, mergeTwo(b, c)); } } static ConfigOrigin mergeOrigins(ConfigOrigin a, ConfigOrigin b) { return mergeTwo((SimpleConfigOrigin) a, (SimpleConfigOrigin) b); } static ConfigOrigin mergeOrigins(List stack) { List origins = new ArrayList(stack.size()); for (AbstractConfigValue v : stack) { origins.add(v.origin()); } return mergeOrigins(origins); } static ConfigOrigin mergeOrigins(Collection stack) { if (stack.isEmpty()) { throw new ConfigException.BugOrBroken("can't merge empty list of origins"); } else if (stack.size() == 1) { return stack.iterator().next(); } else if (stack.size() == 2) { Iterator i = stack.iterator(); return mergeTwo((SimpleConfigOrigin) i.next(), (SimpleConfigOrigin) i.next()); } else { List remaining = new ArrayList(); for (ConfigOrigin o : stack) { remaining.add((SimpleConfigOrigin) o); } while (remaining.size() > 2) { SimpleConfigOrigin c = remaining.get(remaining.size() - 1); remaining.remove(remaining.size() - 1); SimpleConfigOrigin b = remaining.get(remaining.size() - 1); remaining.remove(remaining.size() - 1); SimpleConfigOrigin a = remaining.get(remaining.size() - 1); remaining.remove(remaining.size() - 1); SimpleConfigOrigin merged = mergeThree(a, b, c); remaining.add(merged); } // should be down to either 1 or 2 return mergeOrigins(remaining); } } Map toFields() { Map m = new EnumMap(SerializedField.class); m.put(SerializedField.ORIGIN_DESCRIPTION, description); if (lineNumber >= 0) m.put(SerializedField.ORIGIN_LINE_NUMBER, lineNumber); if (endLineNumber >= 0) m.put(SerializedField.ORIGIN_END_LINE_NUMBER, endLineNumber); m.put(SerializedField.ORIGIN_TYPE, originType.ordinal()); if (urlOrNull != null) m.put(SerializedField.ORIGIN_URL, urlOrNull); if (resourceOrNull != null) m.put(SerializedField.ORIGIN_RESOURCE, resourceOrNull); if (commentsOrNull != null) m.put(SerializedField.ORIGIN_COMMENTS, commentsOrNull); return m; } Map toFieldsDelta(SimpleConfigOrigin baseOrigin) { Map baseFields; if (baseOrigin != null) baseFields = baseOrigin.toFields(); else baseFields = Collections. emptyMap(); return fieldsDelta(baseFields, toFields()); } // Here we're trying to avoid serializing the same info over and over // in the common case that child objects have the same origin fields // as their parent objects. e.g. we don't need to store the source // filename with every single value. static Map fieldsDelta(Map base, Map child) { Map m = new EnumMap(child); for (Map.Entry baseEntry : base.entrySet()) { SerializedField f = baseEntry.getKey(); if (m.containsKey(f) && ConfigImplUtil.equalsHandlingNull(baseEntry.getValue(), m.get(f))) { // if field is unchanged, just remove it so we inherit m.remove(f); } else if (!m.containsKey(f)) { // if field has been removed, we have to add a deletion entry switch (f) { case ORIGIN_DESCRIPTION: throw new ConfigException.BugOrBroken("origin missing description field? " + child); case ORIGIN_LINE_NUMBER: m.put(SerializedField.ORIGIN_LINE_NUMBER, -1); break; case ORIGIN_END_LINE_NUMBER: m.put(SerializedField.ORIGIN_END_LINE_NUMBER, -1); break; case ORIGIN_TYPE: throw new ConfigException.BugOrBroken("should always be an ORIGIN_TYPE field"); case ORIGIN_URL: m.put(SerializedField.ORIGIN_NULL_URL, ""); break; case ORIGIN_RESOURCE: m.put(SerializedField.ORIGIN_NULL_RESOURCE, ""); break; case ORIGIN_COMMENTS: m.put(SerializedField.ORIGIN_NULL_COMMENTS, ""); break; case ORIGIN_NULL_URL: // FALL THRU case ORIGIN_NULL_RESOURCE: // FALL THRU case ORIGIN_NULL_COMMENTS: throw new ConfigException.BugOrBroken("computing delta, base object should not contain " + f + " " + base); case END_MARKER: case ROOT_VALUE: case ROOT_WAS_CONFIG: case UNKNOWN: case VALUE_DATA: case VALUE_ORIGIN: throw new ConfigException.BugOrBroken("should not appear here: " + f); } } else { // field is in base and child, but differs, so leave it } } return m; } static SimpleConfigOrigin fromFields(Map m) throws IOException { // we represent a null origin as one with no fields at all if (m.isEmpty()) return null; String description = (String) m.get(SerializedField.ORIGIN_DESCRIPTION); Integer lineNumber = (Integer) m.get(SerializedField.ORIGIN_LINE_NUMBER); Integer endLineNumber = (Integer) m.get(SerializedField.ORIGIN_END_LINE_NUMBER); Number originTypeOrdinal = (Number) m.get(SerializedField.ORIGIN_TYPE); if (originTypeOrdinal == null) throw new IOException("Missing ORIGIN_TYPE field"); OriginType originType = OriginType.values()[originTypeOrdinal.byteValue()]; String urlOrNull = (String) m.get(SerializedField.ORIGIN_URL); String resourceOrNull = (String) m.get(SerializedField.ORIGIN_RESOURCE); @SuppressWarnings("unchecked") List commentsOrNull = (List) m.get(SerializedField.ORIGIN_COMMENTS); // Older versions did not have a resource field, they stuffed it into // the description. if (originType == OriginType.RESOURCE && resourceOrNull == null) { resourceOrNull = description; } return new SimpleConfigOrigin(description, lineNumber != null ? lineNumber : -1, endLineNumber != null ? endLineNumber : -1, originType, urlOrNull, resourceOrNull, commentsOrNull); } static Map applyFieldsDelta(Map base, Map delta) throws IOException { Map m = new EnumMap(delta); for (Map.Entry baseEntry : base.entrySet()) { SerializedField f = baseEntry.getKey(); if (delta.containsKey(f)) { // delta overrides when keys are in both // "m" should already contain the right thing } else { // base has the key and delta does not. // we inherit from base unless a "NULL" key blocks. switch (f) { case ORIGIN_DESCRIPTION: m.put(f, base.get(f)); break; case ORIGIN_URL: if (delta.containsKey(SerializedField.ORIGIN_NULL_URL)) { m.remove(SerializedField.ORIGIN_NULL_URL); } else { m.put(f, base.get(f)); } break; case ORIGIN_RESOURCE: if (delta.containsKey(SerializedField.ORIGIN_NULL_RESOURCE)) { m.remove(SerializedField.ORIGIN_NULL_RESOURCE); } else { m.put(f, base.get(f)); } break; case ORIGIN_COMMENTS: if (delta.containsKey(SerializedField.ORIGIN_NULL_COMMENTS)) { m.remove(SerializedField.ORIGIN_NULL_COMMENTS); } else { m.put(f, base.get(f)); } break; case ORIGIN_NULL_URL: // FALL THRU case ORIGIN_NULL_RESOURCE: // FALL THRU case ORIGIN_NULL_COMMENTS: // FALL THRU // base objects shouldn't contain these, should just // lack the field. these are only in deltas. throw new ConfigException.BugOrBroken("applying fields, base object should not contain " + f + " " + base); case ORIGIN_END_LINE_NUMBER: // FALL THRU case ORIGIN_LINE_NUMBER: // FALL THRU case ORIGIN_TYPE: m.put(f, base.get(f)); break; case END_MARKER: case ROOT_VALUE: case ROOT_WAS_CONFIG: case UNKNOWN: case VALUE_DATA: case VALUE_ORIGIN: throw new ConfigException.BugOrBroken("should not appear here: " + f); } } } return m; } static SimpleConfigOrigin fromBase(SimpleConfigOrigin baseOrigin, Map delta) throws IOException { Map baseFields; if (baseOrigin != null) baseFields = baseOrigin.toFields(); else baseFields = Collections. emptyMap(); Map fields = applyFieldsDelta(baseFields, delta); return fromFields(fields); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/SimpleIncludeContext.java000066400000000000000000000030221277147274600304510ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import com.typesafe.config.ConfigIncludeContext; import com.typesafe.config.ConfigParseOptions; import com.typesafe.config.ConfigParseable; class SimpleIncludeContext implements ConfigIncludeContext { private final Parseable parseable; private final ConfigParseOptions options; SimpleIncludeContext(Parseable parseable) { this.parseable = parseable; this.options = SimpleIncluder.clearForInclude(parseable.options()); } private SimpleIncludeContext(Parseable parseable, ConfigParseOptions options) { this.parseable = parseable; this.options = options; } SimpleIncludeContext withParseable(Parseable parseable) { if (parseable == this.parseable) return this; else return new SimpleIncludeContext(parseable); } @Override public ConfigParseable relativeTo(String filename) { if (ConfigImpl.traceLoadsEnabled()) ConfigImpl.trace("Looking for '" + filename + "' relative to " + parseable); if (parseable != null) return parseable.relativeTo(filename); else return null; } @Override public ConfigParseOptions parseOptions() { return options; } @Override public ConfigIncludeContext setParseOptions(ConfigParseOptions options) { return new SimpleIncludeContext(parseable, options.setSyntax(null).setOriginDescription(null)); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/SimpleIncluder.java000066400000000000000000000270361277147274600273010ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigIncludeContext; import com.typesafe.config.ConfigIncluder; import com.typesafe.config.ConfigIncluderClasspath; import com.typesafe.config.ConfigIncluderFile; import com.typesafe.config.ConfigIncluderURL; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigParseOptions; import com.typesafe.config.ConfigParseable; import com.typesafe.config.ConfigSyntax; class SimpleIncluder implements FullIncluder { private ConfigIncluder fallback; SimpleIncluder(ConfigIncluder fallback) { this.fallback = fallback; } // ConfigIncludeContext does this for us on its options static ConfigParseOptions clearForInclude(ConfigParseOptions options) { // the class loader and includer are inherited, but not this other // stuff. return options.setSyntax(null).setOriginDescription(null).setAllowMissing(true); } // this is the heuristic includer @Override public ConfigObject include(final ConfigIncludeContext context, String name) { ConfigObject obj = includeWithoutFallback(context, name); // now use the fallback includer if any and merge // its result. if (fallback != null) { return obj.withFallback(fallback.include(context, name)); } else { return obj; } } // the heuristic includer in static form static ConfigObject includeWithoutFallback(final ConfigIncludeContext context, String name) { // the heuristic is valid URL then URL, else relative to including file; // relativeTo in a file falls back to classpath inside relativeTo(). URL url; try { url = new URL(name); } catch (MalformedURLException e) { url = null; } if (url != null) { return includeURLWithoutFallback(context, url); } else { NameSource source = new RelativeNameSource(context); return fromBasename(source, name, context.parseOptions()); } } @Override public ConfigObject includeURL(ConfigIncludeContext context, URL url) { ConfigObject obj = includeURLWithoutFallback(context, url); // now use the fallback includer if any and merge // its result. if (fallback != null && fallback instanceof ConfigIncluderURL) { return obj.withFallback(((ConfigIncluderURL) fallback).includeURL(context, url)); } else { return obj; } } static ConfigObject includeURLWithoutFallback(final ConfigIncludeContext context, URL url) { return ConfigFactory.parseURL(url, context.parseOptions()).root(); } @Override public ConfigObject includeFile(ConfigIncludeContext context, File file) { ConfigObject obj = includeFileWithoutFallback(context, file); // now use the fallback includer if any and merge // its result. if (fallback != null && fallback instanceof ConfigIncluderFile) { return obj.withFallback(((ConfigIncluderFile) fallback).includeFile(context, file)); } else { return obj; } } static ConfigObject includeFileWithoutFallback(final ConfigIncludeContext context, File file) { return ConfigFactory.parseFileAnySyntax(file, context.parseOptions()).root(); } @Override public ConfigObject includeResources(ConfigIncludeContext context, String resource) { ConfigObject obj = includeResourceWithoutFallback(context, resource); // now use the fallback includer if any and merge // its result. if (fallback != null && fallback instanceof ConfigIncluderClasspath) { return obj.withFallback(((ConfigIncluderClasspath) fallback).includeResources(context, resource)); } else { return obj; } } static ConfigObject includeResourceWithoutFallback(final ConfigIncludeContext context, String resource) { return ConfigFactory.parseResourcesAnySyntax(resource, context.parseOptions()).root(); } @Override public ConfigIncluder withFallback(ConfigIncluder fallback) { if (this == fallback) { throw new ConfigException.BugOrBroken("trying to create includer cycle"); } else if (this.fallback == fallback) { return this; } else if (this.fallback != null) { return new SimpleIncluder(this.fallback.withFallback(fallback)); } else { return new SimpleIncluder(fallback); } } interface NameSource { ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions); } static private class RelativeNameSource implements NameSource { final private ConfigIncludeContext context; RelativeNameSource(ConfigIncludeContext context) { this.context = context; } @Override public ConfigParseable nameToParseable(String name, ConfigParseOptions options) { ConfigParseable p = context.relativeTo(name); if (p == null) { // avoid returning null return Parseable .newNotFound(name, "include was not found: '" + name + "'", options); } else { return p; } } }; // this function is a little tricky because there are three places we're // trying to use it; for 'include "basename"' in a .conf file, for // loading app.{conf,json,properties} from classpath, and for // loading app.{conf,json,properties} from the filesystem. static ConfigObject fromBasename(NameSource source, String name, ConfigParseOptions options) { ConfigObject obj; if (name.endsWith(".conf") || name.endsWith(".json") || name.endsWith(".properties")) { ConfigParseable p = source.nameToParseable(name, options); obj = p.parse(p.options().setAllowMissing(options.getAllowMissing())); } else { ConfigParseable confHandle = source.nameToParseable(name + ".conf", options); ConfigParseable jsonHandle = source.nameToParseable(name + ".json", options); ConfigParseable propsHandle = source.nameToParseable(name + ".properties", options); boolean gotSomething = false; List fails = new ArrayList(); ConfigSyntax syntax = options.getSyntax(); obj = SimpleConfigObject.empty(SimpleConfigOrigin.newSimple(name)); if (syntax == null || syntax == ConfigSyntax.CONF) { try { obj = confHandle.parse(confHandle.options().setAllowMissing(false) .setSyntax(ConfigSyntax.CONF)); gotSomething = true; } catch (ConfigException.IO e) { fails.add(e); } } if (syntax == null || syntax == ConfigSyntax.JSON) { try { ConfigObject parsed = jsonHandle.parse(jsonHandle.options() .setAllowMissing(false).setSyntax(ConfigSyntax.JSON)); obj = obj.withFallback(parsed); gotSomething = true; } catch (ConfigException.IO e) { fails.add(e); } } if (syntax == null || syntax == ConfigSyntax.PROPERTIES) { try { ConfigObject parsed = propsHandle.parse(propsHandle.options() .setAllowMissing(false).setSyntax(ConfigSyntax.PROPERTIES)); obj = obj.withFallback(parsed); gotSomething = true; } catch (ConfigException.IO e) { fails.add(e); } } if (!options.getAllowMissing() && !gotSomething) { if (ConfigImpl.traceLoadsEnabled()) { // the individual exceptions should have been logged already // with tracing enabled ConfigImpl.trace("Did not find '" + name + "' with any extension (.conf, .json, .properties); " + "exceptions should have been logged above."); } if (fails.isEmpty()) { // this should not happen throw new ConfigException.BugOrBroken( "should not be reached: nothing found but no exceptions thrown"); } else { StringBuilder sb = new StringBuilder(); for (Throwable t : fails) { sb.append(t.getMessage()); sb.append(", "); } sb.setLength(sb.length() - 2); throw new ConfigException.IO(SimpleConfigOrigin.newSimple(name), sb.toString(), fails.get(0)); } } else if (!gotSomething) { if (ConfigImpl.traceLoadsEnabled()) { ConfigImpl.trace("Did not find '" + name + "' with any extension (.conf, .json, .properties); but '" + name + "' is allowed to be missing. Exceptions from load attempts should have been logged above."); } } } return obj; } // the Proxy is a proxy for an application-provided includer that uses our // default implementations when the application-provided includer doesn't // have an implementation. static private class Proxy implements FullIncluder { final ConfigIncluder delegate; Proxy(ConfigIncluder delegate) { this.delegate = delegate; } @Override public ConfigIncluder withFallback(ConfigIncluder fallback) { // we never fall back return this; } @Override public ConfigObject include(ConfigIncludeContext context, String what) { return delegate.include(context, what); } @Override public ConfigObject includeResources(ConfigIncludeContext context, String what) { if (delegate instanceof ConfigIncluderClasspath) return ((ConfigIncluderClasspath) delegate).includeResources(context, what); else return includeResourceWithoutFallback(context, what); } @Override public ConfigObject includeURL(ConfigIncludeContext context, URL what) { if (delegate instanceof ConfigIncluderURL) return ((ConfigIncluderURL) delegate).includeURL(context, what); else return includeURLWithoutFallback(context, what); } @Override public ConfigObject includeFile(ConfigIncludeContext context, File what) { if (delegate instanceof ConfigIncluderFile) return ((ConfigIncluderFile) delegate).includeFile(context, what); else return includeFileWithoutFallback(context, what); } } static FullIncluder makeFull(ConfigIncluder includer) { if (includer instanceof FullIncluder) return (FullIncluder) includer; else return new Proxy(includer); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/SubstitutionExpression.java000066400000000000000000000022271277147274600311510ustar00rootroot00000000000000package com.typesafe.config.impl; final class SubstitutionExpression { final private Path path; final private boolean optional; SubstitutionExpression(Path path, boolean optional) { this.path = path; this.optional = optional; } Path path() { return path; } boolean optional() { return optional; } SubstitutionExpression changePath(Path newPath) { if (newPath == path) return this; else return new SubstitutionExpression(newPath, optional); } @Override public String toString() { return "${" + (optional ? "?" : "") + path.render() + "}"; } @Override public boolean equals(Object other) { if (other instanceof SubstitutionExpression) { SubstitutionExpression otherExp = (SubstitutionExpression) other; return otherExp.path.equals(this.path) && otherExp.optional == this.optional; } else { return false; } } @Override public int hashCode() { int h = 41 * (41 + path.hashCode()); h = 41 * (h + (optional ? 1 : 0)); return h; } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/Token.java000066400000000000000000000046721277147274600254430ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; class Token { final private TokenType tokenType; final private String debugString; final private ConfigOrigin origin; final private String tokenText; Token(TokenType tokenType, ConfigOrigin origin) { this(tokenType, origin, null); } Token(TokenType tokenType, ConfigOrigin origin, String tokenText) { this(tokenType, origin, tokenText, null); } Token(TokenType tokenType, ConfigOrigin origin, String tokenText, String debugString) { this.tokenType = tokenType; this.origin = origin; this.debugString = debugString; this.tokenText = tokenText; } // this is used for singleton tokens like COMMA or OPEN_CURLY static Token newWithoutOrigin(TokenType tokenType, String debugString, String tokenText) { return new Token(tokenType, null, tokenText, debugString); } final TokenType tokenType() { return tokenType; } public String tokenText() { return tokenText; } // this is final because we don't always use the origin() accessor, // and we don't because it throws if origin is null final ConfigOrigin origin() { // code is only supposed to call origin() on token types that are // expected to have an origin. if (origin == null) throw new ConfigException.BugOrBroken( "tried to get origin from token that doesn't have one: " + this); return origin; } final int lineNumber() { if (origin != null) return origin.lineNumber(); else return -1; } @Override public String toString() { if (debugString != null) return debugString; else return tokenType.name(); } protected boolean canEqual(Object other) { return other instanceof Token; } @Override public boolean equals(Object other) { if (other instanceof Token) { // origin is deliberately left out return canEqual(other) && this.tokenType == ((Token) other).tokenType; } else { return false; } } @Override public int hashCode() { // origin is deliberately left out return tokenType.hashCode(); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/TokenType.java000066400000000000000000000005711277147274600262770ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; enum TokenType { START, END, COMMA, EQUALS, COLON, OPEN_CURLY, CLOSE_CURLY, OPEN_SQUARE, CLOSE_SQUARE, VALUE, NEWLINE, UNQUOTED_TEXT, IGNORED_WHITESPACE, SUBSTITUTION, PROBLEM, COMMENT, PLUS_EQUALS; } config-1.3.1/config/src/main/java/com/typesafe/config/impl/Tokenizer.java000066400000000000000000000630461277147274600263350ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Queue; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigSyntax; final class Tokenizer { // this exception should not leave this file private static class ProblemException extends Exception { private static final long serialVersionUID = 1L; final private Token problem; ProblemException(Token problem) { this.problem = problem; } Token problem() { return problem; } } private static String asString(int codepoint) { if (codepoint == '\n') return "newline"; else if (codepoint == '\t') return "tab"; else if (codepoint == -1) return "end of file"; else if (ConfigImplUtil.isC0Control(codepoint)) return String.format("control character 0x%x", codepoint); else return String.format("%c", codepoint); } /** * Tokenizes a Reader. Does not close the reader; you have to arrange to do * that after you're done with the returned iterator. */ static Iterator tokenize(ConfigOrigin origin, Reader input, ConfigSyntax flavor) { return new TokenIterator(origin, input, flavor != ConfigSyntax.JSON); } static String render(Iterator tokens) { StringBuilder renderedText = new StringBuilder(); while (tokens.hasNext()) { renderedText.append(tokens.next().tokenText()); } return renderedText.toString(); } private static class TokenIterator implements Iterator { private static class WhitespaceSaver { // has to be saved inside value concatenations private StringBuilder whitespace; // may need to value-concat with next value private boolean lastTokenWasSimpleValue; WhitespaceSaver() { whitespace = new StringBuilder(); lastTokenWasSimpleValue = false; } void add(int c) { whitespace.appendCodePoint(c); } Token check(Token t, ConfigOrigin baseOrigin, int lineNumber) { if (isSimpleValue(t)) { return nextIsASimpleValue(baseOrigin, lineNumber); } else { return nextIsNotASimpleValue(baseOrigin, lineNumber); } } // called if the next token is not a simple value; // discards any whitespace we were saving between // simple values. private Token nextIsNotASimpleValue(ConfigOrigin baseOrigin, int lineNumber) { lastTokenWasSimpleValue = false; return createWhitespaceTokenFromSaver(baseOrigin, lineNumber); } // called if the next token IS a simple value, // so creates a whitespace token if the previous // token also was. private Token nextIsASimpleValue(ConfigOrigin baseOrigin, int lineNumber) { Token t = createWhitespaceTokenFromSaver(baseOrigin, lineNumber); if (!lastTokenWasSimpleValue) { lastTokenWasSimpleValue = true; } return t; } private Token createWhitespaceTokenFromSaver(ConfigOrigin baseOrigin, int lineNumber) { if (whitespace.length() > 0) { Token t; if (lastTokenWasSimpleValue) { t = Tokens.newUnquotedText( lineOrigin(baseOrigin, lineNumber), whitespace.toString()); } else { t = Tokens.newIgnoredWhitespace(lineOrigin(baseOrigin, lineNumber), whitespace.toString()); } whitespace.setLength(0); // reset return t; } return null; } } final private SimpleConfigOrigin origin; final private Reader input; final private LinkedList buffer; private int lineNumber; private ConfigOrigin lineOrigin; final private Queue tokens; final private WhitespaceSaver whitespaceSaver; final private boolean allowComments; TokenIterator(ConfigOrigin origin, Reader input, boolean allowComments) { this.origin = (SimpleConfigOrigin) origin; this.input = input; this.allowComments = allowComments; this.buffer = new LinkedList(); lineNumber = 1; lineOrigin = this.origin.withLineNumber(lineNumber); tokens = new LinkedList(); tokens.add(Tokens.START); whitespaceSaver = new WhitespaceSaver(); } // this should ONLY be called from nextCharSkippingComments // or when inside a quoted string, or when parsing a sequence // like ${ or +=, everything else should use // nextCharSkippingComments(). private int nextCharRaw() { if (buffer.isEmpty()) { try { return input.read(); } catch (IOException e) { throw new ConfigException.IO(origin, "read error: " + e.getMessage(), e); } } else { int c = buffer.pop(); return c; } } private void putBack(int c) { if (buffer.size() > 2) { throw new ConfigException.BugOrBroken( "bug: putBack() three times, undesirable look-ahead"); } buffer.push(c); } static boolean isWhitespace(int c) { return ConfigImplUtil.isWhitespace(c); } static boolean isWhitespaceNotNewline(int c) { return c != '\n' && ConfigImplUtil.isWhitespace(c); } private boolean startOfComment(int c) { if (c == -1) { return false; } else { if (allowComments) { if (c == '#') { return true; } else if (c == '/') { int maybeSecondSlash = nextCharRaw(); // we want to predictably NOT consume any chars putBack(maybeSecondSlash); if (maybeSecondSlash == '/') { return true; } else { return false; } } else { return false; } } else { return false; } } } // get next char, skipping non-newline whitespace private int nextCharAfterWhitespace(WhitespaceSaver saver) { for (;;) { int c = nextCharRaw(); if (c == -1) { return -1; } else { if (isWhitespaceNotNewline(c)) { saver.add(c); continue; } else { return c; } } } } private ProblemException problem(String message) { return problem("", message, null); } private ProblemException problem(String what, String message) { return problem(what, message, null); } private ProblemException problem(String what, String message, boolean suggestQuotes) { return problem(what, message, suggestQuotes, null); } private ProblemException problem(String what, String message, Throwable cause) { return problem(lineOrigin, what, message, cause); } private ProblemException problem(String what, String message, boolean suggestQuotes, Throwable cause) { return problem(lineOrigin, what, message, suggestQuotes, cause); } private static ProblemException problem(ConfigOrigin origin, String what, String message, Throwable cause) { return problem(origin, what, message, false, cause); } private static ProblemException problem(ConfigOrigin origin, String what, String message, boolean suggestQuotes, Throwable cause) { if (what == null || message == null) throw new ConfigException.BugOrBroken( "internal error, creating bad ProblemException"); return new ProblemException(Tokens.newProblem(origin, what, message, suggestQuotes, cause)); } private static ProblemException problem(ConfigOrigin origin, String message) { return problem(origin, "", message, null); } private static ConfigOrigin lineOrigin(ConfigOrigin baseOrigin, int lineNumber) { return ((SimpleConfigOrigin) baseOrigin).withLineNumber(lineNumber); } // ONE char has always been consumed, either the # or the first /, but // not both slashes private Token pullComment(int firstChar) { boolean doubleSlash = false; if (firstChar == '/') { int discard = nextCharRaw(); if (discard != '/') throw new ConfigException.BugOrBroken("called pullComment but // not seen"); doubleSlash = true; } StringBuilder sb = new StringBuilder(); for (;;) { int c = nextCharRaw(); if (c == -1 || c == '\n') { putBack(c); if (doubleSlash) return Tokens.newCommentDoubleSlash(lineOrigin, sb.toString()); else return Tokens.newCommentHash(lineOrigin, sb.toString()); } else { sb.appendCodePoint(c); } } } // chars JSON allows a number to start with static final String firstNumberChars = "0123456789-"; // chars JSON allows to be part of a number static final String numberChars = "0123456789eE+-."; // chars that stop an unquoted string static final String notInUnquotedText = "$\"{}[]:=,+#`^?!@*&\\"; // The rules here are intended to maximize convenience while // avoiding confusion with real valid JSON. Basically anything // that parses as JSON is treated the JSON way and otherwise // we assume it's a string and let the parser sort it out. private Token pullUnquotedText() { ConfigOrigin origin = lineOrigin; StringBuilder sb = new StringBuilder(); int c = nextCharRaw(); while (true) { if (c == -1) { break; } else if (notInUnquotedText.indexOf(c) >= 0) { break; } else if (isWhitespace(c)) { break; } else if (startOfComment(c)) { break; } else { sb.appendCodePoint(c); } // we parse true/false/null tokens as such no matter // what is after them, as long as they are at the // start of the unquoted token. if (sb.length() == 4) { String s = sb.toString(); if (s.equals("true")) return Tokens.newBoolean(origin, true); else if (s.equals("null")) return Tokens.newNull(origin); } else if (sb.length() == 5) { String s = sb.toString(); if (s.equals("false")) return Tokens.newBoolean(origin, false); } c = nextCharRaw(); } // put back the char that ended the unquoted text putBack(c); String s = sb.toString(); return Tokens.newUnquotedText(origin, s); } private Token pullNumber(int firstChar) throws ProblemException { StringBuilder sb = new StringBuilder(); sb.appendCodePoint(firstChar); boolean containedDecimalOrE = false; int c = nextCharRaw(); while (c != -1 && numberChars.indexOf(c) >= 0) { if (c == '.' || c == 'e' || c == 'E') containedDecimalOrE = true; sb.appendCodePoint(c); c = nextCharRaw(); } // the last character we looked at wasn't part of the number, put it // back putBack(c); String s = sb.toString(); try { if (containedDecimalOrE) { // force floating point representation return Tokens.newDouble(lineOrigin, Double.parseDouble(s), s); } else { // this should throw if the integer is too large for Long return Tokens.newLong(lineOrigin, Long.parseLong(s), s); } } catch (NumberFormatException e) { // not a number after all, see if it's an unquoted string. for (char u : s.toCharArray()) { if (notInUnquotedText.indexOf(u) >= 0) throw problem(asString(u), "Reserved character '" + asString(u) + "' is not allowed outside quotes", true /* suggestQuotes */); } // no evil chars so we just decide this was a string and // not a number. return Tokens.newUnquotedText(lineOrigin, s); } } private void pullEscapeSequence(StringBuilder sb, StringBuilder sbOrig) throws ProblemException { int escaped = nextCharRaw(); if (escaped == -1) throw problem("End of input but backslash in string had nothing after it"); // This is needed so we return the unescaped escape characters back out when rendering // the token sbOrig.appendCodePoint('\\'); sbOrig.appendCodePoint(escaped); switch (escaped) { case '"': sb.append('"'); break; case '\\': sb.append('\\'); break; case '/': sb.append('/'); break; case 'b': sb.append('\b'); break; case 'f': sb.append('\f'); break; case 'n': sb.append('\n'); break; case 'r': sb.append('\r'); break; case 't': sb.append('\t'); break; case 'u': { // kind of absurdly slow, but screw it for now char[] a = new char[4]; for (int i = 0; i < 4; ++i) { int c = nextCharRaw(); if (c == -1) throw problem("End of input but expecting 4 hex digits for \\uXXXX escape"); a[i] = (char) c; } String digits = new String(a); sbOrig.append(a); try { sb.appendCodePoint(Integer.parseInt(digits, 16)); } catch (NumberFormatException e) { throw problem(digits, String.format( "Malformed hex digits after \\u escape in string: '%s'", digits), e); } } break; default: throw problem( asString(escaped), String.format( "backslash followed by '%s', this is not a valid escape sequence (quoted strings use JSON escaping, so use double-backslash \\\\ for literal backslash)", asString(escaped))); } } private void appendTripleQuotedString(StringBuilder sb, StringBuilder sbOrig) throws ProblemException { // we are after the opening triple quote and need to consume the // close triple int consecutiveQuotes = 0; for (;;) { int c = nextCharRaw(); if (c == '"') { consecutiveQuotes += 1; } else if (consecutiveQuotes >= 3) { // the last three quotes end the string and the others are // kept. sb.setLength(sb.length() - 3); putBack(c); break; } else { consecutiveQuotes = 0; if (c == -1) throw problem("End of input but triple-quoted string was still open"); else if (c == '\n') { // keep the line number accurate lineNumber += 1; lineOrigin = origin.withLineNumber(lineNumber); } } sb.appendCodePoint(c); sbOrig.appendCodePoint(c); } } private Token pullQuotedString() throws ProblemException { // the open quote has already been consumed StringBuilder sb = new StringBuilder(); // We need a second string builder to keep track of escape characters. // We want to return them exactly as they appeared in the original text, // which means we will need a new StringBuilder to escape escape characters // so we can also keep the actual value of the string. This is gross. StringBuilder sbOrig = new StringBuilder(); sbOrig.appendCodePoint('"'); while (true) { int c = nextCharRaw(); if (c == -1) throw problem("End of input but string quote was still open"); if (c == '\\') { pullEscapeSequence(sb, sbOrig); } else if (c == '"') { sbOrig.appendCodePoint(c); break; } else if (ConfigImplUtil.isC0Control(c)) { throw problem(asString(c), "JSON does not allow unescaped " + asString(c) + " in quoted strings, use a backslash escape"); } else { sb.appendCodePoint(c); sbOrig.appendCodePoint(c); } } // maybe switch to triple-quoted string, sort of hacky... if (sb.length() == 0) { int third = nextCharRaw(); if (third == '"') { sbOrig.appendCodePoint(third); appendTripleQuotedString(sb, sbOrig); } else { putBack(third); } } return Tokens.newString(lineOrigin, sb.toString(), sbOrig.toString()); } private Token pullPlusEquals() throws ProblemException { // the initial '+' has already been consumed int c = nextCharRaw(); if (c != '=') { throw problem(asString(c), "'+' not followed by =, '" + asString(c) + "' not allowed after '+'", true /* suggestQuotes */); } return Tokens.PLUS_EQUALS; } private Token pullSubstitution() throws ProblemException { // the initial '$' has already been consumed ConfigOrigin origin = lineOrigin; int c = nextCharRaw(); if (c != '{') { throw problem(asString(c), "'$' not followed by {, '" + asString(c) + "' not allowed after '$'", true /* suggestQuotes */); } boolean optional = false; c = nextCharRaw(); if (c == '?') { optional = true; } else { putBack(c); } WhitespaceSaver saver = new WhitespaceSaver(); List expression = new ArrayList(); Token t; do { t = pullNextToken(saver); // note that we avoid validating the allowed tokens inside // the substitution here; we even allow nested substitutions // in the tokenizer. The parser sorts it out. if (t == Tokens.CLOSE_CURLY) { // end the loop, done! break; } else if (t == Tokens.END) { throw problem(origin, "Substitution ${ was not closed with a }"); } else { Token whitespace = saver.check(t, origin, lineNumber); if (whitespace != null) expression.add(whitespace); expression.add(t); } } while (true); return Tokens.newSubstitution(origin, optional, expression); } private Token pullNextToken(WhitespaceSaver saver) throws ProblemException { int c = nextCharAfterWhitespace(saver); if (c == -1) { return Tokens.END; } else if (c == '\n') { // newline tokens have the just-ended line number Token line = Tokens.newLine(lineOrigin); lineNumber += 1; lineOrigin = origin.withLineNumber(lineNumber); return line; } else { Token t; if (startOfComment(c)) { t = pullComment(c); } else { switch (c) { case '"': t = pullQuotedString(); break; case '$': t = pullSubstitution(); break; case ':': t = Tokens.COLON; break; case ',': t = Tokens.COMMA; break; case '=': t = Tokens.EQUALS; break; case '{': t = Tokens.OPEN_CURLY; break; case '}': t = Tokens.CLOSE_CURLY; break; case '[': t = Tokens.OPEN_SQUARE; break; case ']': t = Tokens.CLOSE_SQUARE; break; case '+': t = pullPlusEquals(); break; default: t = null; break; } if (t == null) { if (firstNumberChars.indexOf(c) >= 0) { t = pullNumber(c); } else if (notInUnquotedText.indexOf(c) >= 0) { throw problem(asString(c), "Reserved character '" + asString(c) + "' is not allowed outside quotes", true /* suggestQuotes */); } else { putBack(c); t = pullUnquotedText(); } } } if (t == null) throw new ConfigException.BugOrBroken( "bug: failed to generate next token"); return t; } } private static boolean isSimpleValue(Token t) { if (Tokens.isSubstitution(t) || Tokens.isUnquotedText(t) || Tokens.isValue(t)) { return true; } else { return false; } } private void queueNextToken() throws ProblemException { Token t = pullNextToken(whitespaceSaver); Token whitespace = whitespaceSaver.check(t, origin, lineNumber); if (whitespace != null) tokens.add(whitespace); tokens.add(t); } @Override public boolean hasNext() { return !tokens.isEmpty(); } @Override public Token next() { Token t = tokens.remove(); if (tokens.isEmpty() && t != Tokens.END) { try { queueNextToken(); } catch (ProblemException e) { tokens.add(e.problem()); } if (tokens.isEmpty()) throw new ConfigException.BugOrBroken( "bug: tokens queue should not be empty here"); } return t; } @Override public void remove() { throw new UnsupportedOperationException( "Does not make sense to remove items from token stream"); } } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/Tokens.java000066400000000000000000000366231277147274600256270ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.util.List; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; import com.typesafe.config.ConfigValueType; /* FIXME the way the subclasses of Token are private with static isFoo and accessors is kind of ridiculous. */ final class Tokens { static private class Value extends Token { final private AbstractConfigValue value; Value(AbstractConfigValue value) { this(value, null); } Value(AbstractConfigValue value, String origText) { super(TokenType.VALUE, value.origin(), origText); this.value = value; } AbstractConfigValue value() { return value; } @Override public String toString() { if (value().resolveStatus() == ResolveStatus.RESOLVED) return "'" + value().unwrapped() + "' (" + value.valueType().name() + ")"; else return "'' (" + value.valueType().name() + ")"; } @Override protected boolean canEqual(Object other) { return other instanceof Value; } @Override public boolean equals(Object other) { return super.equals(other) && ((Value) other).value.equals(value); } @Override public int hashCode() { return 41 * (41 + super.hashCode()) + value.hashCode(); } } static private class Line extends Token { Line(ConfigOrigin origin) { super(TokenType.NEWLINE, origin); } @Override public String toString() { return "'\\n'@" + lineNumber(); } @Override protected boolean canEqual(Object other) { return other instanceof Line; } @Override public boolean equals(Object other) { return super.equals(other) && ((Line) other).lineNumber() == lineNumber(); } @Override public int hashCode() { return 41 * (41 + super.hashCode()) + lineNumber(); } @Override public String tokenText() { return "\n"; } } // This is not a Value, because it requires special processing static private class UnquotedText extends Token { final private String value; UnquotedText(ConfigOrigin origin, String s) { super(TokenType.UNQUOTED_TEXT, origin); this.value = s; } String value() { return value; } @Override public String toString() { return "'" + value + "'"; } @Override protected boolean canEqual(Object other) { return other instanceof UnquotedText; } @Override public boolean equals(Object other) { return super.equals(other) && ((UnquotedText) other).value.equals(value); } @Override public int hashCode() { return 41 * (41 + super.hashCode()) + value.hashCode(); } @Override public String tokenText() { return value; } } static private class IgnoredWhitespace extends Token { final private String value; IgnoredWhitespace(ConfigOrigin origin, String s) { super(TokenType.IGNORED_WHITESPACE, origin); this.value = s; } @Override public String toString() { return "'" + value + "' (WHITESPACE)"; } @Override protected boolean canEqual(Object other) { return other instanceof IgnoredWhitespace; } @Override public boolean equals(Object other) { return super.equals(other) && ((IgnoredWhitespace) other).value.equals(value); } @Override public int hashCode() { return 41 * (41 + super.hashCode()) + value.hashCode(); } @Override public String tokenText() { return value; } } static private class Problem extends Token { final private String what; final private String message; final private boolean suggestQuotes; final private Throwable cause; Problem(ConfigOrigin origin, String what, String message, boolean suggestQuotes, Throwable cause) { super(TokenType.PROBLEM, origin); this.what = what; this.message = message; this.suggestQuotes = suggestQuotes; this.cause = cause; } String what() { return what; } String message() { return message; } boolean suggestQuotes() { return suggestQuotes; } Throwable cause() { return cause; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append('\''); sb.append(what); sb.append('\''); sb.append(" ("); sb.append(message); sb.append(")"); return sb.toString(); } @Override protected boolean canEqual(Object other) { return other instanceof Problem; } @Override public boolean equals(Object other) { return super.equals(other) && ((Problem) other).what.equals(what) && ((Problem) other).message.equals(message) && ((Problem) other).suggestQuotes == suggestQuotes && ConfigImplUtil.equalsHandlingNull(((Problem) other).cause, cause); } @Override public int hashCode() { int h = 41 * (41 + super.hashCode()); h = 41 * (h + what.hashCode()); h = 41 * (h + message.hashCode()); h = 41 * (h + Boolean.valueOf(suggestQuotes).hashCode()); if (cause != null) h = 41 * (h + cause.hashCode()); return h; } } static private abstract class Comment extends Token { final private String text; Comment(ConfigOrigin origin, String text) { super(TokenType.COMMENT, origin); this.text = text; } final static class DoubleSlashComment extends Comment { DoubleSlashComment(ConfigOrigin origin, String text) { super(origin, text); } @Override public String tokenText() { return "//" + super.text; } } final static class HashComment extends Comment { HashComment(ConfigOrigin origin, String text) { super(origin, text); } @Override public String tokenText() { return "#" + super.text; } } String text() { return text; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("'#"); sb.append(text); sb.append("' (COMMENT)"); return sb.toString(); } @Override protected boolean canEqual(Object other) { return other instanceof Comment; } @Override public boolean equals(Object other) { return super.equals(other) && ((Comment) other).text.equals(text); } @Override public int hashCode() { int h = 41 * (41 + super.hashCode()); h = 41 * (h + text.hashCode()); return h; } } // This is not a Value, because it requires special processing static private class Substitution extends Token { final private boolean optional; final private List value; Substitution(ConfigOrigin origin, boolean optional, List expression) { super(TokenType.SUBSTITUTION, origin); this.optional = optional; this.value = expression; } boolean optional() { return optional; } List value() { return value; } @Override public String tokenText() { return "${" + (this.optional? "?" : "") + Tokenizer.render(this.value.iterator()) + "}"; } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (Token t : value) { sb.append(t.toString()); } return "'${" + sb.toString() + "}'"; } @Override protected boolean canEqual(Object other) { return other instanceof Substitution; } @Override public boolean equals(Object other) { return super.equals(other) && ((Substitution) other).value.equals(value); } @Override public int hashCode() { return 41 * (41 + super.hashCode()) + value.hashCode(); } } static boolean isValue(Token token) { return token instanceof Value; } static AbstractConfigValue getValue(Token token) { if (token instanceof Value) { return ((Value) token).value(); } else { throw new ConfigException.BugOrBroken( "tried to get value of non-value token " + token); } } static boolean isValueWithType(Token t, ConfigValueType valueType) { return isValue(t) && getValue(t).valueType() == valueType; } static boolean isNewline(Token token) { return token instanceof Line; } static boolean isProblem(Token token) { return token instanceof Problem; } static String getProblemWhat(Token token) { if (token instanceof Problem) { return ((Problem) token).what(); } else { throw new ConfigException.BugOrBroken("tried to get problem what from " + token); } } static String getProblemMessage(Token token) { if (token instanceof Problem) { return ((Problem) token).message(); } else { throw new ConfigException.BugOrBroken("tried to get problem message from " + token); } } static boolean getProblemSuggestQuotes(Token token) { if (token instanceof Problem) { return ((Problem) token).suggestQuotes(); } else { throw new ConfigException.BugOrBroken("tried to get problem suggestQuotes from " + token); } } static Throwable getProblemCause(Token token) { if (token instanceof Problem) { return ((Problem) token).cause(); } else { throw new ConfigException.BugOrBroken("tried to get problem cause from " + token); } } static boolean isComment(Token token) { return token instanceof Comment; } static String getCommentText(Token token) { if (token instanceof Comment) { return ((Comment) token).text(); } else { throw new ConfigException.BugOrBroken("tried to get comment text from " + token); } } static boolean isUnquotedText(Token token) { return token instanceof UnquotedText; } static String getUnquotedText(Token token) { if (token instanceof UnquotedText) { return ((UnquotedText) token).value(); } else { throw new ConfigException.BugOrBroken( "tried to get unquoted text from " + token); } } static boolean isIgnoredWhitespace(Token token) { return token instanceof IgnoredWhitespace; } static boolean isSubstitution(Token token) { return token instanceof Substitution; } static List getSubstitutionPathExpression(Token token) { if (token instanceof Substitution) { return ((Substitution) token).value(); } else { throw new ConfigException.BugOrBroken( "tried to get substitution from " + token); } } static boolean getSubstitutionOptional(Token token) { if (token instanceof Substitution) { return ((Substitution) token).optional(); } else { throw new ConfigException.BugOrBroken("tried to get substitution optionality from " + token); } } final static Token START = Token.newWithoutOrigin(TokenType.START, "start of file", ""); final static Token END = Token.newWithoutOrigin(TokenType.END, "end of file", ""); final static Token COMMA = Token.newWithoutOrigin(TokenType.COMMA, "','", ","); final static Token EQUALS = Token.newWithoutOrigin(TokenType.EQUALS, "'='", "="); final static Token COLON = Token.newWithoutOrigin(TokenType.COLON, "':'", ":"); final static Token OPEN_CURLY = Token.newWithoutOrigin(TokenType.OPEN_CURLY, "'{'", "{"); final static Token CLOSE_CURLY = Token.newWithoutOrigin(TokenType.CLOSE_CURLY, "'}'", "}"); final static Token OPEN_SQUARE = Token.newWithoutOrigin(TokenType.OPEN_SQUARE, "'['", "["); final static Token CLOSE_SQUARE = Token.newWithoutOrigin(TokenType.CLOSE_SQUARE, "']'", "]"); final static Token PLUS_EQUALS = Token.newWithoutOrigin(TokenType.PLUS_EQUALS, "'+='", "+="); static Token newLine(ConfigOrigin origin) { return new Line(origin); } static Token newProblem(ConfigOrigin origin, String what, String message, boolean suggestQuotes, Throwable cause) { return new Problem(origin, what, message, suggestQuotes, cause); } static Token newCommentDoubleSlash(ConfigOrigin origin, String text) { return new Comment.DoubleSlashComment(origin, text); } static Token newCommentHash(ConfigOrigin origin, String text) { return new Comment.HashComment(origin, text); } static Token newUnquotedText(ConfigOrigin origin, String s) { return new UnquotedText(origin, s); } static Token newIgnoredWhitespace(ConfigOrigin origin, String s) { return new IgnoredWhitespace(origin, s); } static Token newSubstitution(ConfigOrigin origin, boolean optional, List expression) { return new Substitution(origin, optional, expression); } static Token newValue(AbstractConfigValue value) { return new Value(value); } static Token newValue(AbstractConfigValue value, String origText) { return new Value(value, origText); } static Token newString(ConfigOrigin origin, String value, String origText) { return newValue(new ConfigString.Quoted(origin, value), origText); } static Token newInt(ConfigOrigin origin, int value, String origText) { return newValue(ConfigNumber.newNumber(origin, value, origText), origText); } static Token newDouble(ConfigOrigin origin, double value, String origText) { return newValue(ConfigNumber.newNumber(origin, value, origText), origText); } static Token newLong(ConfigOrigin origin, long value, String origText) { return newValue(ConfigNumber.newNumber(origin, value, origText), origText); } static Token newNull(ConfigOrigin origin) { return newValue(new ConfigNull(origin), "null"); } static Token newBoolean(ConfigOrigin origin, boolean value) { return newValue(new ConfigBoolean(origin, value), "" + value); } } config-1.3.1/config/src/main/java/com/typesafe/config/impl/Unmergeable.java000066400000000000000000000007461277147274600266070ustar00rootroot00000000000000/** * Copyright (C) 2011-2012 Typesafe Inc. */ package com.typesafe.config.impl; import java.util.Collection; /** * Interface that tags a ConfigValue that is not mergeable until after * substitutions are resolved. Basically these are special ConfigValue that * never appear in a resolved tree, like {@link ConfigSubstitution} and * {@link ConfigDelayedMerge}. */ interface Unmergeable { Collection unmergedValues(); } config-1.3.1/config/src/main/java/com/typesafe/config/impl/package.html000066400000000000000000000012431277147274600257700ustar00rootroot00000000000000

Internal implementation details that can change ABI at any time.

Please check out the {@link com.typesafe.config.Config public API} instead, unless you're interested in browsing implementation details. None of the ABI under impl has any guarantees; it will change whenever someone feels like changing it. If you feel you need access to something in impl, please file a feature request.

config-1.3.1/config/src/main/java/com/typesafe/config/package.html000066400000000000000000000061711277147274600250340ustar00rootroot00000000000000

An API for loading and using configuration files, see the project site for more information.

Typically you would load configuration with a static method from {@link com.typesafe.config.ConfigFactory} and then use it with methods in the {@link com.typesafe.config.Config} interface. Configuration may be in the form of JSON files, Java properties, or HOCON files; you may also build your own configuration in code or from your own file formats.

An application can simply call {@link com.typesafe.config.ConfigFactory#load()} and place its configuration in "application.conf" on the classpath. If you use the default configuration from {@link com.typesafe.config.ConfigFactory#load()} there's no need to pass a configuration to your libraries and frameworks, as long as they all default to this same default, which they should.
Example application code: Java and Scala.
Showing a couple of more special-purpose features, a more complex example: Java and Scala.

A library or framework should ship a file "reference.conf" in its jar, and allow an application to pass in a {@link com.typesafe.config.Config} to be used for the library. If no {@link com.typesafe.config.Config} is provided, call {@link com.typesafe.config.ConfigFactory#load()} to get the default one. Typically a library might offer two constructors, one with a Config parameter and one which uses {@link com.typesafe.config.ConfigFactory#load()}.
Example library code: Java and Scala.

Check out the full examples directory on GitHub.

What else to read:

  • The overview documentation for interface {@link com.typesafe.config.Config}.
  • The README for the library.
  • If you want to use .conf files in addition to .json and .properties, see the README for some short examples and the full HOCON spec for the long version.

config-1.3.1/config/src/main/java/com/typesafe/config/parser/000077500000000000000000000000001277147274600240425ustar00rootroot00000000000000config-1.3.1/config/src/main/java/com/typesafe/config/parser/ConfigDocument.java000066400000000000000000000075111277147274600276150ustar00rootroot00000000000000package com.typesafe.config.parser; import com.typesafe.config.ConfigValue; /** * Represents an individual HOCON or JSON file, preserving all * formatting and syntax details. This can be used to replace * individual values and exactly render the original text of the * input. * *

* Because this object is immutable, it is safe to use from multiple threads and * there's no need for "defensive copies." * *

* Do not implement interface {@code ConfigDocument}; 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. If * the document has an array as the root value, an exception will be thrown. * * @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 withValueText(String path, String newValue); /** * Returns a new ConfigDocument that is a copy of the current * ConfigDocument, but with the desired value set at the * desired path. Works like {@link #withValueText(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 withValue(String path, ConfigValue newValue); /** * Returns a new ConfigDocument that is a copy of the current ConfigDocument, but with * all values at the desired path removed. If the path does not exist in the document, * a copy of the current document will be returned. If there is an array at the root, an exception * will be thrown. * * @param path the path to remove from the document * @return a copy of the ConfigDocument with the desired value removed from the document. */ ConfigDocument withoutPath(String path); /** * Returns a boolean indicating whether or not a ConfigDocument has a value at the desired path. * null counts as a value for purposes of this check. * @param path the path to check * @return true if the path exists in the document, otherwise false */ boolean hasPath(String path); /** * The original text of the input, modified if necessary with * any replaced or added values. * @return the modified original text */ String render(); } config-1.3.1/config/src/main/java/com/typesafe/config/parser/ConfigDocumentFactory.java000066400000000000000000000056001277147274600311420ustar00rootroot00000000000000package com.typesafe.config.parser; import com.typesafe.config.ConfigParseOptions; import com.typesafe.config.impl.Parseable; import java.io.File; import java.io.Reader; /** * Factory for creating {@link * com.typesafe.config.parser.ConfigDocument} instances. */ 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 com.typesafe.config.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 com.typesafe.config.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 com.typesafe.config.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 com.typesafe.config.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()); } } config-1.3.1/config/src/main/java/com/typesafe/config/parser/ConfigNode.java000066400000000000000000000023521277147274600267220ustar00rootroot00000000000000/** * Copyright (C) 2015 Typesafe Inc. */ package com.typesafe.config.parser; /** * A node in the syntax tree for a HOCON or JSON document. * *

* Note: at present there is no way to obtain an instance of this interface, so * please ignore it. A future release will make syntax tree nodes available in * the public API. If you are interested in working on it, please see: https://github.com/typesafehub/config/issues/300 * *

* Because this object is immutable, it is safe to use from multiple threads and * there's no need for "defensive copies." * *

* Do not implement interface {@code ConfigNode}; 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(); } config-1.3.1/config/src/main/java/com/typesafe/config/parser/package.html000066400000000000000000000021001277147274600263140ustar00rootroot00000000000000

This package supplies a raw parser and syntax tree for individual HOCON and JSON files. You do not want this package for everyday config in your app: see the com.typesafe.config package instead. You would use the raw parser if you're doing something like reading, modifying, and re-saving a config file. For info on the main config API this parser is a part of, see the project site.

For working with the raw syntax tree, some important classes are:

  • {@link com.typesafe.config.parser.ConfigDocument} - a loaded HOCON or JSON document
  • {@link com.typesafe.config.parser.ConfigDocumentFactory} - static methods to instantiate a document
  • {@link com.typesafe.config.parser.ConfigNode} - syntax node in a document

config-1.3.1/config/src/test/000077500000000000000000000000001277147274600160155ustar00rootroot00000000000000config-1.3.1/config/src/test/java/000077500000000000000000000000001277147274600167365ustar00rootroot00000000000000config-1.3.1/config/src/test/java/beanconfig/000077500000000000000000000000001277147274600210315ustar00rootroot00000000000000config-1.3.1/config/src/test/java/beanconfig/ArraysConfig.java000066400000000000000000000061101277147274600242610ustar00rootroot00000000000000package beanconfig; import java.util.List; import java.time.Duration; import com.typesafe.config.Config; import com.typesafe.config.ConfigMemorySize; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigValue; public class ArraysConfig { List empty; List ofInt; List ofString; List ofDouble; List ofLong; List ofNull; List ofBoolean; List ofObject; List ofConfig; List ofConfigObject; List ofConfigValue; List ofDuration; List ofMemorySize; List ofStringBean; public List getEmpty() { return empty; } public void setEmpty(List empty) { this.empty = empty; } public List getOfInt() { return ofInt; } public void setOfInt(List ofInt) { this.ofInt = ofInt; } public List getOfString() { return ofString; } public void setOfString(List ofString) { this.ofString = ofString; } public List getOfDouble() { return ofDouble; } public void setOfDouble(List ofDouble) { this.ofDouble = ofDouble; } public List getOfNull() { return ofNull; } public void setOfNull(List ofNull) { this.ofNull = ofNull; } public List getOfBoolean() { return ofBoolean; } public void setOfBoolean(List ofBoolean) { this.ofBoolean = ofBoolean; } public List getOfObject() { return ofObject; } public void setOfObject(List ofObject) { this.ofObject = ofObject; } public List getOfLong() { return ofLong; } public void setOfLong(List ofLong) { this.ofLong = ofLong; } public List getOfConfig() { return ofConfig; } public void setOfConfig(List ofConfig) { this.ofConfig = ofConfig; } public List getOfConfigObject() { return ofConfigObject; } public void setOfConfigObject(List ofConfigObject) { this.ofConfigObject = ofConfigObject; } public List getOfConfigValue() { return ofConfigValue; } public void setOfConfigValue(List ofConfigValue) { this.ofConfigValue = ofConfigValue; } public List getOfDuration() { return ofDuration; } public void setOfDuration(List ofDuration) { this.ofDuration = ofDuration; } public List getOfMemorySize() { return ofMemorySize; } public void setOfMemorySize(List ofMemorySize) { this.ofMemorySize = ofMemorySize; } public List getOfStringBean() { return ofStringBean; } public void setOfStringBean(List ofStringBean) { this.ofStringBean = ofStringBean; } } config-1.3.1/config/src/test/java/beanconfig/BooleansConfig.java000066400000000000000000000021151277147274600245630ustar00rootroot00000000000000package beanconfig; public class BooleansConfig { Boolean trueVal; Boolean trueValAgain; Boolean falseVal; Boolean falseValAgain; Boolean on; boolean off; public Boolean getTrueVal() { return trueVal; } public void setTrueVal(Boolean trueVal) { this.trueVal = trueVal; } public Boolean getTrueValAgain() { return trueValAgain; } public void setTrueValAgain(Boolean trueValAgain) { this.trueValAgain = trueValAgain; } public Boolean getFalseVal() { return falseVal; } public void setFalseVal(Boolean falseVal) { this.falseVal = falseVal; } public Boolean getFalseValAgain() { return falseValAgain; } public void setFalseValAgain(Boolean falseValAgain) { this.falseValAgain = falseValAgain; } public Boolean getOn() { return on; } public void setOn(Boolean on) { this.on = on; } public boolean getOff() { return off; } public void setOff(boolean off) { this.off = off; } } config-1.3.1/config/src/test/java/beanconfig/BytesConfig.java000066400000000000000000000014031277147274600241060ustar00rootroot00000000000000package beanconfig; import com.typesafe.config.ConfigMemorySize; public class BytesConfig { private ConfigMemorySize kilobyte; private ConfigMemorySize kibibyte; private ConfigMemorySize thousandBytes; public ConfigMemorySize getKilobyte() { return kilobyte; } public void setKilobyte(ConfigMemorySize kilobyte) { this.kilobyte = kilobyte; } public ConfigMemorySize getKibibyte() { return kibibyte; } public void setKibibyte(ConfigMemorySize kibibyte) { this.kibibyte = kibibyte; } public ConfigMemorySize getThousandBytes() { return thousandBytes; } public void setThousandBytes(ConfigMemorySize thousandBytes) { this.thousandBytes = thousandBytes; } } config-1.3.1/config/src/test/java/beanconfig/DurationsConfig.java000066400000000000000000000012341277147274600247720ustar00rootroot00000000000000package beanconfig; import java.time.Duration; public class DurationsConfig { Duration second; Duration secondAsNumber; Duration halfSecond; public Duration getSecond() { return second; } public void setSecond(Duration second) { this.second = second; } public Duration getSecondAsNumber() { return secondAsNumber; } public void setSecondAsNumber(Duration secondAsNumber) { this.secondAsNumber = secondAsNumber; } public Duration getHalfSecond() { return halfSecond; } public void setHalfSecond(Duration halfSecond) { this.halfSecond = halfSecond; } } config-1.3.1/config/src/test/java/beanconfig/EnumsConfig.java000066400000000000000000000026471277147274600241220ustar00rootroot00000000000000package beanconfig; import java.util.List; public class EnumsConfig { public enum Problem { P1, P2, P3; }; public enum Solution { S1, S2, S3; } Problem problem; List solutions; public Problem getProblem() { return problem; } public void setProblem(Problem problem) { this.problem = problem; } public List getSolutions() { return solutions; } public void setSolutions(List solutions) { this.solutions = solutions; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof EnumsConfig)) { return false; } EnumsConfig that = (EnumsConfig) o; if (getProblem() != that.getProblem()) { return false; } return getSolutions() == that.getSolutions(); } @Override public int hashCode() { int result = getProblem() != null ? getProblem().hashCode() : 0; result = 31 * result + (getSolutions() != null ? getSolutions().hashCode() : 0); return result; } @Override public String toString() { final StringBuffer sb = new StringBuffer("EnumsConfig{"); sb.append("problem=").append(problem); sb.append(", solution=").append(solutions); sb.append('}'); return sb.toString(); } } config-1.3.1/config/src/test/java/beanconfig/NotABeanFieldConfig.java000066400000000000000000000004561277147274600254220ustar00rootroot00000000000000package beanconfig; public class NotABeanFieldConfig { public static class NotABean { int stuff; } private NotABean notBean; public NotABean getNotBean() { return notBean; } public void setNotBean(NotABean notBean) { this.notBean = notBean; } } config-1.3.1/config/src/test/java/beanconfig/NumbersConfig.java000066400000000000000000000021241277147274600244340ustar00rootroot00000000000000package beanconfig; public class NumbersConfig { private int intVal; private Integer intObj; private long longVal; private Long longObj; private double doubleVal; private Double doubleObj; public int getIntVal() { return intVal; } public void setIntVal(int intVal) { this.intVal = intVal; } public Integer getIntObj() { return intObj; } public void setIntObj(Integer intObj) { this.intObj = intObj; } public long getLongVal() { return longVal; } public void setLongVal(long longVal) { this.longVal = longVal; } public Long getLongObj() { return longObj; } public void setLongObj(Long longObj) { this.longObj = longObj; } public double getDoubleVal() { return doubleVal; } public void setDoubleVal(double doubleVal) { this.doubleVal = doubleVal; } public Double getDoubleObj() { return doubleObj; } public void setDoubleObj(Double doubleObj) { this.doubleObj = doubleObj; } } config-1.3.1/config/src/test/java/beanconfig/ObjectsConfig.java000066400000000000000000000030721277147274600244150ustar00rootroot00000000000000package beanconfig; import com.typesafe.config.Optional; public class ObjectsConfig { public static class ValueObject { @Optional private String optionalValue; private String mandatoryValue; public String getMandatoryValue() { return mandatoryValue; } public void setMandatoryValue(String mandatoryValue) { this.mandatoryValue = mandatoryValue; } public String getOptionalValue() { return optionalValue; } public void setOptionalValue(String optionalValue) { this.optionalValue = optionalValue; } } private ValueObject valueObject; public ValueObject getValueObject() { return valueObject; } public void setValueObject(ValueObject valueObject) { this.valueObject = valueObject; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ObjectsConfig)) { return false; } ObjectsConfig that = (ObjectsConfig) o; return !(getValueObject() != null ? !getValueObject().equals(that.getValueObject()) : that.getValueObject() != null); } @Override public int hashCode() { return getValueObject() != null ? getValueObject().hashCode() : 0; } @Override public String toString() { final StringBuffer sb = new StringBuffer("ObjectsConfig{"); sb.append("innerType=").append(valueObject); sb.append('}'); return sb.toString(); } } config-1.3.1/config/src/test/java/beanconfig/PreferCamelNamesConfig.java000066400000000000000000000005731277147274600262000ustar00rootroot00000000000000package beanconfig; public class PreferCamelNamesConfig { private String fooBar; private String bazBar; public String getFooBar() { return fooBar; } public void setFooBar(String v) { this.fooBar = v; } public String getBazBar() { return bazBar; } public void setBazBar(String v) { this.bazBar = v; } } config-1.3.1/config/src/test/java/beanconfig/StringsConfig.java000066400000000000000000000015121277147274600244520ustar00rootroot00000000000000package beanconfig; public class StringsConfig { String abcd; String yes; public String getAbcd() { return abcd; } public void setAbcd(String s) { abcd = s; } public String getYes() { return yes; } public void setYes(String s) { yes = s; } @Override public boolean equals(Object o) { if (o instanceof StringsConfig) { StringsConfig sc = (StringsConfig) o; return sc.abcd.equals(abcd) && sc.yes.equals(yes); } else { return false; } } @Override public int hashCode() { int h = 41 * (41 + abcd.hashCode()); return h + yes.hashCode(); } @Override public String toString() { return "StringsConfig(" + abcd + "," + yes + ")"; } } config-1.3.1/config/src/test/java/beanconfig/TestBeanConfig.java000066400000000000000000000003741277147274600245330ustar00rootroot00000000000000package beanconfig; public class TestBeanConfig { private NumbersConfig numbers; public NumbersConfig getNumbers() { return numbers; } public void setNumbers(NumbersConfig numbers) { this.numbers = numbers; } } config-1.3.1/config/src/test/java/beanconfig/UnsupportedListElementConfig.java000066400000000000000000000004141277147274600275170ustar00rootroot00000000000000package beanconfig; import java.net.URI; import java.util.List; public class UnsupportedListElementConfig { private List uri; public List getUri() { return uri; } public void setUri(List uri) { this.uri = uri; } }config-1.3.1/config/src/test/java/beanconfig/UnsupportedMapKeyConfig.java000066400000000000000000000004231277147274600264600ustar00rootroot00000000000000package beanconfig; import java.util.Map; public class UnsupportedMapKeyConfig { private Map map; public Map getMap() { return map; } public void setMap(Map map) { this.map = map; } }config-1.3.1/config/src/test/java/beanconfig/UnsupportedMapValueConfig.java000066400000000000000000000004251277147274600270060ustar00rootroot00000000000000package beanconfig; import java.util.Map; public class UnsupportedMapValueConfig { private Map map; public Map getMap() { return map; } public void setMap(Map map) { this.map = map; } }config-1.3.1/config/src/test/java/beanconfig/ValidationBeanConfig.java000066400000000000000000000016761277147274600257140ustar00rootroot00000000000000package beanconfig; import java.util.List; public class ValidationBeanConfig extends TestBeanConfig{ private String propNotListedInConfig; private int shouldBeInt; private boolean shouldBeBoolean; private List shouldBeList; public String getPropNotListedInConfig() { return propNotListedInConfig; } public void setPropNotListedInConfig(String propNotListedInConfig) { this.propNotListedInConfig = propNotListedInConfig; } public int getShouldBeInt() { return shouldBeInt; } public void setShouldBeInt(int v) { shouldBeInt = v; } public boolean getShouldBeBoolean() { return shouldBeBoolean; } public void setShouldBeBoolean(boolean v) { shouldBeBoolean = v; } public List getShouldBeList() { return shouldBeList; } public void setShouldBeList(List v) { shouldBeList = v; } } config-1.3.1/config/src/test/java/beanconfig/ValuesConfig.java000066400000000000000000000025531277147274600242660ustar00rootroot00000000000000package beanconfig; import java.util.Map; import com.typesafe.config.Config; import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigValue; // test bean for various "uncooked" values public class ValuesConfig { Object obj; Config config; ConfigObject configObj; ConfigValue configValue; ConfigList list; Map unwrappedMap; public Object getObj() { return obj; } public void setObj(Object obj) { this.obj = obj; } public Config getConfig() { return config; } public void setConfig(Config config) { this.config = config; } public ConfigObject getConfigObj() { return configObj; } public void setConfigObj(ConfigObject configObj) { this.configObj = configObj; } public ConfigValue getConfigValue() { return configValue; } public void setConfigValue(ConfigValue configValue) { this.configValue = configValue; } public ConfigList getList() { return list; } public void setList(ConfigList list) { this.list = list; } public Map getUnwrappedMap() { return unwrappedMap; } public void setUnwrappedMap(Map unwrappedMap) { this.unwrappedMap = unwrappedMap; } } config-1.3.1/config/src/test/resources/000077500000000000000000000000001277147274600200275ustar00rootroot00000000000000config-1.3.1/config/src/test/resources/a_1.conf000066400000000000000000000000041277147274600213300ustar00rootroot00000000000000a=1 config-1.3.1/config/src/test/resources/b_2.conf000066400000000000000000000000041277147274600213320ustar00rootroot00000000000000b=2 config-1.3.1/config/src/test/resources/beanconfig/000077500000000000000000000000001277147274600221225ustar00rootroot00000000000000config-1.3.1/config/src/test/resources/beanconfig/beanconfig01.conf000066400000000000000000000054331277147274600252320ustar00rootroot00000000000000{ "booleans" : { "trueVal" : true, "trueValAgain" : ${booleans.trueVal}, "falseVal" : false, "falseValAgain" : ${booleans.falseVal}, "on" : "on", "off" : "off" } "numbers" : { "byteVal" : "1", "byteObj" : ${numbers.byteVal}, "shortVal" : "2", "shortObj" : ${numbers.shortVal}, "intVal" : "3", "intObj" : ${numbers.intVal}, "longVal" : "4", "longObj" : ${numbers.longVal}, "doubleVal" : "1.0", "doubleObj" : ${numbers.doubleVal} } // "strings" : { "abcd" : "abcd", "abcd-again" : ${strings.a}${strings.b}${strings.c}${strings.d}, "a" : "a", "b" : "b", "c" : "c", "d" : "d", "concatenated" : null bar 42 baz true 3.14 hi, "double" : "3.14", "number" : "57", "null" : "null", "true" : "true", "yes" : "yes", "false" : "false", "no" : "no" }, "arrays" : { "empty" : [], "ofInt" : [1, 2, 3], "ofString" : [ ${strings.a}, ${strings.b}, ${strings.c} ], "of-double" : [3.14, 4.14, 5.14], "of-long" : { "1" : 32, "2" : 42, "3" : 52 }, // object-to-list conversion "ofNull" : [null, null, null], "ofBoolean" : [true, false], "ofArray" : [${arrays.ofString}, ${arrays.ofString}, ${arrays.ofString}], "ofObject" : [${numbers}, ${booleans}, ${strings}], "ofConfig" : [${numbers}, ${booleans}, ${strings}], "ofConfigObject" : [${numbers}, ${booleans}, ${strings}], "ofConfigValue" : [1, 2, "a"], "ofDuration" : [1, 2h, 3 days], "ofMemorySize" : [1024, 1M, 1G], "ofStringBean" : [ { abcd : "testAbcdOne" yes : "testYesOne" }, { abcd : "testAbcdTwo" yes : "testYesTwo" } ] }, "bytes" : { "kilobyte" : "1kB", "kibibyte" : "1K", "thousandBytes" : "1000B" }, "durations" : { "second" : 1s, "secondAsNumber" : 1000, "halfSecond" : 0.5s }, "validation" : { "shouldBeInt" : true, "should-be-boolean" : 42, "should-be-list" : "hello" }, "preferCamelNames" : { "foo-bar" : "no", "fooBar" : "yes", "baz-bar" : "no", "bazBar" : "yes" }, "values" : { "obj" : 42, "config" : ${strings}, "configObj" : ${numbers}, "configValue" : "hello world", "list" : [1,2,3], "unwrappedMap" : ${validation} }, "enums" : { "problem" : "P1", "solutions" : ["S1", "S3"] }, "objects" : { "valueObject": { "mandatoryValue": "notNull" } } } config-1.3.1/config/src/test/resources/bom.conf000066400000000000000000000000171277147274600214510ustar00rootroot00000000000000# foo = bar config-1.3.1/config/src/test/resources/cycle.conf000066400000000000000000000000251277147274600217720ustar00rootroot00000000000000include "cycle.conf" config-1.3.1/config/src/test/resources/equiv01/000077500000000000000000000000001277147274600213215ustar00rootroot00000000000000config-1.3.1/config/src/test/resources/equiv01/comments.conf000066400000000000000000000017021277147274600240150ustar00rootroot00000000000000{ // These are some integers "ints" : { # we can comment them with # too "fortyTwo" : 42, ## double-# ! "fortyTwoAgain" : 42 # { } }, ######## COMMENT COMMENT COMMENT /// I COMMENT YOU "floats" : { "fortyTwoPointOne" : 42.1, "fortyTwoPointOneAgain" : 42.1 // I CAN HAS COMMENT }, "strings" : { "abcd" : // I got yr comment right here "abcd", "abcdAgain" : "abcd", "a" : "a", "b" : "b", "c" : "c", "d" : "d", "concatenated" # more to say? : "null bar 42 baz true 3.14 hi" }, "arrays" : { "empty" : [], "1" : [ 1 ], "12" : [1, 2], "123" : [1, 2, 3], "ofString" : [ "a", "b", "c" ] }, "booleans" : { "true" : true, "trueAgain" : true, "false" : false, "falseAgain" : false }, "nulls" : { "null" : null, "nullAgain" : null } } config-1.3.1/config/src/test/resources/equiv01/equals.conf000066400000000000000000000013621277147274600234640ustar00rootroot00000000000000{ "ints" = { "fortyTwo" = 42, "fortyTwoAgain" = 42 }, "floats" = { "fortyTwoPointOne" = 42.1, "fortyTwoPointOneAgain" = 42.1 }, "strings" = { "abcd" = "abcd", "abcdAgain" = "abcd", "a" = "a", "b" = "b", "c" = "c", "d" = "d", "concatenated" = "null bar 42 baz true 3.14 hi" }, "arrays" = { "empty" = [], "1" = [ 1 ], "12" = [1, 2], "123" = [1, 2, 3], "ofString" = [ "a", "b", "c" ] }, "booleans" = { "true" = true, "trueAgain" = true, "false" = false, "falseAgain" = false }, "nulls" = { "null" = null, "nullAgain" = null } } config-1.3.1/config/src/test/resources/equiv01/no-commas.conf000066400000000000000000000014261277147274600240640ustar00rootroot00000000000000{ "ints" : { "fortyTwo" : 42 "fortyTwoAgain" : 42 } "floats" : { "fortyTwoPointOne" : 42.1 "fortyTwoPointOneAgain" : 42.1 } "strings" : { "abcd" : "abcd" "abcdAgain" : "abcd" "a" : "a" "b" : "b" "c" : "c" "d" : "d" "concatenated" : "null bar 42 baz true 3.14 hi" } "arrays" : { "empty" : [] "1" : [ 1 ] "12" : [1 2] "123" : [1 2 3 ] "ofString" : [ "a" "b" "c" ] } "booleans" : { "true" : true "trueAgain" : true "false" : false "falseAgain" : false } "nulls" : { "null" : null "nullAgain" : null } } config-1.3.1/config/src/test/resources/equiv01/no-root-braces.conf000066400000000000000000000011471277147274600250250ustar00rootroot00000000000000"ints" : { "fortyTwo" : 42, "fortyTwoAgain" : 42 }, "floats" : { "fortyTwoPointOne" : 42.1, "fortyTwoPointOneAgain" : 42.1 }, "strings" : { "abcd" : "abcd", "abcdAgain" : "abcd", "a" : "a", "b" : "b", "c" : "c", "d" : "d", "concatenated" : "null bar 42 baz true 3.14 hi" }, "arrays" : { "empty" : [], "1" : [ 1 ], "12" : [1, 2], "123" : [1, 2, 3], "ofString" : [ "a", "b", "c" ] }, "booleans" : { "true" : true, "trueAgain" : true, "false" : false, "falseAgain" : false }, "nulls" : { "null" : null, "nullAgain" : null } config-1.3.1/config/src/test/resources/equiv01/no-whitespace.json000066400000000000000000000006501277147274600247630ustar00rootroot00000000000000{"ints":{"fortyTwo":42,"fortyTwoAgain":42},"floats":{"fortyTwoPointOne":42.1,"fortyTwoPointOneAgain":42.1},"strings":{"abcd":"abcd","abcdAgain":"abcd","a":"a","b":"b","c":"c","d":"d","concatenated":"null bar 42 baz true 3.14 hi"},"arrays":{"empty":[],"1":[1],"12":[1,2],"123":[1,2,3],"ofString":["a","b","c"]},"booleans":{"true":true,"trueAgain":true,"false":false,"falseAgain":false},"nulls":{"null":null,"nullAgain":null}}config-1.3.1/config/src/test/resources/equiv01/omit-colons.conf000066400000000000000000000013741277147274600244400ustar00rootroot00000000000000{ "ints" { "fortyTwo" : 42, "fortyTwoAgain" : 42 }, "floats" { "fortyTwoPointOne" : 42.1, "fortyTwoPointOneAgain" : 42.1 }, "strings" { "abcd" : "abcd", "abcdAgain" : "abcd", "a" : "a", "b" : "b", "c" : "c", "d" : "d", "concatenated" : "null bar 42 baz true 3.14 hi" }, "arrays" { "empty" : [], "1" : [ 1 ], "12" : [1, 2], "123" : [1, 2, 3], "ofString" : [ "a", "b", "c" ] }, "booleans" { "true" : true, "trueAgain" : true, "false" : false, "falseAgain" : false }, "nulls" { "null" : null, "nullAgain" : null } } config-1.3.1/config/src/test/resources/equiv01/original.json000066400000000000000000000013621277147274600240220ustar00rootroot00000000000000{ "ints" : { "fortyTwo" : 42, "fortyTwoAgain" : 42 }, "floats" : { "fortyTwoPointOne" : 42.1, "fortyTwoPointOneAgain" : 42.1 }, "strings" : { "abcd" : "abcd", "abcdAgain" : "abcd", "a" : "a", "b" : "b", "c" : "c", "d" : "d", "concatenated" : "null bar 42 baz true 3.14 hi" }, "arrays" : { "empty" : [], "1" : [ 1 ], "12" : [1, 2], "123" : [1, 2, 3], "ofString" : [ "a", "b", "c" ] }, "booleans" : { "true" : true, "trueAgain" : true, "false" : false, "falseAgain" : false }, "nulls" : { "null" : null, "nullAgain" : null } } config-1.3.1/config/src/test/resources/equiv01/path-keys.conf000066400000000000000000000012131277147274600240720ustar00rootroot00000000000000{ ints.fortyTwo : 42, ints.fortyTwoAgain : 42, floats.fortyTwoPointOne : 42.1, floats.fortyTwoPointOneAgain : 42.1, strings.abcd : "abcd", strings.abcdAgain : "abcd", strings.a : "a", strings.b : "b", strings.c : "c", strings.d : "d", strings.concatenated : "null bar 42 baz true 3.14 hi", arrays."empty" : [], arrays."1" : [ 1 ], arrays.12 : [1, 2], arrays.123 : [1, 2, 3], arrays.ofString : [ "a", "b", "c" ], booleans.true : true, booleans.trueAgain : true, booleans.false : false, booleans.falseAgain : false, nulls.null : null, nulls.nullAgain : null } config-1.3.1/config/src/test/resources/equiv01/properties-style.conf000066400000000000000000000021171277147274600255230ustar00rootroot00000000000000# this file is in a no-braces java properties style, # but it's a .conf file with json-style escaping, # not a Java properties file. # lots of playing with whitespace and commas # in here to test various cases; don't copy # this as exemplary, it has gratuitously # stupid formatting. ints.fortyTwo=42 ints.fortyTwoAgain = 42 // whitespace ignored around the = floats.fortyTwoPointOne=42.1 floats.fortyTwoPointOneAgain:42.1 // colon is fine too strings.abcd=abcd strings.abcdAgain : "abcd", // colon and quotes and comma! strings.a=a strings.b=b strings.c="c", strings.d:"d" strings.concatenated=null bar 42 baz true 3.14 hi # this gets squished to a string arrays.empty=[] arrays.1 : [ 1 ] # it's allowed to omit commas in arrays if you have newlines arrays.12 = [ 1 2 ] arrays.123 = [1, 2 3] arrays.ofString = [ "a", "b", "c" ] booleans.true=true booleans.trueAgain=${booleans.true} booleans.false=false # you could quote keys, though here it's pointless # since the key contains no special characters "booleans"."falseAgain" : ${booleans.false}, nulls.null=${nulls.nullAgain} nulls.nullAgain=null config-1.3.1/config/src/test/resources/equiv01/substitutions.conf000066400000000000000000000015741277147274600251360ustar00rootroot00000000000000{ "ints" : { "fortyTwo" : 42, "fortyTwoAgain" : ${ints.fortyTwo} }, "floats" : { "fortyTwoPointOne" : 42.1, "fortyTwoPointOneAgain" : ${floats.fortyTwoPointOne} }, "strings" : { "abcd" : "abcd", "abcdAgain" : ${strings.a}${strings.b}${strings.c}${strings.d}, "a" : "a", "b" : "b", "c" : "c", "d" : "d", "concatenated" : "null bar 42 baz true 3.14 hi" }, "arrays" : { "empty" : [], "1" : [ 1 ], "12" : [1, 2], "123" : [1, 2, 3], "ofString" : [ ${strings.a}, ${strings.b}, ${strings.c} ] }, "booleans" : { "true" : true, "trueAgain" : ${booleans.true}, "false" : false, "falseAgain" : ${booleans.false} }, "nulls" : { "null" : null, "nullAgain" : ${nulls.null} } } config-1.3.1/config/src/test/resources/equiv01/unquoted.conf000066400000000000000000000013421277147274600240340ustar00rootroot00000000000000{ "ints" : { "fortyTwo" : 42, "fortyTwoAgain" : 42 }, "floats" : { "fortyTwoPointOne" : 42.1, "fortyTwoPointOneAgain" : 42.1 }, "strings" : { "abcd" : abcd, "abcdAgain" : abcd, "a" : a, "b" : b, "c" : c, "d" : d, "concatenated" : null bar 42 "baz" true 3.14 hi }, "arrays" : { "empty" : [], "1" : [ 1 ], "12" : [1, 2], "123" : [1, 2, 3], "ofString" : [a, b, c] }, "booleans" : { "true" : true, "trueAgain" : true, "false" : false, "falseAgain" : false }, "nulls" : { "null" : null, "nullAgain" : null } } config-1.3.1/config/src/test/resources/equiv02/000077500000000000000000000000001277147274600213225ustar00rootroot00000000000000config-1.3.1/config/src/test/resources/equiv02/original.json000066400000000000000000000004151277147274600240210ustar00rootroot00000000000000{ "a" : { "s" : 303, "b" : { "r" : 302, "c" : { "q" : 301, "d" : { "e" : 401, "a" : 1, "b" : 2, "z" : 102 } } } } } config-1.3.1/config/src/test/resources/equiv02/path-keys-weird-whitespace.conf000066400000000000000000000003551277147274600273430ustar00rootroot00000000000000{ a.b.c.d : { "a" : 1, "z" : 101 }, a.b.c : { "q" : 301 }, a.b : { "r" : 302 }, a : { "s" : 303 }, a.b.c.d : { "b" : 2, "z" : 102 }, a.b.c.d.e : 400, a.b.c.d.e : 401 } config-1.3.1/config/src/test/resources/equiv02/path-keys.conf000066400000000000000000000002661277147274600241020ustar00rootroot00000000000000{ a.b.c.d : { "a" : 1, "z" : 101 }, a.b.c : { "q" : 301 }, a.b : { "r" : 302 }, a : { "s" : 303 }, a.b.c.d : { "b" : 2, "z" : 102 }, a.b.c.d.e : 400, a.b.c.d.e : 401 } config-1.3.1/config/src/test/resources/equiv03/000077500000000000000000000000001277147274600213235ustar00rootroot00000000000000config-1.3.1/config/src/test/resources/equiv03/includes.conf000066400000000000000000000001631277147274600240000ustar00rootroot00000000000000letters { include "letters/a.conf" include "letters/b.json" include "letters/c" } include "root/foo.conf"config-1.3.1/config/src/test/resources/equiv03/letters/000077500000000000000000000000001277147274600230055ustar00rootroot00000000000000config-1.3.1/config/src/test/resources/equiv03/letters/a.conf000066400000000000000000000001031277147274600240660ustar00rootroot00000000000000numbers { include "numbers/1.conf" include "numbers/2" } a=ok config-1.3.1/config/src/test/resources/equiv03/letters/b.json000066400000000000000000000000201277147274600241110ustar00rootroot00000000000000{ "b" : 507 }config-1.3.1/config/src/test/resources/equiv03/letters/c.conf000066400000000000000000000000171277147274600240740ustar00rootroot00000000000000c.fromConf=89 config-1.3.1/config/src/test/resources/equiv03/letters/c.properties000066400000000000000000000000201277147274600253350ustar00rootroot00000000000000c.fromProp=true config-1.3.1/config/src/test/resources/equiv03/letters/numbers/000077500000000000000000000000001277147274600244605ustar00rootroot00000000000000config-1.3.1/config/src/test/resources/equiv03/letters/numbers/1.conf000066400000000000000000000000061277147274600254630ustar00rootroot00000000000000"1"=1 config-1.3.1/config/src/test/resources/equiv03/letters/numbers/2.properties000066400000000000000000000000071277147274600267340ustar00rootroot000000000000002=abcd config-1.3.1/config/src/test/resources/equiv03/original.json000066400000000000000000000004071277147274600240230ustar00rootroot00000000000000{ "letters" : { "numbers" : { "2" : "abcd", "1" : 1 }, "a" : "ok", "b" : 507, "c" : { "fromConf" : 89, "fromProp" : "true" } }, "root" : "this gets included at the root" } config-1.3.1/config/src/test/resources/equiv03/root/000077500000000000000000000000001277147274600223065ustar00rootroot00000000000000config-1.3.1/config/src/test/resources/equiv03/root/foo.conf000066400000000000000000000000461277147274600237400ustar00rootroot00000000000000root="this gets included at the root" config-1.3.1/config/src/test/resources/equiv04/000077500000000000000000000000001277147274600213245ustar00rootroot00000000000000config-1.3.1/config/src/test/resources/equiv04/missing-substitutions.conf000066400000000000000000000001041277147274600265740ustar00rootroot00000000000000a=${?NOT_DEFINED_ANYWHERE} b=${?also.not.defined.anywhere} c=${?a} config-1.3.1/config/src/test/resources/equiv04/original.json000066400000000000000000000000051277147274600240160ustar00rootroot00000000000000{ } config-1.3.1/config/src/test/resources/equiv05/000077500000000000000000000000001277147274600213255ustar00rootroot00000000000000config-1.3.1/config/src/test/resources/equiv05/original.json000066400000000000000000000005041277147274600240230ustar00rootroot00000000000000{ "a" : "hello", "b" : "hello\nworld", "b1" : "hello\\nworld", "c" : " \n hello \n world \n ", "d" : " \"hello\" ", "e" : "\"hello\"", "f1" : "hello\"", "f2" : "hello\"\"", "f3" : "hello\"\"\"", "f4" : "hello\"\"\"\"", "g1" : "\"hello", "g2" : "\"\"hello", "h" : "\"\"", "i" : "foo\"\"bar" } config-1.3.1/config/src/test/resources/equiv05/triple-quotes.conf000066400000000000000000000005061277147274600250120ustar00rootroot00000000000000{ a : """hello""", b : """hello world""", b1 : """hello\nworld""", c : """ hello world """, d : """ "hello" """, e : """"hello"""", f1 : """hello"""", f2 : """hello""""", f3 : """hello"""""", f4 : """hello""""""", g1 : """"hello""", g2 : """""hello""", h : """""""", i : """foo""bar""" } config-1.3.1/config/src/test/resources/file-include.conf000066400000000000000000000002141277147274600232330ustar00rootroot00000000000000base=41 # included without file() in a subdir include "subdir/foo.conf" # included using file() in a subdir include file("subdir/baz.conf") config-1.3.1/config/src/test/resources/include-from-list.conf000066400000000000000000000002241277147274600242310ustar00rootroot00000000000000// The {} inside the [] is needed because // just [ include ] means an array with the // string "include" in it. a = [ { include "test01.conf" } ] config-1.3.1/config/src/test/resources/onclasspath.conf000066400000000000000000000001211277147274600232070ustar00rootroot00000000000000# this is just a file on the classpath that we ensure we can load onclasspath=42 config-1.3.1/config/src/test/resources/subdir/000077500000000000000000000000001277147274600213175ustar00rootroot00000000000000config-1.3.1/config/src/test/resources/subdir/bar-file.conf000066400000000000000000000000141277147274600236420ustar00rootroot00000000000000bar-file=44 config-1.3.1/config/src/test/resources/subdir/bar.conf000066400000000000000000000000071277147274600227270ustar00rootroot00000000000000bar=43 config-1.3.1/config/src/test/resources/subdir/baz.conf000066400000000000000000000000071277147274600227370ustar00rootroot00000000000000baz=45 config-1.3.1/config/src/test/resources/subdir/foo.conf000066400000000000000000000001521277147274600227470ustar00rootroot00000000000000foo=42 # included without file() include "bar.conf" # included using file() include file("bar-file.conf") config-1.3.1/config/src/test/resources/test01.conf000066400000000000000000000045031277147274600220200ustar00rootroot00000000000000{ "ints" : { "fortyTwo" : 42, "fortyTwoAgain" : ${ints.fortyTwo} }, "floats" : { "fortyTwoPointOne" : 42.1, "fortyTwoPointOneAgain" : ${floats.fortyTwoPointOne}, "pointThirtyThree": .33 "pointThirtyThreeAgain": ${floats.pointThirtyThree} }, "strings" : { "abcd" : "abcd", "abcdAgain" : ${strings.a}${strings.b}${strings.c}${strings.d}, "a" : "a", "b" : "b", "c" : "c", "d" : "d", "concatenated" : null bar 42 baz true 3.14 hi, "double" : "3.14", "doubleStartingWithDot": ".33", "number" : "57", "null" : "null", "true" : "true", "yes" : "yes", "false" : "false", "no" : "no" }, "arrays" : { "empty" : [], "ofInt" : [1, 2, 3], "ofString" : [ ${strings.a}, ${strings.b}, ${strings.c} ], "ofDouble" : [3.14, 4.14, 5.14], "ofNull" : [null, null, null], "ofBoolean" : [true, false], "ofArray" : [${arrays.ofString}, ${arrays.ofString}, ${arrays.ofString}], "ofObject" : [${ints}, ${booleans}, ${strings}], "firstElementNotASubst" : [ "a", ${strings.b} ] }, "booleans" : { "true" : true, "trueAgain" : ${booleans.true}, "false" : false, "falseAgain" : ${booleans.false} }, "nulls" : { "null" : null, "nullAgain" : ${nulls.null} }, "durations" : { "second" : 1s, "secondsList" : [1s,2seconds,3 s, 4000], "secondAsNumber" : 1000, "halfSecond" : 0.5s, "millis" : 1 milli, "micros" : 2000 micros, "largeNanos" : 4878955355435272204ns, "plusLargeNanos" : "+4878955355435272204ns", "minusLargeNanos" : -4878955355435272204ns }, "memsizes" : { "meg" : 1M, "megsList" : [1M, 1024K, 1048576], "megAsNumber" : 1048576, "halfMeg" : 0.5M }, "system" : { "javaversion" : ${?java.version}, "userhome" : ${?user.home}, "home" : ${?HOME}, "pwd" : ${?PWD}, "shell" : ${?SHELL}, "lang" : ${?LANG}, "path" : ${?PATH}, "not_here" : ${?NOT_HERE}, "concatenated" : Your Java version is ${?system.javaversion} and your user.home is ${?system.userhome} } } config-1.3.1/config/src/test/resources/test01.json000066400000000000000000000000561277147274600220430ustar00rootroot00000000000000{ "fromJson1" : 1, "fromJsonA" : "A" }config-1.3.1/config/src/test/resources/test01.properties000066400000000000000000000001111277147274600232560ustar00rootroot00000000000000# .properties file fromProps.abc=abc fromProps.one=1 fromProps.bool=true config-1.3.1/config/src/test/resources/test02.conf000066400000000000000000000004171277147274600220210ustar00rootroot00000000000000{ "" : { "" : { "" : 42 } }, "42_a" : ${""."".""}, "42_b" : ${ "".""."" }, "a" : { "b" : { "c" : 57 } }, "57_a" : ${a.b.c}, "57_b" : ${"a"."b"."c"}, "a.b.c" : 103, "103_a" : ${"a.b.c"}, "a-c" : 259, "a_c" : 260, "-" : 261 } config-1.3.1/config/src/test/resources/test03-included.conf000066400000000000000000000003601277147274600236040ustar00rootroot00000000000000# this file is used to include in test03.conf foo="This is in the included file" # foo is in this file, so "a" should always be our local ${foo} a=${foo} # bar is only in the parent file, so "b" should always be the root ${bar} b=${bar} config-1.3.1/config/src/test/resources/test03.conf000066400000000000000000000014341277147274600220220ustar00rootroot00000000000000{ "test01" : { "ints" : 12, include "test01", "booleans" : 42 }, "test02" : { include "test02.conf" }, "equiv01" : { include "equiv01/original.json" }, # missing includes are supposed to be silently ignored nonexistent { include "nothere" include "nothere.conf" include "nothere.json" include "nothere.properties" } # make sure included file substitutions fall back to parent file, # both when the include is at the root (so doesn't need to have # substitutions adjusted) and when it is not. foo="This is in the including file" bar="This is in the including file" include "test03-included.conf" subtree { include "test03-included.conf" } } config-1.3.1/config/src/test/resources/test04.conf000066400000000000000000000431741277147274600220320ustar00rootroot00000000000000# This is an unmodified akka-reference.conf, except for this # comment and quoting one key that contained slashes. # Note: the outer akka{} would not be used in the usual setup # with this config lib. ############################## # Akka Reference Config File # ############################## # This the reference config file has all the default settings. # All these could be removed with no visible effect. # Modify as needed. # This file is imported in the 'akka.conf' file. Make your edits/overrides there. akka { version = "2.0-SNAPSHOT" # Akka version, checked against the runtime version of Akka. enabled-modules = [] # Comma separated list of the enabled modules. Options: ["cluster", "camel", "http"] time-unit = "seconds" # Time unit for all timeout properties throughout the config event-handlers = ["akka.event.Logging$DefaultLogger"] # Event handlers to register at boot time (Logging$DefaultLogger logs to STDOUT) loglevel = "WARNING" # Options: ERROR, WARNING, INFO, DEBUG # this level is used by the configured loggers (see "event-handlers") as soon # as they have been started; before that, see "stdout-loglevel" stdout-loglevel = "WARNING" # Loglevel for the very basic logger activated during AkkaApplication startup event-handler-dispatcher { type = "Dispatcher" # Must be one of the following # Dispatcher, (BalancingDispatcher, only valid when all actors using it are of the same type), # A FQCN to a class inheriting MessageDispatcherConfigurator with a no-arg visible constructor name = "EventHandlerDispatcher" # Optional, will be a generated UUID if omitted keep-alive-time = 60 # Keep alive time for threads core-pool-size = 1 # No of core threads max-pool-size = 8 # Max no of threads executor-bounds = -1 # Makes the Executor bounded, -1 is unbounded task-queue-size = -1 # Specifies the bounded capacity of the task queue (< 1 == unbounded) task-queue-type = "linked" # Specifies which type of task queue will be used, can be "array" or "linked" (default) allow-core-timeout = on # Allow core threads to time out rejection-policy = "caller-runs" # abort, caller-runs, discard-oldest, discard throughput = 5 # Throughput for Dispatcher, set to 1 for complete fairness throughput-deadline-time = -1 # Throughput deadline for Dispatcher, set to 0 or negative for no deadline mailbox-capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default) # If positive then a bounded mailbox is used and the capacity is set using the property # NOTE: setting a mailbox to 'blocking' can be a bit dangerous, could lead to deadlock, use with care # The following are only used for Dispatcher and only if mailbox-capacity > 0 mailbox-push-timeout-time = 10 # Specifies the timeout to add a new message to a mailbox that is full - negative number means infinite timeout # (in unit defined by the time-unit property) } # These boot classes are loaded (and created) automatically when the Akka Microkernel boots up # Can be used to bootstrap your application(s) # Should be the FQN (Fully Qualified Name) of the boot class which needs to have a default constructor # boot = ["sample.camel.Boot", # "sample.rest.java.Boot", # "sample.rest.scala.Boot", # "sample.security.Boot"] boot = [] actor { timeout = 5 # Default timeout for Future based invocations # - Actor: ask && ? # - UntypedActor: ask # - TypedActor: methods with non-void return type serialize-messages = off # Does a deep clone of (non-primitive) messages to ensure immutability throughput = 5 # Default throughput for all Dispatcher, set to 1 for complete fairness throughput-deadline-time = -1 # Default throughput deadline for all Dispatcher, set to 0 or negative for no deadline dispatcher-shutdown-timeout = 1 # Using the akka.time-unit, how long dispatchers by default will wait for new actors until they shut down deployment { "/app/service-ping" { # deployment id pattern router = "round-robin" # routing (load-balance) scheme to use # available: "direct", "round-robin", "random", "scatter-gather" # "least-cpu", "least-ram", "least-messages" # or: fully qualified class name of the router class # default is "direct"; # if 'replication' is used then the only available router is "direct" nr-of-instances = 3 # number of actor instances in the cluster # available: positive integer (1-N) or the string "auto" for auto-scaling # default is '1' # if the "direct" router is used then this element is ignored (always '1') #create-as { # class = "com.biz.app.MyActor" # FIXME document 'create-as' #} remote { nodes = ["wallace:2552", "gromit:2552"] # A list of hostnames and ports for instantiating the remote actor instances # The format should be on "hostname:port", where: # - hostname can be either hostname or IP address the remote actor should connect to # - port should be the port for the remote server on the other node } #cluster { # defines the actor as a clustered actor # default (if omitted) is local non-clustered actor # preferred-nodes = ["node:node1"] # a list of preferred nodes for instantiating the actor instances on # defined as node name # available: "node:" # replication { # use replication or not? only makes sense for a stateful actor # FIXME should we have this config option here? If so, implement it all through. # serialize-mailbox = off # should the actor mailbox be part of the serialized snapshot? # default is 'off' # storage = "transaction-log" # storage model for replication # available: "transaction-log" and "data-grid" # default is "transaction-log" # strategy = "write-through" # guaranteees for replication # available: "write-through" and "write-behind" # default is "write-through" # } #} } } default-dispatcher { type = "Dispatcher" # Must be one of the following # Dispatcher, (BalancingDispatcher, only valid when all actors using it are of the same type), # A FQCN to a class inheriting MessageDispatcherConfigurator with a no-arg visible constructor name = "MyDispatcher" # Optional, will be a generated UUID if omitted keep-alive-time = 60 # Keep alive time for threads core-pool-size-factor = 8.0 # No of core threads ... ceil(available processors * factor) max-pool-size-factor = 8.0 # Max no of threads ... ceil(available processors * factor) executor-bounds = -1 # Makes the Executor bounded, -1 is unbounded task-queue-size = -1 # Specifies the bounded capacity of the task queue (< 1 == unbounded) task-queue-type = "linked" # Specifies which type of task queue will be used, can be "array" or "linked" (default) allow-core-timeout = on # Allow core threads to time out rejection-policy = "caller-runs" # abort, caller-runs, discard-oldest, discard throughput = 5 # Throughput for Dispatcher, set to 1 for complete fairness throughput-deadline-time = -1 # Throughput deadline for Dispatcher, set to 0 or negative for no deadline mailbox-capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default) # If positive then a bounded mailbox is used and the capacity is set using the property # NOTE: setting a mailbox to 'blocking' can be a bit dangerous, could lead to deadlock, use with care # The following are only used for Dispatcher and only if mailbox-capacity > 0 mailbox-push-timeout-time = 10 # Specifies the timeout to add a new message to a mailbox that is full - negative number means infinite timeout # (in unit defined by the time-unit property) } debug { receive = off # enable function of Actor.loggable(), which is to log any received message at DEBUG level autoreceive = off # enable DEBUG logging of all AutoReceiveMessages (Kill, PoisonPill and the like) lifecycle = off # enable DEBUG logging of actor lifecycle changes } mailbox { file-based { directory-path = "./_mb" max-items = 2147483647 max-size = 2147483647 max-items = 2147483647 max-age = 0 max-journal-size = 16777216 # 16 * 1024 * 1024 max-memory-size = 134217728 # 128 * 1024 * 1024 max-journal-overflow = 10 max-journal-size-absolute = 9223372036854775807 discard-old-when-full = on keep-journal = on sync-journal = off } redis { hostname = "127.0.0.1" port = 6379 } mongodb { # Any specified collection name will be used as a prefix for collections that use durable mongo mailboxes uri = "mongodb://localhost/akka.mailbox" # Follow Mongo URI Spec - http://www.mongodb.org/display/DOCS/Connections # Configurable timeouts for certain ops timeout { read = 3000 # number of milliseconds to wait for a read to succeed before timing out the future write = 3000 # number of milliseconds to wait for a write to succeed before timing out the future } } zookeeper { server-addresses = "localhost:2181" session-timeout = 60 connection-timeout = 60 blocking-queue = on } beanstalk { hostname = "127.0.0.1" port = 11300 reconnect-window = 5 message-submit-delay = 0 message-submit-timeout = 5 message-time-to-live = 120 } } # Entries for pluggable serializers and their bindings. If a binding for a specific class is not found, # then the default serializer (Java serialization) is used. # # serializers { # java = "akka.serialization.JavaSerializer" # proto = "akka.testing.ProtobufSerializer" # sjson = "akka.testing.SJSONSerializer" # default = "akka.serialization.JavaSerializer" # } # serialization-bindings { # java = ["akka.serialization.SerializeSpec$Address", # "akka.serialization.MyJavaSerializableActor", # "akka.serialization.MyStatelessActorWithMessagesInMailbox", # "akka.serialization.MyActorWithProtobufMessagesInMailbox"] # sjson = ["akka.serialization.SerializeSpec$Person"] # proto = ["com.google.protobuf.Message", # "akka.actor.ProtobufProtocol$MyMessage"] # } } remote { # FIXME rename to transport layer = "akka.cluster.netty.NettyRemoteSupport" secure-cookie = "" # Generate your own with '$AKKA_HOME/scripts/generate_config_with_secure_cookie.sh' # or using 'akka.util.Crypt.generateSecureCookie' remote-daemon-ack-timeout = 30 # Timeout for ACK of cluster operations, lik checking actor out etc. use-passive-connections = on # Reuse inbound connections for outbound messages failure-detector { # accrual failure detection config threshold = 8 # defines the failure detector threshold # A low threshold is prone to generate many wrong suspicions but ensures a # quick detection in the event of a real crash. Conversely, a high threshold # generates fewer mistakes but needs more time to detect actual crashes max-sample-size = 1000 } server { port = 2552 # The default remote server port clients should connect to. Default is 2552 (AKKA) message-frame-size = 1048576 # Increase this if you want to be able to send messages with large payloads connection-timeout = 120 # Length in time-unit require-cookie = off # Should the remote server require that it peers share the same secure-cookie (defined in the 'remote' section)? untrusted-mode = off # Enable untrusted mode for full security of server managed actors, allows untrusted clients to connect. backlog = 4096 # Sets the size of the connection backlog } client { buffering { retry-message-send-on-failure = false # Should message buffering on remote client error be used (buffer flushed on successful reconnect) capacity = -1 # If negative (or zero) then an unbounded mailbox is used (default) # If positive then a bounded mailbox is used and the capacity is set using the property } reconnect-delay = 5 read-timeout = 3600 message-frame-size = 1048576 reap-futures-delay = 5 reconnection-time-window = 600 # Maximum time window that a client should try to reconnect for } } cluster { name = "test-cluster" zookeeper-server-addresses = "localhost:2181" # comma-separated list of ':' elements max-time-to-wait-until-connected = 30 session-timeout = 60 connection-timeout = 60 include-ref-node-in-replica-set = on # Can a replica be instantiated on the same node as the cluster reference to the actor # Default: on log-directory = "_akka_cluster" # Where ZooKeeper should store the logs and data files replication { digest-type = "MAC" # Options: CRC32 (cheap & unsafe), MAC (expensive & secure using password) password = "secret" # FIXME: store open in file? ensemble-size = 3 quorum-size = 2 snapshot-frequency = 1000 # The number of messages that should be logged between every actor snapshot timeout = 30 # Timeout for asyncronous (write-behind) operations } } stm { fair = on # Should global transactions be fair or non-fair (non fair yield better performance) max-retries = 1000 timeout = 5 # Default timeout for blocking transactions and transaction set (in unit defined by # the time-unit property) write-skew = true blocking-allowed = false interruptible = false speculative = true quick-release = true propagation = "requires" trace-level = "none" } test { timefactor = "1.0" # factor by which to scale timeouts during tests, e.g. to account for shared build system load filter-leeway = 3 # time-units EventFilter.intercept waits after the block is finished until all required messages are received single-expect-default = 3 # time-units to wait in expectMsg and friends outside of within() block by default } } config-1.3.1/config/src/test/resources/test05.conf000066400000000000000000000103561277147274600220270ustar00rootroot00000000000000# This is the main configuration file for the application. # ~~~~~ application.name=Yet Another Blog Engine # Configuration of the blog engine # ~~~~~ blog.title=Yet another blog blog.baseline=We will write about nothing # Application mode # ~~~~~ # Set to dev to enable instant reloading and other development help. # Otherwise set to prod. application.mode=dev %prod.application.mode=prod # Secret key # ~~~~~ # The secret key is used to secure cryptographics functions # If you deploy your application to several instances be sure to use the same key ! application.secret=s1kwayg211q9v4387pvarbmyqnht7hrl54d34lsz0yh9btb117br293a25trz31o # Additional modules # ~~~~~ # A module is another play! application. Add a line for each module you want # to add to your application. Modules path are either absolutes or relative to # the application root. # Import the crud module module.crud=${?play.path}/modules/crud # Import the secure module module.secure=${?play.path}/modules/secure # Import the cobertura module in test mode #%test.module.cobertura=${play.path}/modules/cobertura # i18n # ~~~~~ # Define locales used by your application. # You can then place localized messages in conf/messages.{locale} files # application.langs=fr,en,ja # Server configuration # ~~~~~ # If you need to change the HTTP port, uncomment this (default is set to 9000) # http.port=9000 # # By default the server listen for HTTP on the wilcard address. # You can restrict this. # http.address=127.0.0.1 # Session configuration # ~~~~~~~~~~~~~~~~~~~~~~ # By default, session will be written to the transient PLAY_SESSION cookie. # application.session.cookie=PLAY # application.session.maxAge=1h # JPDA configuration # ~~~~~ # Define which port is used by JPDA when application is in debug mode (default is set to 8000) # jpda.port=8000 # Log level # ~~~~~ # Specify log level for your application. # If you want a very customized log, create a log4j.properties file in the conf directory application.log=INFO # Database configuration # ~~~~~ # Enable a database engine if needed. # # To quickly set up a development database, use either: # - mem : for a transient in memory database (H2 in memory) # - fs : for a simple file written database (H2 file stored) db=mem # # To connect to a local MySQL5 database, use: # db=mysql:user:pwd@database_name # # If you need a full JDBC configuration use the following : # db.url=jdbc:postgresql:database_name # db.driver=org.postgresql.Driver # db.user=root # db.pass=secret # # Connections pool configuration : # db.pool.timeout=1000 # db.pool.maxSize=30 # db.pool.minSize=10 # # If you want to reuse an existing Datasource from your application server, use: # db=java:/comp/env/jdbc/myDatasource # JPA Configuration (Hibernate) # ~~~~~ # # Specify the custom JPA dialect to use here (default to guess): # jpa.dialect=org.hibernate.dialect.PostgreSQLDialect # # Specify the ddl generation pattern to use (default to update, set to none to disable it): jpa.ddl=update # # Debug SQL statements (logged using DEBUG level): # jpa.debugSQL=true # # You can even specify additional hibernate properties here: # hibernate.use_sql_comments=true # ... # Memcached configuration # ~~~~~ # Enable memcached if needed. Otherwise a local cache is used. # memcached=enabled # # Specify memcached host (default to 127.0.0.1:11211) # memcached.host=127.0.0.1:11211 # # Or you can specify multiple host to build a distributed cache # memcached.1.host=127.0.0.1:11211 # memcached.2.host=127.0.0.1:11212 # Mail configuration # ~~~~~ # Default is to use a mock Mailer mail.smtp=mock # Or, specify mail host configuration # mail.smtp.host=127.0.0.1 # mail.smtp.user=admin # mail.smtp.pass= # mail.smtp.channel=ssl # Execution pool # ~~~~~ # Default to 1 thread in DEV mode or nb processors + 1 threads in PROD mode. # Try to keep a low as possible. 1 thread will serialize all requests (very usefull for debugging purpose) # play.pool=3 # Open file from errors pages # ~~~~~ # If your text editor supports to open files using URL, Play! will link # error pages to files dynamically # # Example, for textmate: # play.editor=txmt://open?url=file://%s&line=%s # Testing. Set up a custom configuration for test mode # ~~~~~ %test.application.mode=dev %test.db=mem %test.jpa.ddl=create-drop %test.mail.smtp=mock config-1.3.1/config/src/test/resources/test06.conf000066400000000000000000000003401277147274600220200ustar00rootroot00000000000000## This file tests ConfigDelayedMerge and ConfigDelayedMergeObject a=1 b=2 c=3 d={ "foo" : "bar" } # merge some x's x=${a} x=${b} # merge some y's where we know one is an object y=${d} y={ "hello" : "world", "foo" : 10 } config-1.3.1/config/src/test/resources/test07.conf000066400000000000000000000001761277147274600220300ustar00rootroot00000000000000# This file tests including a classpath resource when # loading something as a file or as a resource include "test-lib.conf" config-1.3.1/config/src/test/resources/test08.conf000066400000000000000000000003421277147274600220240ustar00rootroot00000000000000# This file tests including a classpath resource when # loading something as a file or as a resource. # It specifies the classpath resource with a "/" in front, # while test07 specifies it "relative" include "/test-lib.conf" config-1.3.1/config/src/test/resources/test09.conf000066400000000000000000000003531277147274600220270ustar00rootroot00000000000000// This file is testing ConfigDelayedMergeObject and ConfigDelayedMerge x={ q : 10 } y=5 a=1 a.q.r.s=${b} a=${y} a=${x} a={ c : 3 } b=${x} b=${y} // nesting ConfigDelayed inside another one c=${x} c={ d : 600, e : ${a}, f : ${b} } config-1.3.1/config/src/test/resources/test10.conf000066400000000000000000000002341277147274600220150ustar00rootroot00000000000000// this checks relativizing ConfigDelayedMerge, ConfigDelayedMergeObject foo { include "test09.conf" } bar { nested { include "test09.conf" } } config-1.3.1/config/src/test/resources/test11.conf000066400000000000000000000002161277147274600220160ustar00rootroot00000000000000// this checks quoting of strings that could be numbers, when we render "10" = "42" "-10" = "-42" foo-bar = bar-baz "---" = "------" a- = b- config-1.3.1/config/src/test/resources/validate-invalid.conf000066400000000000000000000007131277147274600241140ustar00rootroot00000000000000string1="a string" string2=107 string3={ a : b } string4=[] int1=203 int2="foo" int3={ q : s } float1="the string" float2=false float3=[ 4, 5, 6 ] bool1=709 bool2="string!" bool3={} null1=10000 null2="hello world" null3=true object1={ z : s } object2=[] object3=12345 array1=[1,2,"foo"] array2=[7,8,9] array3=[{ n : m }, 10] array4=[42, 43] array5=64 emptyArray1=[1,2,3] emptyArray2=["a","b","c"] a.b.c.d.e.f.g = 100 a.b.c.d.e.f.h = "foo" a.b.c.d.e.f.i = [] config-1.3.1/config/src/test/resources/validate-reference.conf000066400000000000000000000007071277147274600244270ustar00rootroot00000000000000string1="foo" string2="bar" string3="baz" string4="hello" int1=10 int2=11 int3=12 float1=3.14 float2=3.2 float3=3.3 bool1=true bool2=false bool3=true null1=null null2=null null3=null object1={ a : b } object2={ c : d } object3={ e : f } array1=[1,2,3] array2=[a,b,c] array3=[true, true, false] array4=[{}, {}] array5=[] emptyArray1=[] emptyArray2=[] willBeMissing=90009 a.b.c.d.e.f.g = true a.b.c.d.e.f.h = true a.b.c.d.e.f.i = true a.b.c.d.e.f.j = true config-1.3.1/config/src/test/scala/000077500000000000000000000000001277147274600171005ustar00rootroot00000000000000config-1.3.1/config/src/test/scala/ApiExamples.scala000066400000000000000000000045351277147274600223240ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ import org.junit.Assert._ import org.junit._ import com.typesafe.config._ import scala.collection.JavaConverters._ import scala.collection.mutable import language.implicitConversions /** * This is to show how the API works and to be sure it's usable * from outside of the library's package and in Scala. * It isn't intended to be asserting anything or adding test coverage. */ class ApiExamples { @Test def readSomeConfig() { val conf = ConfigFactory.load("test01") // you don't have to write the types explicitly of course, // just doing that to show what they are. val a: Int = conf.getInt("ints.fortyTwo") val child: Config = conf.getConfig("ints") val b: Int = child.getInt("fortyTwo") val ms: Long = conf.getMilliseconds("durations.halfSecond") // a Config has an associated tree of values, with a ConfigObject // at the root. The ConfigObject implements java.util.Map val obj: ConfigObject = conf.root // this is how you do conf.getInt "manually" on the value tree, if you // were so inclined. (This is not a good approach vs. conf.getInt() above, // just showing how ConfigObject stores a tree of values.) val c: Int = obj.get("ints") .asInstanceOf[ConfigObject] .get("fortyTwo").unwrapped() .asInstanceOf[java.lang.Integer] // this is unfortunate; asScala creates a mutable map but // a ConfigObject is in fact immutable. val objAsScalaMap: mutable.Map[String, ConfigValue] = obj.asScala // this is sort of ugly, but you could improve it // with a Scala implicit wrapper, see below... val d: Int = conf.getAnyRef("ints.fortyTwo") match { case x: java.lang.Integer => x case x: java.lang.Long => x.intValue } // our implicit wrapper: do stuff like this to get a nicer Scala API class EnhancedConfig(c: Config) { def getAny(path: String): Any = c.getAnyRef(path) } implicit def config2enhanced(c: Config) = new EnhancedConfig(c) // somewhat nicer now val e: Int = conf.getAny("ints.fortyTwo") match { case x: Int => x case x: Long => x.intValue } assertEquals(42, e) } } config-1.3.1/config/src/test/scala/Profiling.scala000066400000000000000000000100531277147274600220350ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigException import java.util.concurrent.TimeUnit import scala.annotation.tailrec object Util { @tailrec def timeHelper(body: () => Unit, iterations: Int, retried: Boolean): Double = { // warm up for (i <- 1 to Math.max(20, iterations / 10)) { body() } val start = System.nanoTime() for (i <- 1 to iterations) { body() } val end = System.nanoTime() val elapsed = end - start val nanosInMillisecond = 1000000L if (elapsed < (1000 * nanosInMillisecond)) { System.err.println(s"Total time for $iterations was less than a second; trying with more iterations") timeHelper(body, iterations * 10, true) } else { if (retried) System.out.println(s"with $iterations we got a long enough sample (${elapsed.toDouble / nanosInMillisecond}ms)") (elapsed.toDouble / iterations) / nanosInMillisecond } } def time(body: () => Unit, iterations: Int): Double = { timeHelper(body, iterations, false) } def loop(args: Seq[String], body: () => Unit) { if (args.contains("-loop")) { println("looping; ctrl+C to escape") while (true) { body() } } } } object FileLoad extends App { def task() { val conf = ConfigFactory.load("test04") if (!"2.0-SNAPSHOT".equals(conf.getString("akka.version"))) { throw new Exception("broken file load") } } val ms = Util.time(task, 4000) println("file load: " + ms + "ms") Util.loop(args, task) } object Resolve extends App { val conf = ConfigFactory.load("test02") def task() { conf.resolve() if (conf.getInt("103_a") != 103) { throw new Exception("broken file load") } } val ms = Util.time(task, 3000000) println("resolve: " + ms + "ms") Util.loop(args, task) } object GetExistingPath extends App { val conf = ConfigFactory.parseString("aaaaa.bbbbb.ccccc.d=42").resolve() def task() { if (conf.getInt("aaaaa.bbbbb.ccccc.d") != 42) { throw new Exception("broken get") } } val ms = Util.time(task, 2000000) println("GetExistingPath: " + ms + "ms") Util.loop(args, task) } object GetSeveralExistingPaths extends App { val conf = ConfigFactory.parseString("aaaaa { bbbbb.ccccc.d=42, qqqqq.rrrrr = 43 }, xxxxx.yyyyy.zzzzz = 44 ").resolve() def task() { if (conf.getInt("aaaaa.bbbbb.ccccc.d") != 42 || conf.getInt("aaaaa.qqqqq.rrrrr") != 43 || conf.getInt("xxxxx.yyyyy.zzzzz") != 44) { throw new Exception("broken get") } } val ms = Util.time(task, 5000000) println("GetSeveralExistingPaths: " + ms + "ms") Util.loop(args, task) } object HasPathOnMissing extends App { val conf = ConfigFactory.parseString("aaaaa.bbbbb.ccccc.d=42,x=10, y=11, z=12").resolve() def task() { if (conf.hasPath("aaaaa.bbbbb.ccccc.e")) { throw new Exception("we shouldn't have this path") } } val ms = Util.time(task, 20000000) println("HasPathOnMissing: " + ms + "ms") Util.loop(args, task) } object CatchExceptionOnMissing extends App { val conf = ConfigFactory.parseString("aaaaa.bbbbb.ccccc.d=42,x=10, y=11, z=12").resolve() def anotherStackFrame(remaining: Int)(body: () => Unit): Int = { if (remaining == 0) { body() 123 } else { 42 + anotherStackFrame(remaining - 1)(body) } } def task() { try conf.getInt("aaaaa.bbbbb.ccccc.e") catch { case e: ConfigException.Missing => } } anotherStackFrame(40) { () => val ms = Util.time(task, 300000) println("CatchExceptionOnMissing: " + ms + "ms") Util.loop(args, task) } } config-1.3.1/config/src/test/scala/Rendering.scala000066400000000000000000000052331277147274600220250ustar00rootroot00000000000000import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigRenderOptions object RenderExample extends App { val formatted = args.contains("--formatted") val originComments = args.contains("--origin-comments") val comments = args.contains("--comments") val hocon = args.contains("--hocon") val options = ConfigRenderOptions.defaults() .setFormatted(formatted) .setOriginComments(originComments) .setComments(comments) .setJson(!hocon) def render(what: String) { val conf = ConfigFactory.defaultOverrides() .withFallback(ConfigFactory.parseResourcesAnySyntax(classOf[ConfigFactory], "/" + what)) .withFallback(ConfigFactory.defaultReference()) println("=== BEGIN UNRESOLVED toString() " + what) print(conf.root.toString()) println("=== END UNRESOLVED toString() " + what) println("=== BEGIN UNRESOLVED " + what) print(conf.root.render(options)) println("=== END UNRESOLVED " + what) println("=== BEGIN RESOLVED " + what) print(conf.resolve().root.render(options)) println("=== END RESOLVED " + what) } render("test01") render("test06") render("test05") } object RenderOptions extends App { val conf = ConfigFactory.parseString( """ foo=[1,2,3] # comment1 bar { a = 42 #comment2 b = { c = "hello", d = true } # comment3 e = ${something} f = {} } """) // ah, efficiency def allBooleanLists(length: Int): Seq[Seq[Boolean]] = { if (length == 0) { Seq(Nil) } else { val tails = allBooleanLists(length - 1) (tails map { false +: _ }) ++ (tails map { true +: _ }) } } val rendered = allBooleanLists(4).foldLeft(0) { (count, values) => val formatted = values(0) val originComments = values(1) val comments = values(2) val json = values(3) val options = ConfigRenderOptions.defaults() .setFormatted(formatted) .setOriginComments(originComments) .setComments(comments) .setJson(json) val renderSpec = options.toString.replace("ConfigRenderOptions", "") println("=== " + count + " RENDER WITH " + renderSpec + "===") print(conf.root.render(options)) println("=== " + count + " END RENDER WITH " + renderSpec + "===") count + 1 } println("Rendered " + rendered + " option combinations") } config-1.3.1/config/src/test/scala/com/000077500000000000000000000000001277147274600176565ustar00rootroot00000000000000config-1.3.1/config/src/test/scala/com/typesafe/000077500000000000000000000000001277147274600214765ustar00rootroot00000000000000config-1.3.1/config/src/test/scala/com/typesafe/config/000077500000000000000000000000001277147274600227435ustar00rootroot00000000000000config-1.3.1/config/src/test/scala/com/typesafe/config/impl/000077500000000000000000000000001277147274600237045ustar00rootroot00000000000000config-1.3.1/config/src/test/scala/com/typesafe/config/impl/ConcatenationTest.scala000066400000000000000000000433531277147274600303460ustar00rootroot00000000000000/** * Copyright (C) 2012 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ import com.typesafe.config.ConfigValue import com.typesafe.config.ConfigException import com.typesafe.config.ConfigResolveOptions import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import scala.collection.JavaConverters._ class ConcatenationTest extends TestUtils { @Test def noSubstitutionsStringConcat() { val conf = parseConfig(""" a : true "xyz" 123 foo """).resolve() assertEquals("true xyz 123 foo", conf.getString("a")) } @Test def trivialStringConcat() { val conf = parseConfig(""" a : ${x}foo, x = 1 """).resolve() assertEquals("1foo", conf.getString("a")) } @Test def twoSubstitutionsStringConcat() { val conf = parseConfig(""" a : ${x}foo${x}, x = 1 """).resolve() assertEquals("1foo1", conf.getString("a")) } @Test def stringConcatCannotSpanLines() { val e = intercept[ConfigException.Parse] { parseConfig(""" a : ${x} foo, x = 1 """) } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("not be followed") && e.getMessage.contains("','")) } @Test def noObjectsInStringConcat() { val e = intercept[ConfigException.WrongType] { parseConfig(""" a : abc { x : y } """) } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("Cannot concatenate") && e.getMessage.contains("abc") && e.getMessage.contains("""{"x":"y"}""")) } @Test def noObjectConcatWithNull() { val e = intercept[ConfigException.WrongType] { parseConfig(""" a : null { x : y } """) } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("Cannot concatenate") && e.getMessage.contains("null") && e.getMessage.contains("""{"x":"y"}""")) } @Test def noArraysInStringConcat() { val e = intercept[ConfigException.WrongType] { parseConfig(""" a : abc [1, 2] """) } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("Cannot concatenate") && e.getMessage.contains("abc") && e.getMessage.contains("[1,2]")) } @Test def noObjectsSubstitutedInStringConcat() { val e = intercept[ConfigException.WrongType] { parseConfig(""" a : abc ${x}, x : { y : z } """).resolve() } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("Cannot concatenate") && e.getMessage.contains("abc")) } @Test def noArraysSubstitutedInStringConcat() { val e = intercept[ConfigException.WrongType] { parseConfig(""" a : abc ${x}, x : [1,2] """).resolve() } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("Cannot concatenate") && e.getMessage.contains("abc")) } @Test def noSubstitutionsListConcat() { val conf = parseConfig(""" a : [1,2] [3,4] """) assertEquals(Seq(1, 2, 3, 4), conf.getList("a").unwrapped().asScala) } @Test def listConcatWithSubstitutions() { val conf = parseConfig(""" a : ${x} [3,4] ${y}, x : [1,2], y : [5,6] """).resolve() assertEquals(Seq(1, 2, 3, 4, 5, 6), conf.getList("a").unwrapped().asScala) } @Test def listConcatSelfReferential() { val conf = parseConfig(""" a : [1, 2], a : ${a} [3,4], a : ${a} [5,6] """).resolve() assertEquals(Seq(1, 2, 3, 4, 5, 6), conf.getList("a").unwrapped().asScala) } @Test def noSubstitutionsListConcatCannotSpanLines() { val e = intercept[ConfigException.Parse] { parseConfig(""" a : [1,2] [3,4] """) } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("expecting") && e.getMessage.contains("'['")) } @Test def listConcatCanSpanLinesInsideBrackets() { val conf = parseConfig(""" a : [1,2 ] [3,4] """) assertEquals(Seq(1, 2, 3, 4), conf.getList("a").unwrapped().asScala) } @Test def noSubstitutionsObjectConcat() { val conf = parseConfig(""" a : { b : c } { x : y } """) assertEquals(Map("b" -> "c", "x" -> "y"), conf.getObject("a").unwrapped().asScala) } @Test def objectConcatMergeOrder() { val conf = parseConfig(""" a : { b : 1 } { b : 2 } { b : 3 } { b : 4 } """) assertEquals(4, conf.getInt("a.b")) } @Test def objectConcatWithSubstitutions() { val conf = parseConfig(""" a : ${x} { b : 1 } ${y}, x : { a : 0 }, y : { c : 2 } """).resolve() assertEquals(Map("a" -> 0, "b" -> 1, "c" -> 2), conf.getObject("a").unwrapped().asScala) } @Test def objectConcatSelfReferential() { val conf = parseConfig(""" a : { a : 0 }, a : ${a} { b : 1 }, a : ${a} { c : 2 } """).resolve() assertEquals(Map("a" -> 0, "b" -> 1, "c" -> 2), conf.getObject("a").unwrapped().asScala) } @Test def objectConcatSelfReferentialOverride() { val conf = parseConfig(""" a : { b : 3 }, a : { b : 2 } ${a} """).resolve() assertEquals(Map("b" -> 3), conf.getObject("a").unwrapped().asScala) } @Test def noSubstitutionsObjectConcatCannotSpanLines() { val e = intercept[ConfigException.Parse] { parseConfig(""" a : { b : c } { x : y }""") } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("expecting") && e.getMessage.contains("'{'")) } @Test def objectConcatCanSpanLinesInsideBraces() { val conf = parseConfig(""" a : { b : c } { x : y } """) assertEquals(Map("b" -> "c", "x" -> "y"), conf.getObject("a").unwrapped().asScala) } @Test def stringConcatInsideArrayValue() { val conf = parseConfig(""" a : [ foo bar 10 ] """) assertEquals(Seq("foo bar 10"), conf.getStringList("a").asScala) } @Test def stringNonConcatInsideArrayValue() { val conf = parseConfig(""" a : [ foo bar 10 ] """) assertEquals(Seq("foo", "bar", "10"), conf.getStringList("a").asScala) } @Test def objectConcatInsideArrayValue() { val conf = parseConfig(""" a : [ { b : c } { x : y } ] """) assertEquals(Seq(Map("b" -> "c", "x" -> "y")), conf.getObjectList("a").asScala.map(_.unwrapped().asScala)) } @Test def objectNonConcatInsideArrayValue() { val conf = parseConfig(""" a : [ { b : c } { x : y } ] """) assertEquals(Seq(Map("b" -> "c"), Map("x" -> "y")), conf.getObjectList("a").asScala.map(_.unwrapped().asScala)) } @Test def listConcatInsideArrayValue() { val conf = parseConfig(""" a : [ [1, 2] [3, 4] ] """) assertEquals(List(List(1, 2, 3, 4)), // well that's a little silly conf.getList("a").unwrapped().asScala.toList.map(_.asInstanceOf[java.util.List[_]].asScala.toList)) } @Test def listNonConcatInsideArrayValue() { val conf = parseConfig(""" a : [ [1, 2] [3, 4] ] """) assertEquals(List(List(1, 2), List(3, 4)), // well that's a little silly conf.getList("a").unwrapped().asScala.toList.map(_.asInstanceOf[java.util.List[_]].asScala.toList)) } @Test def stringConcatsAreKeys() { val conf = parseConfig(""" 123 foo : "value" """) assertEquals("value", conf.getString("123 foo")) } @Test def objectsAreNotKeys() { val e = intercept[ConfigException.Parse] { parseConfig("""{ { a : 1 } : "value" }""") } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("expecting a close parentheses") && e.getMessage.contains("'{'")) } @Test def arraysAreNotKeys() { val e = intercept[ConfigException.Parse] { parseConfig("""{ [ "a" ] : "value" }""") } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("expecting a close parentheses") && e.getMessage.contains("'['")) } @Test def emptyArrayPlusEquals() { val conf = parseConfig(""" a = [], a += 2 """).resolve() assertEquals(Seq(2), conf.getIntList("a").asScala.toList) } @Test def missingArrayPlusEquals() { val conf = parseConfig(""" a += 2 """).resolve() assertEquals(Seq(2), conf.getIntList("a").asScala.toList) } @Test def shortArrayPlusEquals() { val conf = parseConfig(""" a = [1], a += 2 """).resolve() assertEquals(Seq(1, 2), conf.getIntList("a").asScala.toList) } @Test def numberPlusEquals() { val e = intercept[ConfigException.WrongType] { val conf = parseConfig(""" a = 10, a += 2 """).resolve() } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("Cannot concatenate") && e.getMessage.contains("10") && e.getMessage.contains("[2]")) } @Test def stringPlusEquals() { val e = intercept[ConfigException.WrongType] { parseConfig(""" a = abc, a += 2 """).resolve() } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("Cannot concatenate") && e.getMessage.contains("abc") && e.getMessage.contains("[2]")) } @Test def objectPlusEquals() { val e = intercept[ConfigException.WrongType] { parseConfig(""" a = { x : y }, a += 2 """).resolve() } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("Cannot concatenate") && e.getMessage.contains("\"x\":\"y\"") && e.getMessage.contains("[2]")) } @Test def plusEqualsNestedPath() { val conf = parseConfig(""" a.b.c = [1], a.b.c += 2 """).resolve() assertEquals(Seq(1, 2), conf.getIntList("a.b.c").asScala.toList) } @Test def plusEqualsNestedObjects() { val conf = parseConfig(""" a : { b : { c : [1] } }, a : { b : { c += 2 } }""").resolve() assertEquals(Seq(1, 2), conf.getIntList("a.b.c").asScala.toList) } @Test def plusEqualsSingleNestedObject() { val conf = parseConfig(""" a : { b : { c : [1], c += 2 } }""").resolve() assertEquals(Seq(1, 2), conf.getIntList("a.b.c").asScala.toList) } @Test def substitutionPlusEqualsSubstitution() { val conf = parseConfig(""" a = ${x}, a += ${y}, x = [1], y = 2 """).resolve() assertEquals(Seq(1, 2), conf.getIntList("a").asScala.toList) } @Test def plusEqualsMultipleTimes() { val conf = parseConfig(""" a += 1, a += 2, a += 3 """).resolve() assertEquals(Seq(1, 2, 3), conf.getIntList("a").asScala.toList) } @Test def plusEqualsMultipleTimesNested() { val conf = parseConfig(""" x { a += 1, a += 2, a += 3 } """).resolve() assertEquals(Seq(1, 2, 3), conf.getIntList("x.a").asScala.toList) } @Test def plusEqualsAnObjectMultipleTimes() { val conf = parseConfig(""" a += { b: 1 }, a += { b: 2 }, a += { b: 3 } """).resolve() assertEquals(Seq(1, 2, 3), conf.getObjectList("a").asScala.toList.map(_.toConfig.getInt("b"))) } @Test def plusEqualsAnObjectMultipleTimesNested() { val conf = parseConfig(""" x { a += { b: 1 }, a += { b: 2 }, a += { b: 3 } } """).resolve() assertEquals(Seq(1, 2, 3), conf.getObjectList("x.a").asScala.toList.map(_.toConfig.getInt("b"))) } // We would ideally make this case NOT throw an exception but we need to do some work // to get there, see https://github.com/typesafehub/config/issues/160 @Test def plusEqualsMultipleTimesNestedInArray() { val e = intercept[ConfigException.Parse] { val conf = parseConfig("""x = [ { a += 1, a += 2, a += 3 } ] """).resolve() assertEquals(Seq(1, 2, 3), conf.getObjectList("x").asScala.toVector(0).toConfig.getIntList("a").asScala.toList) } assertTrue(e.getMessage.contains("limitation")) } // We would ideally make this case NOT throw an exception but we need to do some work // to get there, see https://github.com/typesafehub/config/issues/160 @Test def plusEqualsMultipleTimesNestedInPlusEquals() { val e = intercept[ConfigException.Parse] { val conf = parseConfig("""x += { a += 1, a += 2, a += 3 } """).resolve() assertEquals(Seq(1, 2, 3), conf.getObjectList("x").asScala.toVector(0).toConfig.getIntList("a").asScala.toList) } assertTrue(e.getMessage.contains("limitation")) } // from https://github.com/typesafehub/config/issues/177 @Test def arrayConcatenationInDoubleNestedDelayedMerge() { val unresolved = parseConfig("""d { x = [] }, c : ${d}, c { x += 1, x += 2 }""") val conf = unresolved.resolve() assertEquals(Seq(1, 2), conf.getIntList("c.x").asScala) } // from https://github.com/typesafehub/config/issues/177 @Test def arrayConcatenationAsPartOfDelayedMerge() { val unresolved = parseConfig(""" c { x: [], x : ${c.x}[1], x : ${c.x}[2] }""") val conf = unresolved.resolve() assertEquals(Seq(1, 2), conf.getIntList("c.x").asScala) } // from https://github.com/typesafehub/config/issues/177 @Test def arrayConcatenationInDoubleNestedDelayedMerge2() { val unresolved = parseConfig("""d { x = [] }, c : ${d}, c { x : ${c.x}[1], x : ${c.x}[2] }""") val conf = unresolved.resolve() assertEquals(Seq(1, 2), conf.getIntList("c.x").asScala) } // from https://github.com/typesafehub/config/issues/177 @Test def arrayConcatenationInTripleNestedDelayedMerge() { val unresolved = parseConfig("""{ r: { d.x=[] }, q: ${r}, q : { d { x = [] }, c : ${q.d}, c { x : ${q.c.x}[1], x : ${q.c.x}[2] } } }""") val conf = unresolved.resolve() assertEquals(Seq(1, 2), conf.getIntList("q.c.x").asScala) } @Test def concatUndefinedSubstitutionWithString() { val conf = parseConfig("""a = foo${?bar}""").resolve() assertEquals("foo", conf.getString("a")) } @Test def concatDefinedOptionalSubstitutionWithString() { val conf = parseConfig("""bar=bar, a = foo${?bar}""").resolve() assertEquals("foobar", conf.getString("a")) } @Test def concatUndefinedSubstitutionWithArray() { val conf = parseConfig("""a = [1] ${?bar}""").resolve() assertEquals(Seq(1), conf.getIntList("a").asScala.toList) } @Test def concatDefinedOptionalSubstitutionWithArray() { val conf = parseConfig("""bar=[2], a = [1] ${?bar}""").resolve() assertEquals(Seq(1, 2), conf.getIntList("a").asScala.toList) } @Test def concatUndefinedSubstitutionWithObject() { val conf = parseConfig("""a = { x : "foo" } ${?bar}""").resolve() assertEquals("foo", conf.getString("a.x")) } @Test def concatDefinedOptionalSubstitutionWithObject() { val conf = parseConfig("""bar={ y : 42 }, a = { x : "foo" } ${?bar}""").resolve() assertEquals("foo", conf.getString("a.x")) assertEquals(42, conf.getInt("a.y")) } @Test def concatTwoUndefinedSubstitutions() { val conf = parseConfig("""a = ${?foo}${?bar}""").resolve() assertFalse("no field 'a'", conf.hasPath("a")) } @Test def concatSeveralUndefinedSubstitutions() { val conf = parseConfig("""a = ${?foo}${?bar}${?baz}${?woooo}""").resolve() assertFalse("no field 'a'", conf.hasPath("a")) } @Test def concatTwoUndefinedSubstitutionsWithASpace() { val conf = parseConfig("""a = ${?foo} ${?bar}""").resolve() assertEquals(" ", conf.getString("a")) } @Test def concatTwoDefinedSubstitutionsWithASpace() { val conf = parseConfig("""foo=abc, bar=def, a = ${foo} ${bar}""").resolve() assertEquals("abc def", conf.getString("a")) } @Test def concatTwoUndefinedSubstitutionsWithEmptyString() { val conf = parseConfig("""a = ""${?foo}${?bar}""").resolve() assertEquals("", conf.getString("a")) } @Test def concatSubstitutionsThatAreObjectsWithNoSpace() { val conf = parseConfig("""foo = { a : 1}, bar = { b : 2 }, x = ${foo}${bar}""").resolve() assertEquals(1, conf.getInt("x.a")) assertEquals(2, conf.getInt("x.b")) } // whitespace is insignificant if substitutions don't turn out to be a string @Test def concatSubstitutionsThatAreObjectsWithSpace() { val conf = parseConfig("""foo = { a : 1}, bar = { b : 2 }, x = ${foo} ${bar}""").resolve() assertEquals(1, conf.getInt("x.a")) assertEquals(2, conf.getInt("x.b")) } // whitespace is insignificant if substitutions don't turn out to be a string @Test def concatSubstitutionsThatAreListsWithSpace() { val conf = parseConfig("""foo = [1], bar = [2], x = ${foo} ${bar}""").resolve() assertEquals(List(1, 2), conf.getIntList("x").asScala) } // but quoted whitespace should be an error @Test def concatSubstitutionsThatAreObjectsWithQuotedSpace() { val e = intercept[ConfigException.WrongType] { parseConfig("""foo = { a : 1}, bar = { b : 2 }, x = ${foo}" "${bar}""").resolve() } } // but quoted whitespace should be an error @Test def concatSubstitutionsThatAreListsWithQuotedSpace() { val e = intercept[ConfigException.WrongType] { parseConfig("""foo = [1], bar = [2], x = ${foo}" "${bar}""").resolve() } } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/ConfParserTest.scala000066400000000000000000000746601277147274600276300ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ import java.io.{ File, StringReader } import com.typesafe.config._ import scala.collection.JavaConverters._ import java.net.URL import java.util.Properties class ConfParserTest extends TestUtils { def parseWithoutResolving(s: String) = { val options = ConfigParseOptions.defaults(). setOriginDescription("test conf string"). setSyntax(ConfigSyntax.CONF) Parseable.newString(s, options).parseValue().asInstanceOf[AbstractConfigValue] } def parse(s: String) = { val tree = parseWithoutResolving(s) // resolve substitutions so we can test problems with that, like cycles or // interpolating arrays into strings tree match { case obj: AbstractConfigObject => ResolveContext.resolve(tree, obj, ConfigResolveOptions.noSystem()) case _ => tree } } @Test def invalidConfThrows(): Unit = { // be sure we throw for (invalid <- whitespaceVariations(invalidConf, false)) { addOffendingJsonToException("config", invalid.test) { intercept[ConfigException] { parse(invalid.test) } } } } @Test def validConfWorks(): Unit = { // all we're checking here unfortunately is that it doesn't throw. // for a more thorough check, use the EquivalentsTest stuff. for (valid <- whitespaceVariations(validConf, true)) { val ourAST = addOffendingJsonToException("config-conf", valid.test) { parse(valid.test) } // let's also check round-trip rendering val rendered = ourAST.render() val reparsed = addOffendingJsonToException("config-conf-reparsed", rendered) { parse(rendered) } assertEquals(ourAST, reparsed) } } private def parsePath(s: String): Path = { var firstException: ConfigException = null var secondException: ConfigException = null // parse first by wrapping into a whole document and using // the regular parser. val result = try { val tree = parseWithoutResolving("[${" + s + "}]") tree match { case list: ConfigList => list.get(0) match { case ref: ConfigReference => ref.expression().path() } } } catch { case e: ConfigException => firstException = e null } // also parse with the standalone path parser and be sure the // outcome is the same. try { val shouldBeSame = PathParser.parsePath(s) assertEquals(result, shouldBeSame) } catch { case e: ConfigException => secondException = e } if (firstException == null && secondException != null) throw new AssertionError("only the standalone path parser threw", secondException) if (firstException != null && secondException == null) throw new AssertionError("only the whole-document parser threw", firstException) if (firstException != null) throw firstException if (secondException != null) throw new RuntimeException("wtf, should have thrown because not equal") result } @Test def pathParsing() { assertEquals(path("a"), parsePath("a")) assertEquals(path("a", "b"), parsePath("a.b")) assertEquals(path("a.b"), parsePath("\"a.b\"")) assertEquals(path("a."), parsePath("\"a.\"")) assertEquals(path(".b"), parsePath("\".b\"")) assertEquals(path("true"), parsePath("true")) assertEquals(path("a"), parsePath(" a ")) assertEquals(path("a ", "b"), parsePath(" a .b")) assertEquals(path("a ", " b"), parsePath(" a . b")) assertEquals(path("a b"), parsePath(" a b")) assertEquals(path("a", "b.c", "d"), parsePath("a.\"b.c\".d")) assertEquals(path("3", "14"), parsePath("3.14")) assertEquals(path("3", "14", "159"), parsePath("3.14.159")) assertEquals(path("a3", "14"), parsePath("a3.14")) assertEquals(path(""), parsePath("\"\"")) assertEquals(path("a", "", "b"), parsePath("a.\"\".b")) assertEquals(path("a", ""), parsePath("a.\"\"")) assertEquals(path("", "b"), parsePath("\"\".b")) assertEquals(path("", "", ""), parsePath(""" "".""."" """)) assertEquals(path("a-c"), parsePath("a-c")) assertEquals(path("a_c"), parsePath("a_c")) assertEquals(path("-"), parsePath("\"-\"")) assertEquals(path("-"), parsePath("-")) assertEquals(path("-foo"), parsePath("-foo")) assertEquals(path("-10"), parsePath("-10")) // here 10.0 is part of an unquoted string assertEquals(path("foo10", "0"), parsePath("foo10.0")) // here 10.0 is a number that gets value-concatenated assertEquals(path("10", "0foo"), parsePath("10.0foo")) // just a number assertEquals(path("10", "0"), parsePath("10.0")) // multiple-decimal number assertEquals(path("1", "2", "3", "4"), parsePath("1.2.3.4")) for (invalid <- Seq("", " ", " \n \n ", "a.", ".b", "a..b", "a${b}c", "\"\".", ".\"\"")) { try { intercept[ConfigException.BadPath] { parsePath(invalid) } } catch { case e: Throwable => System.err.println("failed on: '" + invalid + "'"); throw e; } } } @Test def duplicateKeyLastWins() { val obj = parseConfig("""{ "a" : 10, "a" : 11 } """) assertEquals(1, obj.root.size()) assertEquals(11, obj.getInt("a")) } @Test def duplicateKeyObjectsMerged() { val obj = parseConfig("""{ "a" : { "x" : 1, "y" : 2 }, "a" : { "x" : 42, "z" : 100 } }""") assertEquals(1, obj.root.size()) assertEquals(3, obj.getObject("a").size()) assertEquals(42, obj.getInt("a.x")) assertEquals(2, obj.getInt("a.y")) assertEquals(100, obj.getInt("a.z")) } @Test def duplicateKeyObjectsMergedRecursively() { val obj = parseConfig("""{ "a" : { "b" : { "x" : 1, "y" : 2 } }, "a" : { "b" : { "x" : 42, "z" : 100 } } }""") assertEquals(1, obj.root.size()) assertEquals(1, obj.getObject("a").size()) assertEquals(3, obj.getObject("a.b").size()) assertEquals(42, obj.getInt("a.b.x")) assertEquals(2, obj.getInt("a.b.y")) assertEquals(100, obj.getInt("a.b.z")) } @Test def duplicateKeyObjectsMergedRecursivelyDeeper() { val obj = parseConfig("""{ "a" : { "b" : { "c" : { "x" : 1, "y" : 2 } } }, "a" : { "b" : { "c" : { "x" : 42, "z" : 100 } } } }""") assertEquals(1, obj.root.size()) assertEquals(1, obj.getObject("a").size()) assertEquals(1, obj.getObject("a.b").size()) assertEquals(3, obj.getObject("a.b.c").size()) assertEquals(42, obj.getInt("a.b.c.x")) assertEquals(2, obj.getInt("a.b.c.y")) assertEquals(100, obj.getInt("a.b.c.z")) } @Test def duplicateKeyObjectNullObject() { // null is supposed to "reset" the object at key "a" val obj = parseConfig("""{ a : { b : 1 }, a : null, a : { c : 2 } }""") assertEquals(1, obj.root.size()) assertEquals(1, obj.getObject("a").size()) assertEquals(2, obj.getInt("a.c")) } @Test def duplicateKeyObjectNumberObject() { val obj = parseConfig("""{ a : { b : 1 }, a : 42, a : { c : 2 } }""") assertEquals(1, obj.root.size()) assertEquals(1, obj.getObject("a").size()) assertEquals(2, obj.getInt("a.c")) } @Test def impliedCommaHandling() { val valids = Seq( """ // one line { a : y, b : z, c : [ 1, 2, 3 ] }""", """ // multiline but with all commas { a : y, b : z, c : [ 1, 2, 3, ], } """, """ // multiline with no commas { a : y b : z c : [ 1 2 3 ] } """) def dropCurlies(s: String) = { // drop the outside curly braces val first = s.indexOf('{') val last = s.lastIndexOf('}') s.substring(0, first) + s.substring(first + 1, last) + s.substring(last + 1, s.length()) } val changes = Seq( { s: String => s }, { s: String => s.replace("\n", "\n\n") }, { s: String => s.replace("\n", "\n\n\n") }, { s: String => s.replace(",\n", "\n,\n") }, { s: String => s.replace(",\n", "\n\n,\n\n") }, { s: String => s.replace("\n", " \n ") }, { s: String => s.replace(",\n", " \n \n , \n \n ") }, { s: String => dropCurlies(s) }) var tested = 0; for (v <- valids; change <- changes) { tested += 1; val obj = parseConfig(change(v)) assertEquals(3, obj.root.size()) assertEquals("y", obj.getString("a")) assertEquals("z", obj.getString("b")) assertEquals(Seq(1, 2, 3), obj.getIntList("c").asScala) } assertEquals(valids.length * changes.length, tested) // with no newline or comma, we do value concatenation val noNewlineInArray = parseConfig(" { c : [ 1 2 3 ] } ") assertEquals(Seq("1 2 3"), noNewlineInArray.getStringList("c").asScala) val noNewlineInArrayWithQuoted = parseConfig(""" { c : [ "4" "5" "6" ] } """) assertEquals(Seq("4 5 6"), noNewlineInArrayWithQuoted.getStringList("c").asScala) val noNewlineInObject = parseConfig(" { a : b c } ") assertEquals("b c", noNewlineInObject.getString("a")) val noNewlineAtEnd = parseConfig("a : b") assertEquals("b", noNewlineAtEnd.getString("a")) intercept[ConfigException] { parseConfig("{ a : y b : z }") } intercept[ConfigException] { parseConfig("""{ "a" : "y" "b" : "z" }""") } } @Test def keysWithSlash() { val obj = parseConfig("""/a/b/c=42, x/y/z : 32""") assertEquals(42, obj.getInt("/a/b/c")) assertEquals(32, obj.getInt("x/y/z")) } private def lineNumberTest(num: Int, text: String) { val e = intercept[ConfigException] { parseConfig(text) } if (!e.getMessage.contains(num + ":")) throw new Exception("error message did not contain line '" + num + "' '" + text.replace("\n", "\\n") + "'", e) } @Test def lineNumbersInErrors() { // error is at the last char lineNumberTest(1, "}") lineNumberTest(2, "\n}") lineNumberTest(3, "\n\n}") // error is before a final newline lineNumberTest(1, "}\n") lineNumberTest(2, "\n}\n") lineNumberTest(3, "\n\n}\n") // with unquoted string lineNumberTest(1, "foo") lineNumberTest(2, "\nfoo") lineNumberTest(3, "\n\nfoo") // with quoted string lineNumberTest(1, "\"foo\"") lineNumberTest(2, "\n\"foo\"") lineNumberTest(3, "\n\n\"foo\"") // newlines in triple-quoted string should not hose up the numbering lineNumberTest(1, "a : \"\"\"foo\"\"\"}") lineNumberTest(2, "a : \"\"\"foo\n\"\"\"}") lineNumberTest(3, "a : \"\"\"foo\nbar\nbaz\"\"\"}") // newlines after the triple quoted string lineNumberTest(5, "a : \"\"\"foo\nbar\nbaz\"\"\"\n\n}") // triple quoted string ends in a newline lineNumberTest(6, "a : \"\"\"foo\nbar\nbaz\n\"\"\"\n\n}") // end in the middle of triple-quoted string lineNumberTest(5, "a : \"\"\"foo\n\n\nbar\n") } @Test def toStringForParseables() { // just be sure the toString don't throw, to get test coverage val options = ConfigParseOptions.defaults() Parseable.newFile(new File("foo"), options).toString Parseable.newResources(classOf[ConfParserTest], "foo", options).toString Parseable.newURL(new URL("file:///foo"), options).toString Parseable.newProperties(new Properties(), options).toString Parseable.newReader(new StringReader("{}"), options).toString } private def assertComments(comments: Seq[String], conf: Config) { assertEquals(comments, conf.root().origin().comments().asScala.toSeq) } private def assertComments(comments: Seq[String], conf: Config, path: String) { assertEquals(comments, conf.getValue(path).origin().comments().asScala.toSeq) } private def assertComments(comments: Seq[String], conf: Config, path: String, index: Int) { val v = conf.getList(path).get(index) assertEquals(comments, v.origin().comments().asScala.toSeq) } @Test def trackCommentsForSingleField() { // no comments val conf0 = parseConfig(""" { foo=10 } """) assertComments(Seq(), conf0, "foo") // comment in front of a field is used val conf1 = parseConfig(""" { # Before foo=10 } """) assertComments(Seq(" Before"), conf1, "foo") // comment with a blank line after is dropped val conf2 = parseConfig(""" { # BlankAfter foo=10 } """) assertComments(Seq(), conf2, "foo") // comment in front of a field is used with no root {} val conf3 = parseConfig(""" # BeforeNoBraces foo=10 """) assertComments(Seq(" BeforeNoBraces"), conf3, "foo") // comment with a blank line after is dropped with no root {} val conf4 = parseConfig(""" # BlankAfterNoBraces foo=10 """) assertComments(Seq(), conf4, "foo") // comment same line after field is used val conf5 = parseConfig(""" { foo=10 # SameLine } """) assertComments(Seq(" SameLine"), conf5, "foo") // comment before field separator is used val conf6 = parseConfig(""" { foo # BeforeSep =10 } """) assertComments(Seq(" BeforeSep"), conf6, "foo") // comment after field separator is used val conf7 = parseConfig(""" { foo= # AfterSep 10 } """) assertComments(Seq(" AfterSep"), conf7, "foo") // comment on next line is NOT used val conf8 = parseConfig(""" { foo=10 # NextLine } """) assertComments(Seq(), conf8, "foo") // comment before field separator on new line val conf9 = parseConfig(""" { foo # BeforeSepOwnLine =10 } """) assertComments(Seq(" BeforeSepOwnLine"), conf9, "foo") // comment after field separator on its own line val conf10 = parseConfig(""" { foo= # AfterSepOwnLine 10 } """) assertComments(Seq(" AfterSepOwnLine"), conf10, "foo") // comments comments everywhere val conf11 = parseConfig(""" {# Before foo # BeforeSep = # AfterSepSameLine # AfterSepNextLine 10 # AfterValue # AfterValueNewLine (should NOT be used) } """) assertComments(Seq(" Before", " BeforeSep", " AfterSepSameLine", " AfterSepNextLine", " AfterValue"), conf11, "foo") // empty object val conf12 = parseConfig("""# BeforeEmpty {} #AfterEmpty # NewLine """) assertComments(Seq(" BeforeEmpty", "AfterEmpty"), conf12) // empty array val conf13 = parseConfig(""" foo= # BeforeEmptyArray [] #AfterEmptyArray # NewLine """) assertComments(Seq(" BeforeEmptyArray", "AfterEmptyArray"), conf13, "foo") // array element val conf14 = parseConfig(""" foo=[ # BeforeElement 10 # AfterElement ] """) assertComments(Seq(" BeforeElement", " AfterElement"), conf14, "foo", 0) // field with comma after it val conf15 = parseConfig(""" foo=10, # AfterCommaField """) assertComments(Seq(" AfterCommaField"), conf15, "foo") // element with comma after it val conf16 = parseConfig(""" foo=[10, # AfterCommaElement ] """) assertComments(Seq(" AfterCommaElement"), conf16, "foo", 0) // field with comma after it but comment isn't on the field's line, so not used val conf17 = parseConfig(""" foo=10 , # AfterCommaFieldNotUsed """) assertComments(Seq(), conf17, "foo") // element with comma after it but comment isn't on the field's line, so not used val conf18 = parseConfig(""" foo=[10 , # AfterCommaElementNotUsed ] """) assertComments(Seq(), conf18, "foo", 0) // comment on new line, before comma, should not be used val conf19 = parseConfig(""" foo=10 # BeforeCommaFieldNotUsed , """) assertComments(Seq(), conf19, "foo") // comment on new line, before comma, should not be used val conf20 = parseConfig(""" foo=[10 # BeforeCommaElementNotUsed , ] """) assertComments(Seq(), conf20, "foo", 0) // comment on same line before comma val conf21 = parseConfig(""" foo=10 # BeforeCommaFieldSameLine , """) assertComments(Seq(" BeforeCommaFieldSameLine"), conf21, "foo") // comment on same line before comma val conf22 = parseConfig(""" foo=[10 # BeforeCommaElementSameLine , ] """) assertComments(Seq(" BeforeCommaElementSameLine"), conf22, "foo", 0) // comment with a line containing only whitespace after is dropped val conf23 = parseConfig(""" { # BlankAfter foo=10 } """) assertComments(Seq(), conf23, "foo") } @Test def trackCommentsForMultipleFields() { // nested objects val conf5 = parseConfig(""" # Outside bar { # Ignore me # Middle # two lines baz { # Inner foo=10 # AfterInner # This should be ignored } # AfterMiddle # ignored } # AfterOutside # ignored! """) assertComments(Seq(" Inner", " AfterInner"), conf5, "bar.baz.foo") assertComments(Seq(" Middle", " two lines", " AfterMiddle"), conf5, "bar.baz") assertComments(Seq(" Outside", " AfterOutside"), conf5, "bar") // multiple fields val conf6 = parseConfig("""{ # this is not with a field # this is field A a : 10, # this is field B b : 12 # goes with field B which has no comma # this is field C c : 14, # goes with field C after comma # not used # this is not used # nor is this # multi-line block # this is with field D # this is with field D also d : 16 # this is after the fields }""") assertComments(Seq(" this is field A"), conf6, "a") assertComments(Seq(" this is field B", " goes with field B which has no comma"), conf6, "b") assertComments(Seq(" this is field C", " goes with field C after comma"), conf6, "c") assertComments(Seq(" this is with field D", " this is with field D also"), conf6, "d") // array val conf7 = parseConfig(""" # before entire array array = [ # goes with 0 0, # goes with 1 1, # with 1 after comma # goes with 2 2 # no comma after 2 # not with anything ] # after entire array """) assertComments(Seq(" goes with 0"), conf7, "array", 0) assertComments(Seq(" goes with 1", " with 1 after comma"), conf7, "array", 1) assertComments(Seq(" goes with 2", " no comma after 2"), conf7, "array", 2) assertComments(Seq(" before entire array", " after entire array"), conf7, "array") // properties-like syntax val conf8 = parseConfig(""" # ignored comment # x.y comment x.y = 10 # x.z comment x.z = 11 # x.a comment x.a = 12 # a.b comment a.b = 14 a.c = 15 a.d = 16 # a.d comment # ignored comment """) assertComments(Seq(" x.y comment"), conf8, "x.y") assertComments(Seq(" x.z comment"), conf8, "x.z") assertComments(Seq(" x.a comment"), conf8, "x.a") assertComments(Seq(" a.b comment"), conf8, "a.b") assertComments(Seq(), conf8, "a.c") assertComments(Seq(" a.d comment"), conf8, "a.d") // here we're concerned that comments apply only to leaf // nodes, not to parent objects. assertComments(Seq(), conf8, "x") assertComments(Seq(), conf8, "a") } @Test def includeFile() { val conf = ConfigFactory.parseString("include file(" + jsonQuotedResourceFile("test01") + ")") // should have loaded conf, json, properties assertEquals(42, conf.getInt("ints.fortyTwo")) assertEquals(1, conf.getInt("fromJson1")) assertEquals("abc", conf.getString("fromProps.abc")) } @Test def includeFileWithExtension() { val conf = ConfigFactory.parseString("include file(" + jsonQuotedResourceFile("test01.conf") + ")") assertEquals(42, conf.getInt("ints.fortyTwo")) assertFalse(conf.hasPath("fromJson1")) assertFalse(conf.hasPath("fromProps.abc")) } @Test def includeFileWhitespaceInsideParens() { val conf = ConfigFactory.parseString("include file( \n " + jsonQuotedResourceFile("test01") + " \n )") // should have loaded conf, json, properties assertEquals(42, conf.getInt("ints.fortyTwo")) assertEquals(1, conf.getInt("fromJson1")) assertEquals("abc", conf.getString("fromProps.abc")) } @Test def includeFileNoWhitespaceOutsideParens() { val e = intercept[ConfigException.Parse] { ConfigFactory.parseString("include file (" + jsonQuotedResourceFile("test01") + ")") } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("expecting include parameter")) } @Test def includeFileNotQuoted() { val f = resourceFile("test01") val e = intercept[ConfigException.Parse] { ConfigFactory.parseString("include file(" + f + ")") } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("expecting include file() parameter to be a quoted string")) } @Test def includeFileNotQuotedAndSpecialChar() { val f = resourceFile("test01") val e = intercept[ConfigException.Parse] { ConfigFactory.parseString("include file(:" + f + ")") } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("expecting include file() parameter to be a quoted string, rather than: ':'")) } @Test def includeFileUnclosedParens() { val e = intercept[ConfigException.Parse] { ConfigFactory.parseString("include file(" + jsonQuotedResourceFile("test01") + " something") } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("expecting a close parentheses")) } @Test def includeURLBasename() { // "AnySyntax" trick doesn't work for url() includes val url = resourceFile("test01").toURI().toURL().toExternalForm() val conf = ConfigFactory.parseString("include url(" + quoteJsonString(url) + ")") assertTrue("including basename URL doesn't load anything", conf.isEmpty()) } @Test def includeURLWithExtension() { val url = resourceFile("test01.conf").toURI().toURL().toExternalForm() val conf = ConfigFactory.parseString("include url(" + quoteJsonString(url) + ")") assertEquals(42, conf.getInt("ints.fortyTwo")) assertFalse(conf.hasPath("fromJson1")) assertFalse(conf.hasPath("fromProps.abc")) } @Test def includeURLInvalid() { val e = intercept[ConfigException.Parse] { ConfigFactory.parseString("include url(\"junk:junk:junk\")") } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("invalid URL")) } @Test def includeResources() { val conf = ConfigFactory.parseString("include classpath(\"test01\")") // should have loaded conf, json, properties assertEquals(42, conf.getInt("ints.fortyTwo")) assertEquals(1, conf.getInt("fromJson1")) assertEquals("abc", conf.getString("fromProps.abc")) } @Test def includeRequiredMissing() { // set this to allowMissing=true to demonstrate that the missing inclusion causes failure despite this setting val missing = ConfigParseOptions.defaults().setAllowMissing(true) val ex = intercept[Exception] { ConfigFactory.parseString("include required(classpath( \"nonexistant\") )", missing) } val actual = ex.getMessage val expected = ".*resource not found on classpath.*" assertTrue(s"expected match for <$expected> but got <$actual>", actual.matches(expected)) } @Test def includeRequiredFoundButNestedIncludeMissing() { // set this to allowMissing=true to demonstrate that the missing nested inclusion is permitted despite this setting val missing = ConfigParseOptions.defaults().setAllowMissing(false) // test03 has a missing include val conf = ConfigFactory.parseString("include required(classpath( \"test03\") )", missing) val expected = "This is in the included file" val actual = conf.getString("foo") assertTrue(s"expected match for <$expected> but got <$actual>", actual.matches(expected)) } @Test def includeRequiredFound() { val confs = Seq( "include required(\"test01\")", "include required( \"test01\" )", "include required(classpath(\"test01\"))", "include required( classpath(\"test01\"))", "include required(classpath(\"test01\") )", "include required( classpath(\"test01\") )", "include required(classpath( \"test01\" ))", "include required( classpath( \"test01\" ))", "include required(classpath( \"test01\" ) )", "include required( classpath( \"test01\" ) )") // should have loaded conf, json, properties confs.foreach { c => try { val conf = ConfigFactory.parseString(c) assertEquals(42, conf.getInt("ints.fortyTwo")) assertEquals(1, conf.getInt("fromJson1")) assertEquals("abc", conf.getString("fromProps.abc")) } catch { case ex: Exception => System.err.println(s"failed parsing: $c") throw ex } } } @Test def includeURLHeuristically() { val url = resourceFile("test01.conf").toURI().toURL().toExternalForm() val conf = ConfigFactory.parseString("include " + quoteJsonString(url)) assertEquals(42, conf.getInt("ints.fortyTwo")) assertFalse(conf.hasPath("fromJson1")) assertFalse(conf.hasPath("fromProps.abc")) } @Test def includeURLBasenameHeuristically() { // "AnySyntax" trick doesn't work for url includes val url = resourceFile("test01").toURI().toURL().toExternalForm() val conf = ConfigFactory.parseString("include " + quoteJsonString(url)) assertTrue("including basename URL doesn't load anything", conf.isEmpty()) } @Test def acceptBOMStartingFile() { // BOM at start of file should be ignored val conf = ConfigFactory.parseResources("bom.conf") assertEquals("bar", conf.getString("foo")) } @Test def acceptBOMStartOfStringConfig() { // BOM at start of file is just whitespace, so ignored val conf = ConfigFactory.parseString("\uFEFFfoo=bar") assertEquals("bar", conf.getString("foo")) } @Test def acceptBOMInStringValue() { // BOM inside quotes should be preserved, just as other whitespace would be val conf = ConfigFactory.parseString("foo=\"\uFEFF\uFEFF\"") assertEquals("\uFEFF\uFEFF", conf.getString("foo")) } @Test def acceptBOMWhitespace() { // BOM here should be treated like other whitespace (ignored, since no quotes) val conf = ConfigFactory.parseString("foo= \uFEFFbar\uFEFF") assertEquals("bar", conf.getString("foo")) } @Test def acceptMultiPeriodNumericPath() { val conf1 = ConfigFactory.parseString("0.1.2.3=foobar1") assertEquals("foobar1", conf1.getString("0.1.2.3")) val conf2 = ConfigFactory.parseString("0.1.2.3.ABC=foobar2") assertEquals("foobar2", conf2.getString("0.1.2.3.ABC")) val conf3 = ConfigFactory.parseString("ABC.0.1.2.3=foobar3") assertEquals("foobar3", conf3.getString("ABC.0.1.2.3")) } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/ConfigBeanFactoryTest.scala000066400000000000000000000250651277147274600311040ustar00rootroot00000000000000/** * Copyright (C) 2013 Typesafe Inc. */ package com.typesafe.config.impl import beanconfig.EnumsConfig.{ Solution, Problem } import com.typesafe.config._ import java.io.{ InputStream, InputStreamReader } import java.time.Duration; import beanconfig._ import org.junit.Assert._ import org.junit._ import scala.collection.JavaConverters._ import scala.collection.mutable.ArrayBuffer class ConfigBeanFactoryTest extends TestUtils { @Test def toCamelCase() { assertEquals("configProp", ConfigImplUtil.toCamelCase("config-prop")) assertEquals("configProp", ConfigImplUtil.toCamelCase("configProp")) assertEquals("fooBar", ConfigImplUtil.toCamelCase("foo-----bar")) assertEquals("fooBar", ConfigImplUtil.toCamelCase("fooBar")) assertEquals("foo", ConfigImplUtil.toCamelCase("-foo")) assertEquals("bar", ConfigImplUtil.toCamelCase("bar-")) } @Test def testCreate() { val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/beanconfig01.conf") val config: Config = ConfigFactory.parseReader(new InputStreamReader(configIs), ConfigParseOptions.defaults.setSyntax(ConfigSyntax.CONF)).resolve val beanConfig: TestBeanConfig = ConfigBeanFactory.create(config, classOf[TestBeanConfig]) assertNotNull(beanConfig) // recursive bean inside the first bean assertEquals(3, beanConfig.getNumbers.getIntVal) } @Test def testValidation() { val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/beanconfig01.conf") val config: Config = ConfigFactory.parseReader(new InputStreamReader(configIs), ConfigParseOptions.defaults.setSyntax(ConfigSyntax.CONF)).resolve.getConfig("validation") val e = intercept[ConfigException.ValidationFailed] { ConfigBeanFactory.create(config, classOf[ValidationBeanConfig]) } val expecteds = Seq(Missing("propNotListedInConfig", 77, "string"), WrongType("shouldBeInt", 78, "number", "boolean"), WrongType("should-be-boolean", 79, "boolean", "number"), WrongType("should-be-list", 80, "list", "string")) checkValidationException(e, expecteds) } @Test def testCreateBool() { val beanConfig: BooleansConfig = ConfigBeanFactory.create(loadConfig().getConfig("booleans"), classOf[BooleansConfig]) assertNotNull(beanConfig) assertEquals(true, beanConfig.getTrueVal) assertEquals(false, beanConfig.getFalseVal) } @Test def testCreateString() { val beanConfig: StringsConfig = ConfigBeanFactory.create(loadConfig().getConfig("strings"), classOf[StringsConfig]) assertNotNull(beanConfig) assertEquals("abcd", beanConfig.getAbcd) assertEquals("yes", beanConfig.getYes) } @Test def testCreateEnum() { val beanConfig: EnumsConfig = ConfigBeanFactory.create(loadConfig().getConfig("enums"), classOf[EnumsConfig]) assertNotNull(beanConfig) assertEquals(Problem.P1, beanConfig.getProblem) assertEquals(ArrayBuffer(Solution.S1, Solution.S3), beanConfig.getSolutions.asScala) } @Test def testCreateNumber() { val beanConfig: NumbersConfig = ConfigBeanFactory.create(loadConfig().getConfig("numbers"), classOf[NumbersConfig]) assertNotNull(beanConfig) assertEquals(3, beanConfig.getIntVal) assertEquals(3, beanConfig.getIntObj) assertEquals(4L, beanConfig.getLongVal) assertEquals(4L, beanConfig.getLongObj) assertEquals(1.0, beanConfig.getDoubleVal, 1e-6) assertEquals(1.0, beanConfig.getDoubleObj, 1e-6) } @Test def testCreateList() { val beanConfig: ArraysConfig = ConfigBeanFactory.create(loadConfig().getConfig("arrays"), classOf[ArraysConfig]) assertNotNull(beanConfig) assertEquals(List().asJava, beanConfig.getEmpty) assertEquals(List(1, 2, 3).asJava, beanConfig.getOfInt) assertEquals(List(32L, 42L, 52L).asJava, beanConfig.getOfLong) assertEquals(List("a", "b", "c").asJava, beanConfig.getOfString) //assertEquals(List(List("a", "b", "c").asJava, // List("a", "b", "c").asJava, // List("a", "b", "c").asJava).asJava, // beanConfig.getOfArray) assertEquals(3, beanConfig.getOfObject.size) assertEquals(3, beanConfig.getOfDouble.size) assertEquals(3, beanConfig.getOfConfig.size) assertTrue(beanConfig.getOfConfig.get(0).isInstanceOf[Config]) assertEquals(3, beanConfig.getOfConfigObject.size) assertTrue(beanConfig.getOfConfigObject.get(0).isInstanceOf[ConfigObject]) assertEquals(List(intValue(1), intValue(2), stringValue("a")), beanConfig.getOfConfigValue.asScala) assertEquals(List(Duration.ofMillis(1), Duration.ofHours(2), Duration.ofDays(3)), beanConfig.getOfDuration.asScala) assertEquals(List(ConfigMemorySize.ofBytes(1024), ConfigMemorySize.ofBytes(1048576), ConfigMemorySize.ofBytes(1073741824)), beanConfig.getOfMemorySize.asScala) val stringsConfigOne = new StringsConfig(); stringsConfigOne.setAbcd("testAbcdOne") stringsConfigOne.setYes("testYesOne") val stringsConfigTwo = new StringsConfig(); stringsConfigTwo.setAbcd("testAbcdTwo") stringsConfigTwo.setYes("testYesTwo") assertEquals(List(stringsConfigOne, stringsConfigTwo).asJava, beanConfig.getOfStringBean) } @Test def testCreateDuration() { val beanConfig: DurationsConfig = ConfigBeanFactory.create(loadConfig().getConfig("durations"), classOf[DurationsConfig]) assertNotNull(beanConfig) assertEquals(Duration.ofMillis(500), beanConfig.getHalfSecond) assertEquals(Duration.ofMillis(1000), beanConfig.getSecond) assertEquals(Duration.ofMillis(1000), beanConfig.getSecondAsNumber) } @Test def testCreateBytes() { val beanConfig: BytesConfig = ConfigBeanFactory.create(loadConfig().getConfig("bytes"), classOf[BytesConfig]) assertNotNull(beanConfig) assertEquals(ConfigMemorySize.ofBytes(1024), beanConfig.getKibibyte) assertEquals(ConfigMemorySize.ofBytes(1000), beanConfig.getKilobyte) assertEquals(ConfigMemorySize.ofBytes(1000), beanConfig.getThousandBytes) } @Test def testPreferCamelNames() { val beanConfig = ConfigBeanFactory.create(loadConfig().getConfig("preferCamelNames"), classOf[PreferCamelNamesConfig]) assertNotNull(beanConfig) assertEquals("yes", beanConfig.getFooBar) assertEquals("yes", beanConfig.getBazBar) } @Test def testValues() { val beanConfig = ConfigBeanFactory.create(loadConfig().getConfig("values"), classOf[ValuesConfig]) assertNotNull(beanConfig) assertEquals(42, beanConfig.getObj()) assertEquals("abcd", beanConfig.getConfig.getString("abcd")) assertEquals(3, beanConfig.getConfigObj.toConfig.getInt("intVal")) assertEquals(stringValue("hello world"), beanConfig.getConfigValue) assertEquals(List(1, 2, 3).map(intValue(_)), beanConfig.getList.asScala) assertEquals(true, beanConfig.getUnwrappedMap.get("shouldBeInt")) assertEquals(42, beanConfig.getUnwrappedMap.get("should-be-boolean")) } @Test def testOptionalProperties() { val beanConfig: ObjectsConfig = ConfigBeanFactory.create(loadConfig().getConfig("objects"), classOf[ObjectsConfig]) assertNotNull(beanConfig) assertNotNull(beanConfig.getValueObject) assertNull(beanConfig.getValueObject.getOptionalValue) assertEquals("notNull", beanConfig.getValueObject.getMandatoryValue) } @Test def testNotAnOptionalProperty(): Unit = { val e = intercept[ConfigException.ValidationFailed] { ConfigBeanFactory.create(parseConfig("{valueObject: {}}"), classOf[ObjectsConfig]) } assertTrue("missing value error", e.getMessage.contains("No setting")) assertTrue("error about the right property", e.getMessage.contains("mandatoryValue")) } @Test def testNotABeanField() { val e = intercept[ConfigException.BadBean] { ConfigBeanFactory.create(parseConfig("notBean=42"), classOf[NotABeanFieldConfig]) } assertTrue("unsupported type error", e.getMessage.contains("unsupported type")) assertTrue("error about the right property", e.getMessage.contains("notBean")) } @Test def testNotAnEnumField() { val e = intercept[ConfigException.BadValue] { ConfigBeanFactory.create(parseConfig("{problem=P1,solutions=[S4]}"), classOf[EnumsConfig]) } assertTrue("invalid value error", e.getMessage.contains("Invalid value")) assertTrue("error about the right property", e.getMessage.contains("solutions")) assertTrue("error enumerates the enum constants", e.getMessage.contains("should be one of [S1, S2, S3]")) } @Test def testUnsupportedListElement() { val e = intercept[ConfigException.BadBean] { ConfigBeanFactory.create(parseConfig("uri=[42]"), classOf[UnsupportedListElementConfig]) } assertTrue("unsupported element type error", e.getMessage.contains("unsupported list element type")) assertTrue("error about the right property", e.getMessage.contains("uri")) } @Test def testUnsupportedMapKey() { val e = intercept[ConfigException.BadBean] { ConfigBeanFactory.create(parseConfig("map={}"), classOf[UnsupportedMapKeyConfig]) } assertTrue("unsupported map type error", e.getMessage.contains("unsupported Map")) assertTrue("error about the right property", e.getMessage.contains("'map'")) } @Test def testUnsupportedMapValue() { val e = intercept[ConfigException.BadBean] { ConfigBeanFactory.create(parseConfig("map={}"), classOf[UnsupportedMapValueConfig]) } assertTrue("unsupported map type error", e.getMessage.contains("unsupported Map")) assertTrue("error about the right property", e.getMessage.contains("'map'")) } private def loadConfig(): Config = { val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/beanconfig01.conf") try { val config: Config = ConfigFactory.parseReader(new InputStreamReader(configIs), ConfigParseOptions.defaults.setSyntax(ConfigSyntax.CONF)).resolve config } finally { configIs.close() } } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentParserTest.scala000066400000000000000000000266441277147274600316460ustar00rootroot00000000000000package com.typesafe.config.impl import com.typesafe.config.{ ConfigException, ConfigSyntax, ConfigParseOptions } import org.junit.Assert._ import org.junit.Test class ConfigDocumentParserTest extends TestUtils { private def parseTest(origText: String) { val node = ConfigDocumentParser.parse(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults()) assertEquals(origText, node.render()) } private def parseJSONFailuresTest(origText: String, containsMessage: String) { var exceptionThrown = false val e = intercept[ConfigException] { ConfigDocumentParser.parse(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) } assertTrue(e.getMessage.contains(containsMessage)) } private def parseSimpleValueTest(origText: String, finalText: String = null) { val expectedRenderedText = if (finalText == null) origText else finalText val node = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults()) assertEquals(expectedRenderedText, node.render()) assertTrue(node.isInstanceOf[ConfigNodeSimpleValue]) val nodeJSON = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) assertEquals(expectedRenderedText, nodeJSON.render()) assertTrue(nodeJSON.isInstanceOf[ConfigNodeSimpleValue]) } private def parseComplexValueTest(origText: String) { val node = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults()) assertEquals(origText, node.render()) assertTrue(node.isInstanceOf[ConfigNodeComplexValue]) val nodeJSON = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) assertEquals(origText, nodeJSON.render()) assertTrue(nodeJSON.isInstanceOf[ConfigNodeComplexValue]) } private def parseSingleValueInvalidJSONTest(origText: String, containsMessage: String) { val node = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults()) assertEquals(origText, node.render()) val e = intercept[ConfigException] { ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) } assertTrue(e.getMessage.contains(containsMessage)) } private def parseLeadingTrailingFailure(toReplace: String) { val e = intercept[ConfigException] { ConfigDocumentParser.parseValue(tokenize(toReplace), fakeOrigin(), ConfigParseOptions.defaults()) } assertTrue("expected message parsing leading trailing", e.getMessage.contains("The value from withValueText cannot have leading or trailing newlines, whitespace, or comments")) } @Test def parseSuccess { parseTest("foo:bar") parseTest(" foo : bar ") parseTest("""include "foo.conf" """) parseTest(" \nfoo:bar\n ") // Can parse a map with all simple types parseTest( """ aUnquoted : bar aString = "qux" aNum:123 aDouble=123.456 aTrue=true aFalse=false aNull=null aSub = ${a.b} include "foo.conf" """) parseTest("{}") parseTest("{foo:bar}") parseTest("{ foo : bar }") parseTest("{foo:bar} ") parseTest("""{include "foo.conf"}""") parseTest(" \n{foo:bar}\n ") //Can parse a map with all simple types parseTest( """{ aUnquoted : bar aString = "qux" aNum:123 aDouble=123.456 aTrue=true aFalse=false aNull=null aSub = ${a.b} include "foo.conf" }""") // Test that maps can be nested within other maps parseTest( """ foo.bar.baz : { qux : "abcdefg" "abc".def."ghi" : 123 abc = { foo:bar } } qux = 123.456 """) // Test that comments can be parsed in maps parseTest( """{ foo: bar // This is a comment baz:qux // This is another comment }""") // Basic array tests parseTest("[]") parseTest("[foo]") // Test trailing comment and whitespace parseTest("[foo,]") parseTest("[foo,] ") // Test leading and trailing whitespace parseTest(" \n[]\n ") // Can parse arrays with all simple types parseTest("""[foo, bar,"qux", 123,123.456, true,false, null, ${a.b}]""") parseTest("""[foo, bar,"qux" , 123 , 123.456, true,false, null, ${a.b} ]""") // Basic concatenation tests parseTest("[foo bar baz qux]") parseTest("{foo: foo bar baz qux}") parseTest("[abc 123 123.456 null true false [1, 2, 3] {a:b}, 2]") // Complex node with all types test parseTest( """{ foo: bar baz qux ernie // The above was a concatenation baz = [ abc 123, {a:12 b: { c: 13 d: { a: 22 b: "abcdefg" # this is a comment c: [1, 2, 3] } } }, # this was an object in an array //The above value is a map containing a map containing a map, all in an array 22, // The below value is an array contained in another array [1,2,3]] // This is a map with some nested maps and arrays within it, as well as some concatenations qux { baz: abc 123 bar: { baz: abcdefg bar: { a: null b: true c: [true false 123, null, [1, 2, 3]] } } } // Did I cover everything? }""") // Can correctly parse a JSON string val origText = """{ "foo": "bar", "baz": 123, "qux": true, "array": [ {"a": true, "c": false}, 12 ] } """ val node = ConfigDocumentParser.parse(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) assertEquals(origText, node.render()) } @Test def parseJSONFailures() { // JSON does not support concatenations parseJSONFailuresTest("""{ "foo": 123 456 789 } """, "Expecting close brace } or a comma") // JSON must begin with { or [ parseJSONFailuresTest(""""a": 123, "b": 456""", "Document must have an object or array at root") // JSON does not support unquoted text parseJSONFailuresTest("""{"foo": unquotedtext}""", "Token not allowed in valid JSON") // JSON does not support substitutions parseJSONFailuresTest("""{"foo": ${"a.b"}}""", "Substitutions (${} syntax) not allowed in JSON") // JSON does not support multi-element paths parseJSONFailuresTest("""{"foo"."bar": 123}""", "Token not allowed in valid JSON") // JSON does not support = parseJSONFailuresTest("""{"foo"=123}""", """Key '"foo"' may not be followed by token: '='""") // JSON does not support += parseJSONFailuresTest("""{"foo" += "bar"}""", """Key '"foo"' may not be followed by token: '+='""") // JSON does not support duplicate keys parseJSONFailuresTest("""{"foo" : 123, "foo": 456}""", "JSON does not allow duplicate fields") // JSON does not support trailing commas parseJSONFailuresTest("""{"foo" : 123,}""", "expecting a field name after a comma, got a close brace } instead") // JSON does not support empty documents parseJSONFailuresTest("", "Empty document") } @Test def parseSingleValues() { // Parse simple values parseSimpleValueTest("123") parseSimpleValueTest("123.456") parseSimpleValueTest(""""a string"""") parseSimpleValueTest("true") parseSimpleValueTest("false") parseSimpleValueTest("null") // Can parse complex values parseComplexValueTest("""{"a": "b"}""") parseComplexValueTest("""["a","b","c"]""") // Check that concatenations are handled by CONF parsing var origText = "123 456 \"abc\"" var node = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults()) assertEquals(origText, node.render()) // Check that keys with no separators and object values are handled by CONF parsing origText = """{"foo" { "bar" : 12 } }""" node = ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults()) assertEquals(origText, node.render()) } @Test def parseSingleValuesFailures { // Parse Simple Value throws on leading and trailing whitespace, comments, or newlines parseLeadingTrailingFailure(" 123") parseLeadingTrailingFailure("123 ") parseLeadingTrailingFailure(" 123 ") parseLeadingTrailingFailure("\n123") parseLeadingTrailingFailure("123\n") parseLeadingTrailingFailure("\n123\n") parseLeadingTrailingFailure("#thisisacomment\n123#comment") // Parse Simple Value correctly throws on whitespace after a concatenation parseLeadingTrailingFailure("123 456 789 ") parseSingleValueInvalidJSONTest("unquotedtext", "Token not allowed in valid JSON") parseSingleValueInvalidJSONTest("${a.b}", "Substitutions (${} syntax) not allowed in JSON") // Check that concatenations in JSON will throw an error var origText = "123 456 \"abc\"" var e = intercept[ConfigException] { ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) } assertTrue("expected message for parsing concat as json", e.getMessage.contains("Parsing JSON and the value set in withValueText was either a concatenation or had trailing whitespace, newlines, or comments")) // Check that keys with no separators and object values in JSON will throw an error origText = """{"foo" { "bar" : 12 } }""" e = intercept[ConfigException] { ConfigDocumentParser.parseValue(tokenize(origText), fakeOrigin(), ConfigParseOptions.defaults().setSyntax((ConfigSyntax.JSON))) } assertTrue("expected failure for key foo followed by token", e.getMessage.contains("""Key '"foo"' may not be followed by token: '{'""")) } @Test def parseEmptyDocument { val node = ConfigDocumentParser.parse(tokenize(""), fakeOrigin(), ConfigParseOptions.defaults()) assertTrue(node.value().isInstanceOf[ConfigNodeObject]) assertTrue(node.value().children().isEmpty()) val node2 = ConfigDocumentParser.parse(tokenize("#comment\n#comment\n\n"), fakeOrigin(), ConfigParseOptions.defaults()) assertTrue(node2.value().isInstanceOf[ConfigNodeObject]) } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala000066400000000000000000000460601277147274600304630ustar00rootroot00000000000000package com.typesafe.config.impl import java.io.{ BufferedReader, FileReader } import java.nio.file.{ Paths, Files } import com.typesafe.config._ import com.typesafe.config.parser._ import org.junit.Assert._ import org.junit.Test import scala.collection.JavaConverters._ class ConfigDocumentTest extends TestUtils { private def configDocumentReplaceJsonTest(origText: String, finalText: String, newValue: String, replacePath: String) { val configDocument = ConfigDocumentFactory.parseString(origText, ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) assertEquals(origText, configDocument.render()) val newDocument = configDocument.withValueText(replacePath, newValue) assertTrue(newDocument.isInstanceOf[SimpleConfigDocument]) assertEquals(finalText, newDocument.render()) } private def configDocumentReplaceConfTest(origText: String, finalText: String, newValue: String, replacePath: String) { val configDocument = ConfigDocumentFactory.parseString(origText) assertEquals(origText, configDocument.render()) val newDocument = configDocument.withValueText(replacePath, newValue) assertTrue(newDocument.isInstanceOf[SimpleConfigDocument]) assertEquals(finalText, newDocument.render()) } @Test def configDocumentReplace { // Can handle parsing/replacement with a very simple map configDocumentReplaceConfTest("""{"a":1}""", """{"a":2}""", "2", "a") configDocumentReplaceJsonTest("""{"a":1}""", """{"a":2}""", "2", "a") // Can handle parsing/replacement with a map without surrounding braces configDocumentReplaceConfTest("a: b\nc = d", "a: b\nc = 12", "12", "c") // Can handle parsing/replacement with a complicated map var origText = """{ "a":123, "b": 123.456, "c": true, "d": false, "e": null, "f": "a string", "g": [1,2,3,4,5], "h": { "a": 123, "b": { "a": 12 }, "c": [1, 2, 3, {"a": "b"}, [1,2,3]] } }""" var finalText = """{ "a":123, "b": 123.456, "c": true, "d": false, "e": null, "f": "a string", "g": [1,2,3,4,5], "h": { "a": 123, "b": { "a": "i am now a string" }, "c": [1, 2, 3, {"a": "b"}, [1,2,3]] } }""" configDocumentReplaceConfTest(origText, finalText, """"i am now a string"""", "h.b.a") configDocumentReplaceJsonTest(origText, finalText, """"i am now a string"""", "h.b.a") // Can handle replacing values with maps finalText = """{ "a":123, "b": 123.456, "c": true, "d": false, "e": null, "f": "a string", "g": [1,2,3,4,5], "h": { "a": 123, "b": { "a": {"a":"b", "c":"d"} }, "c": [1, 2, 3, {"a": "b"}, [1,2,3]] } }""" configDocumentReplaceConfTest(origText, finalText, """{"a":"b", "c":"d"}""", "h.b.a") configDocumentReplaceJsonTest(origText, finalText, """{"a":"b", "c":"d"}""", "h.b.a") // Can handle replacing values with arrays finalText = """{ "a":123, "b": 123.456, "c": true, "d": false, "e": null, "f": "a string", "g": [1,2,3,4,5], "h": { "a": 123, "b": { "a": [1,2,3,4,5] }, "c": [1, 2, 3, {"a": "b"}, [1,2,3]] } }""" configDocumentReplaceConfTest(origText, finalText, "[1,2,3,4,5]", "h.b.a") configDocumentReplaceJsonTest(origText, finalText, "[1,2,3,4,5]", "h.b.a") finalText = """{ "a":123, "b": 123.456, "c": true, "d": false, "e": null, "f": "a string", "g": [1,2,3,4,5], "h": { "a": 123, "b": { "a": this is a concatenation 123 456 {a:b} [1,2,3] {a: this is another 123 concatenation null true} }, "c": [1, 2, 3, {"a": "b"}, [1,2,3]] } }""" configDocumentReplaceConfTest(origText, finalText, "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.withValueText("a", "2").render()) origText = "{a: b, a: e, a.b.c: d}" configDoc = ConfigDocumentFactory.parseString(origText) assertEquals("{a: 2, }", configDoc.withValueText("a", "2").render()) origText = "{a.b.c: d}" configDoc = ConfigDocumentFactory.parseString(origText) assertEquals("{ a : 2}", configDoc.withValueText("a", "2").render()) } @Test def configDocumentSetNewValueBraceRoot { val origText = "{\n\t\"a\":\"b\",\n\t\"c\":\"d\"\n}" val finalTextConf = "{\n\t\"a\":\"b\",\n\t\"c\":\"d\"\n\t\"e\" : \"f\"\n}" val finalTextJson = "{\n\t\"a\":\"b\",\n\t\"c\":\"d\",\n\t\"e\" : \"f\"\n}" configDocumentReplaceConfTest(origText, finalTextConf, "\"f\"", "\"e\"") configDocumentReplaceJsonTest(origText, finalTextJson, "\"f\"", "\"e\"") } @Test def configDocumentSetNewValueNoBraces { val origText = "\"a\":\"b\",\n\"c\":\"d\"\n" val finalText = "\"a\":\"b\",\n\"c\":\"d\"\n\"e\" : \"f\"\n" configDocumentReplaceConfTest(origText, finalText, "\"f\"", "\"e\"") } @Test def configDocumentSetNewValueMultiLevelConf { val origText = "a:b\nc:d" val finalText = "a:b\nc:d\ne : {\n f : {\n g : 12\n }\n}" configDocumentReplaceConfTest(origText, finalText, "12", "e.f.g") } @Test def configDocumentSetNewValueMultiLevelJson { val origText = "{\"a\":\"b\",\n\"c\":\"d\"}" val finalText = "{\"a\":\"b\",\n\"c\":\"d\",\n \"e\" : {\n \"f\" : {\n \"g\" : 12\n }\n }}" configDocumentReplaceJsonTest(origText, finalText, "12", "e.f.g") } @Test def configDocumentSetNewConfigValue { val origText = "{\"a\": \"b\"}" val finalText = "{\"a\": 12}" val configDocHOCON = ConfigDocumentFactory.parseString(origText) val configDocJSON = ConfigDocumentFactory.parseString(origText, ConfigParseOptions.defaults.setSyntax(ConfigSyntax.JSON)) val newValue = ConfigValueFactory.fromAnyRef(12) assertEquals(origText, configDocHOCON.render()) assertEquals(origText, configDocJSON.render()) assertEquals(finalText, configDocHOCON.withValue("a", newValue).render()) assertEquals(finalText, configDocJSON.withValue("a", newValue).render()) } @Test def configDocumentHasValue { val origText = "{a: b, a.b.c.d: e, c: {a: {b: c}}}" val configDoc = ConfigDocumentFactory.parseString(origText) assertTrue(configDoc.hasPath("a")) assertTrue(configDoc.hasPath("a.b.c")) assertTrue(configDoc.hasPath("c.a.b")) assertFalse(configDoc.hasPath("c.a.b.c")) assertFalse(configDoc.hasPath("a.b.c.d.e")) assertFalse(configDoc.hasPath("this.does.not.exist")) } @Test def configDocumentRemoveValue { val origText = "{a: b, a.b.c.d: e, c: {a: {b: c}}}" val configDoc = ConfigDocumentFactory.parseString(origText) assertEquals("{c: {a: {b: c}}}", configDoc.withoutPath("a").render()) assertEquals("{a: b, a.b.c.d: e, }", configDoc.withoutPath("c").render()) assertEquals(configDoc, configDoc.withoutPath("this.does.not.exist")) } @Test def configDocumentRemoveValueJSON { val origText = """{"a": "b", "c": "d"}""" val configDoc = ConfigDocumentFactory.parseString(origText, ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) // Ensure that removing a value in JSON does not leave us with a trailing comma assertEquals("""{"a": "b" }""", configDoc.withoutPath("c").render()) } @Test def configDocumentRemoveMultiple { val origText = "a { b: 42 }, a.b = 43, a { b: { c: 44 } }" val configDoc = ConfigDocumentFactory.parseString(origText) val removed = configDoc.withoutPath("a.b") assertEquals("a { }, a { }", removed.render()) } @Test def configDocumentRemoveOverridden { val origText = "a { b: 42 }, a.b = 43, a { b: { c: 44 } }, a : 57 " val configDoc = ConfigDocumentFactory.parseString(origText) val removed = configDoc.withoutPath("a.b") assertEquals("a { }, a { }, a : 57 ", removed.render()) } @Test def configDocumentRemoveNested { val origText = "a { b: 42 }, a.b = 43, a { b: { c: 44 } }" val configDoc = ConfigDocumentFactory.parseString(origText) val removed = configDoc.withoutPath("a.b.c") assertEquals("a { b: 42 }, a.b = 43, a { b: { } }", removed.render()) } @Test def configDocumentArrayFailures { // Attempting certain methods on a ConfigDocument parsed from an array throws an error val origText = "[1, 2, 3, 4, 5]" val document = ConfigDocumentFactory.parseString(origText) val e1 = intercept[ConfigException] { document.withValueText("a", "1") } assertTrue(e1.getMessage.contains("ConfigDocument had an array at the root level")) val e2 = intercept[ConfigException] { document.hasPath("a") } assertTrue(e2.getMessage.contains("ConfigDocument had an array at the root level")) val e3 = intercept[ConfigException] { document.withoutPath("a") } assertTrue(e3.getMessage.contains("ConfigDocument had an array at the root level")) } @Test def configDocumentJSONReplaceFailure { // Attempting a replace on a ConfigDocument parsed from JSON with a value using HOCON syntax // will fail val origText = "{\"foo\": \"bar\", \"baz\": \"qux\"}" val document = ConfigDocumentFactory.parseString(origText, ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) val e = intercept[ConfigException] { document.withValueText("foo", "unquoted") } assertTrue(e.getMessage.contains("Token not allowed in valid JSON")) } @Test def configDocumentJSONReplaceWithConcatenationFailure { // Attempting a replace on a ConfigDocument parsed from JSON with a concatenation will // fail val origText = "{\"foo\": \"bar\", \"baz\": \"qux\"}" val document = ConfigDocumentFactory.parseString(origText, ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) val e = intercept[ConfigException] { document.withValueText("foo", "1 2 3 concatenation") } assertTrue("got correct exception for concat value", e.getMessage.contains("Parsing JSON and the value set in withValueText was either a concatenation or had trailing whitespace, newlines, or comments")) } @Test def configDocumentFileParse { val configDocument = ConfigDocumentFactory.parseFile(resourceFile("/test03.conf")) val fileReader = new BufferedReader(new FileReader("config/src/test/resources/test03.conf")) var line = fileReader.readLine() val sb = new StringBuilder() while (line != null) { sb.append(line) sb.append("\n") line = fileReader.readLine() } fileReader.close() val fileText = sb.toString() assertEquals(fileText, defaultLineEndingsToUnix(configDocument.render())) } private def defaultLineEndingsToUnix(s: String): String = s.replaceAll(System.lineSeparator(), "\n") @Test def configDocumentReaderParse { val configDocument = ConfigDocumentFactory.parseReader(new FileReader(resourceFile("/test03.conf"))) val configDocumentFile = ConfigDocumentFactory.parseFile(resourceFile("/test03.conf")) assertEquals(configDocumentFile.render(), configDocument.render()) } @Test def configDocumentIndentationSingleLineObject { // Proper insertion for single-line objects var origText = "a { b: c }" var configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a { b: c, d : e }", configDocument.withValueText("a.d", "e").render()) origText = "a { b: c }, d: e" configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a { b: c }, d: e, f : g", configDocument.withValueText("f", "g").render()) origText = "a { b: c }, d: e," configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a { b: c }, d: e, f : g", configDocument.withValueText("f", "g").render()) assertEquals("a { b: c }, d: e, f : { g : { h : i } }", configDocument.withValueText("f.g.h", "i").render()) origText = "{a { b: c }, d: e}" configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("{a { b: c }, d: e, f : g}", configDocument.withValueText("f", "g").render()) assertEquals("{a { b: c }, d: e, f : { g : { h : i } }}", configDocument.withValueText("f.g.h", "i").render()) } @Test def configDocumentIndentationMultiLineObject { var origText = "a {\n b: c\n}" var configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a {\n b: c\n e : f\n}", configDocument.withValueText("a.e", "f").render()) assertEquals("a {\n b: c\n d : {\n e : {\n f : g\n }\n }\n}", configDocument.withValueText("a.d.e.f", "g").render()) origText = "a {\n b: c\n}\n" configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a {\n b: c\n}\nd : e\n", configDocument.withValueText("d", "e").render()) assertEquals("a {\n b: c\n}\nd : {\n e : {\n f : g\n }\n}\n", configDocument.withValueText("d.e.f", "g").render()) } @Test def configDocumentIndentationNested { var origText = "a { b { c { d: e } } }" var configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a { b { c { d: e, f : g } } }", configDocument.withValueText("a.b.c.f", "g").render()) origText = "a {\n b {\n c {\n d: e\n }\n }\n}" configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a {\n b {\n c {\n d: e\n f : g\n }\n }\n}", configDocument.withValueText("a.b.c.f", "g").render()) } @Test def configDocumentIndentationEmptyObject { var origText = "a { }" var configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a { b : c }", configDocument.withValueText("a.b", "c").render()) origText = "a {\n b {\n }\n}" configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a {\n b {\n c : d\n }\n}", configDocument.withValueText("a.b.c", "d").render()) } @Test def configDocumentIndentationMultiLineValue { val origText = "a {\n b {\n c {\n d: e\n }\n }\n}" val configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a {\n b {\n c {\n d: e\n f : {\n g: h\n i: j\n k: {\n l: m\n }\n }\n }\n }\n}", configDocument.withValueText("a.b.c.f", "{\n g: h\n i: j\n k: {\n l: m\n }\n}").render()) assertEquals("a {\n b {\n c {\n d: e\n f : 12 13 [1,\n 2,\n 3,\n {\n a:b\n }]\n }\n }\n}", configDocument.withValueText("a.b.c.f", "12 13 [1,\n2,\n3,\n{\n a:b\n}]").render()) } @Test def configDocumentIndentationMultiLineValueSingleLineObject { // Weird indentation occurs when adding a multi-line value to a single-line object val origText = "a { b { } }" val configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a { b { c : {\n c:d\n } } }", configDocument.withValueText("a.b.c", "{\n c:d\n}").render()) } @Test def configDocumentIndentationSingleLineObjectContainingMultiLineValue { val origText = "a { b {\n c: d\n} }" val configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a { b {\n c: d\n}, e : f }", configDocument.withValueText("a.e", "f").render()) } @Test def configDocumentIndentationReplacingWithMultiLineValue { var origText = "a {\n b {\n c : 22\n }\n}" var configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a {\n b {\n c : {\n d:e\n }\n }\n}", configDocument.withValueText("a.b.c", "{\n d:e\n}").render()) origText = "a {\n b {\n f : 10\n c : 22\n }\n}" configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a {\n b {\n f : 10\n c : {\n d:e\n }\n }\n}", configDocument.withValueText("a.b.c", "{\n d:e\n}").render()) } @Test def configDocumentIndentationValueWithInclude { val origText = "a {\n b {\n c : 22\n }\n}" val configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a {\n b {\n c : 22\n d : {\n include \"foo\"\n e:f\n }\n }\n}", configDocument.withValueText("a.b.d", "{\n include \"foo\"\n e:f\n}").render()) } @Test def configDocumentIndentationBasedOnIncludeNode { val origText = "a : b\n include \"foo\"\n" val configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a : b\n include \"foo\"\n c : d\n", configDocument.withValueText("c", "d").render()) } @Test def configDocumentEmptyTest { val origText = "" val configDocument = ConfigDocumentFactory.parseString(origText) assertEquals("a : 1", configDocument.withValueText("a", "1").render) val mapVal = ConfigValueFactory.fromAnyRef(Map("a" -> 1, "b" -> 2).asJava) assertEquals("a : {\n \"a\" : 1,\n \"b\" : 2\n}", configDocument.withValue("a", mapVal).render) val arrayVal = ConfigValueFactory.fromAnyRef(List(1, 2).asJava) assertEquals("a : [\n 1,\n 2\n]", configDocument.withValue("a", arrayVal).render) } @Test def configDocumentConfigObjectInsertion { val origText = "{ a : b }" val configDocument = ConfigDocumentFactory.parseString(origText) val configVal = ConfigValueFactory.fromAnyRef(Map("a" -> 1, "b" -> 2).asJava) assertEquals("{ a : {\n \"a\" : 1,\n \"b\" : 2\n } }", configDocument.withValue("a", configVal).render) } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/ConfigMemorySizeTest.scala000066400000000000000000000012721277147274600310040ustar00rootroot00000000000000/** * Copyright (C) 2015 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ import com.typesafe.config.ConfigMemorySize class ConfigMemorySizeTest extends TestUtils { @Test def testEquals() { assertTrue("Equal ConfigMemorySize are equal", ConfigMemorySize.ofBytes(10).equals(ConfigMemorySize.ofBytes(10))) assertTrue("Different ConfigMemorySize are not equal", !ConfigMemorySize.ofBytes(10).equals(ConfigMemorySize.ofBytes(11))) } @Test def testToUnits() { val kilobyte = ConfigMemorySize.ofBytes(1024) assertEquals(1024, kilobyte.toBytes) } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/ConfigNodeTest.scala000066400000000000000000000274741277147274600276020ustar00rootroot00000000000000package com.typesafe.config.impl import org.junit.Assert._ import org.junit.Test class ConfigNodeTest extends TestUtils { private def singleTokenNodeTest(token: Token) { val node = configNodeSingleToken(token); assertEquals(node.render(), token.tokenText()) } private def keyNodeTest(path: String) { val node = configNodeKey(path) assertEquals(path, node.render()) } private def simpleValueNodeTest(token: Token) { val node = configNodeSimpleValue(token) assertEquals(node.render(), token.tokenText()) } private def fieldNodeTest(key: ConfigNodePath, value: AbstractConfigNodeValue, newValue: AbstractConfigNodeValue) { val keyValNode = nodeKeyValuePair(key, value) assertEquals(key.render() + " : " + value.render(), keyValNode.render()) assertEquals(key.render, keyValNode.path().render()) assertEquals(value.render, keyValNode.value().render()) val newKeyValNode = keyValNode.replaceValue(newValue) assertEquals(key.render() + " : " + newValue.render(), newKeyValNode.render()) assertEquals(newValue.render(), newKeyValNode.value().render()) } private def topLevelValueReplaceTest(value: AbstractConfigNodeValue, newValue: AbstractConfigNodeValue, key: String = "foo") { val complexNodeChildren = List(nodeOpenBrace, nodeKeyValuePair(configNodeKey(key), value), nodeCloseBrace) val complexNode = configNodeObject(complexNodeChildren) val newNode = complexNode.setValueOnPath(key, newValue) val origText = "{" + key + " : " + value.render() + "}" val finalText = "{" + key + " : " + newValue.render() + "}" assertEquals(origText, complexNode.render()) assertEquals(finalText, newNode.render()) } private def replaceDuplicatesTest(value1: AbstractConfigNodeValue, value2: AbstractConfigNodeValue, value3: AbstractConfigNodeValue) { val key = configNodeKey("foo") val keyValPair1 = nodeKeyValuePair(key, value1) val keyValPair2 = nodeKeyValuePair(key, value2) val keyValPair3 = nodeKeyValuePair(key, value3) val complexNode = configNodeObject(List(keyValPair1, keyValPair2, keyValPair3)) val origText = keyValPair1.render() + keyValPair2.render() + keyValPair3.render() val finalText = key.render() + " : 15" assertEquals(origText, complexNode.render()) assertEquals(finalText, complexNode.setValueOnPath("foo", nodeInt(15)).render()) } private def nonExistentPathTest(value: AbstractConfigNodeValue) { val node = configNodeObject(List(nodeKeyValuePair(configNodeKey("bar"), nodeInt(15)))) assertEquals("bar : 15", node.render()) val newNode = node.setValueOnPath("foo", value) val finalText = "bar : 15, foo : " + value.render() assertEquals(finalText, newNode.render()) } @Test def createBasicConfigNode() { //Ensure a ConfigNodeSingleToken can handle all its required token types singleTokenNodeTest(Tokens.START) singleTokenNodeTest(Tokens.END) singleTokenNodeTest(Tokens.OPEN_CURLY) singleTokenNodeTest(Tokens.CLOSE_CURLY) singleTokenNodeTest(Tokens.OPEN_SQUARE) singleTokenNodeTest(Tokens.CLOSE_SQUARE) singleTokenNodeTest(Tokens.COMMA) singleTokenNodeTest(Tokens.EQUALS) singleTokenNodeTest(Tokens.COLON) singleTokenNodeTest(Tokens.PLUS_EQUALS) singleTokenNodeTest(tokenUnquoted(" ")) singleTokenNodeTest(tokenWhitespace(" ")) singleTokenNodeTest(tokenLine(1)) singleTokenNodeTest(tokenCommentDoubleSlash(" this is a double slash comment ")) singleTokenNodeTest(tokenCommentHash(" this is a hash comment ")) } @Test def createConfigNodeSetting() { //Ensure a ConfigNodeSetting can handle the normal key types keyNodeTest("foo") keyNodeTest("\"Hello I am a key how are you today\"") } @Test def pathNodeSubpath() { val origPath = "a.b.c.\"@$%#@!@#$\".\"\".1234.5678" val pathNode = configNodeKey(origPath) assertEquals(origPath, pathNode.render()) assertEquals("c.\"@$%#@!@#$\".\"\".1234.5678", pathNode.subPath(2).render()) assertEquals("5678", pathNode.subPath(6).render()) } @Test def createConfigNodeSimpleValue() { //Ensure a ConfigNodeSimpleValue can handle the normal value types simpleValueNodeTest(tokenInt(10)) simpleValueNodeTest(tokenLong(10000)) simpleValueNodeTest(tokenDouble(3.14159)) simpleValueNodeTest(tokenFalse) simpleValueNodeTest(tokenTrue) simpleValueNodeTest(tokenNull) simpleValueNodeTest(tokenString("Hello my name is string")) simpleValueNodeTest(tokenUnquoted("mynameisunquotedstring")) simpleValueNodeTest(tokenKeySubstitution("c.d")) simpleValueNodeTest(tokenOptionalSubstitution(tokenUnquoted("x.y"))) simpleValueNodeTest(tokenSubstitution(tokenUnquoted("a.b"))) } @Test def createConfigNodeField() { // Supports Quoted and Unquoted keys fieldNodeTest(configNodeKey("\"abc\""), nodeInt(123), nodeInt(245)) fieldNodeTest(configNodeKey("abc"), nodeInt(123), nodeInt(245)) // Can replace value with values of different types fieldNodeTest(configNodeKey("\"abc\""), nodeInt(123), nodeString("I am a string")) fieldNodeTest(configNodeKey("\"abc\""), nodeInt(123), configNodeObject(List(nodeOpenBrace, nodeCloseBrace))) } @Test def replaceNodes() { //Ensure simple values can be replaced by other simple values topLevelValueReplaceTest(nodeInt(10), nodeInt(15)) topLevelValueReplaceTest(nodeLong(10000), nodeInt(20)) topLevelValueReplaceTest(nodeDouble(3.14159), nodeLong(10000)) topLevelValueReplaceTest(nodeFalse, nodeTrue) topLevelValueReplaceTest(nodeTrue, nodeNull) topLevelValueReplaceTest(nodeNull, nodeString("Hello my name is string")) topLevelValueReplaceTest(nodeString("Hello my name is string"), nodeUnquotedText("mynameisunquotedstring")) topLevelValueReplaceTest(nodeUnquotedText("mynameisunquotedstring"), nodeKeySubstitution("c.d")) topLevelValueReplaceTest(nodeInt(10), nodeOptionalSubstitution(tokenUnquoted("x.y"))) topLevelValueReplaceTest(nodeInt(10), nodeSubstitution(tokenUnquoted("a.b"))) topLevelValueReplaceTest(nodeSubstitution(tokenUnquoted("a.b")), nodeInt(10)) // Ensure arrays can be replaced val array = configNodeArray(List(nodeOpenBracket, nodeInt(10), nodeSpace, nodeComma, nodeSpace, nodeInt(15), nodeCloseBracket)) topLevelValueReplaceTest(nodeInt(10), array) topLevelValueReplaceTest(array, nodeInt(10)) topLevelValueReplaceTest(array, configNodeObject(List(nodeOpenBrace, nodeCloseBrace))) // Ensure objects can be replaced val nestedMap = configNodeObject(List(nodeOpenBrace, nodeKeyValuePair(configNodeKey("abc"), configNodeSimpleValue(tokenString("a string"))), nodeCloseBrace)) topLevelValueReplaceTest(nestedMap, nodeInt(10)) topLevelValueReplaceTest(nodeInt(10), nestedMap) topLevelValueReplaceTest(array, nestedMap) topLevelValueReplaceTest(nestedMap, array) topLevelValueReplaceTest(nestedMap, configNodeObject(List(nodeOpenBrace, nodeCloseBrace))) // Ensure concatenations can be replaced val concatenation = configNodeConcatenation(List(nodeInt(10), nodeSpace, nodeString("Hello"))) topLevelValueReplaceTest(concatenation, nodeInt(12)) topLevelValueReplaceTest(nodeInt(12), concatenation) topLevelValueReplaceTest(nestedMap, concatenation) topLevelValueReplaceTest(concatenation, nestedMap) topLevelValueReplaceTest(array, concatenation) topLevelValueReplaceTest(concatenation, array) //Ensure a key with format "a.b" will be properly replaced topLevelValueReplaceTest(nodeInt(10), nestedMap, "foo.bar") } @Test def removeDuplicates() { val emptyMapNode = configNodeObject(List(nodeOpenBrace, nodeCloseBrace)) val emptyArrayNode = configNodeArray(List(nodeOpenBracket, nodeCloseBracket)) //Ensure duplicates of a key are removed from a map replaceDuplicatesTest(nodeInt(10), nodeTrue, nodeNull) replaceDuplicatesTest(emptyMapNode, emptyMapNode, emptyMapNode) replaceDuplicatesTest(emptyArrayNode, emptyArrayNode, emptyArrayNode) replaceDuplicatesTest(nodeInt(10), emptyMapNode, emptyArrayNode) } @Test def addNonExistentPaths() { nonExistentPathTest(nodeInt(10)) nonExistentPathTest(configNodeArray(List(nodeOpenBracket, nodeInt(15), nodeCloseBracket))) nonExistentPathTest(configNodeObject(List(nodeOpenBrace, nodeKeyValuePair(configNodeKey("foo"), nodeDouble(3.14)), nodeCloseBrace))) } @Test def replaceNestedNodes() { // Test that all features of node replacement in a map work in a complex map containing nested maps val origText = "foo : bar\nbaz : {\n\t\"abc.def\" : 123\n\t//This is a comment about the below setting\n\n\tabc : {\n\t\t" + "def : \"this is a string\"\n\t\tghi : ${\"a.b\"}\n\t}\n}\nbaz.abc.ghi : 52\nbaz.abc.ghi : 53\n}" val lowestLevelMap = configNodeObject(List(nodeOpenBrace, nodeLine(6), nodeWhitespace("\t\t"), nodeKeyValuePair(configNodeKey("def"), configNodeSimpleValue(tokenString("this is a string"))), nodeLine(7), nodeWhitespace("\t\t"), nodeKeyValuePair(configNodeKey("ghi"), configNodeSimpleValue(tokenKeySubstitution("a.b"))), nodeLine(8), nodeWhitespace("\t"), nodeCloseBrace)) val higherLevelMap = configNodeObject(List(nodeOpenBrace, nodeLine(2), nodeWhitespace("\t"), nodeKeyValuePair(configNodeKey("\"abc.def\""), configNodeSimpleValue(tokenInt(123))), nodeLine(3), nodeWhitespace("\t"), nodeCommentDoubleSlash(("This is a comment about the below setting")), nodeLine(4), nodeLine(5), nodeWhitespace("\t"), nodeKeyValuePair(configNodeKey("abc"), lowestLevelMap), nodeLine(9), nodeCloseBrace)) val origNode = configNodeObject(List(nodeKeyValuePair(configNodeKey("foo"), configNodeSimpleValue(tokenUnquoted("bar"))), nodeLine(1), nodeKeyValuePair(configNodeKey("baz"), higherLevelMap), nodeLine(10), nodeKeyValuePair(configNodeKey("baz.abc.ghi"), configNodeSimpleValue(tokenInt(52))), nodeLine(11), nodeKeyValuePair(configNodeKey("baz.abc.ghi"), configNodeSimpleValue(tokenInt(53))), nodeLine(12), nodeCloseBrace)) assertEquals(origText, origNode.render()) val finalText = "foo : bar\nbaz : {\n\t\"abc.def\" : true\n\t//This is a comment about the below setting\n\n\tabc : {\n\t\t" + "def : false\n\t\t\n\t\t\"this.does.not.exist@@@+$#\" : {\n\t\t end : doesnotexist\n\t\t}\n\t}\n}\n\nbaz.abc.ghi : randomunquotedString\n}" //Can replace settings in nested maps // Paths with quotes in the name are treated as a single Path, rather than multiple sub-paths var newNode = origNode.setValueOnPath("baz.\"abc.def\"", configNodeSimpleValue(tokenTrue)) newNode = newNode.setValueOnPath("baz.abc.def", configNodeSimpleValue(tokenFalse)) // Repeats are removed from nested maps newNode = newNode.setValueOnPath("baz.abc.ghi", configNodeSimpleValue(tokenUnquoted("randomunquotedString"))) // Missing paths are added to the top level if they don't appear anywhere, including in nested maps newNode = newNode.setValueOnPath("baz.abc.\"this.does.not.exist@@@+$#\".end", configNodeSimpleValue(tokenUnquoted("doesnotexist"))) // The above operations cause the resultant map to be rendered properly assertEquals(finalText, newNode.render()) } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala000066400000000000000000001156731277147274600314300ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ import com.typesafe.config.ConfigValue import com.typesafe.config.ConfigException import com.typesafe.config.ConfigResolveOptions import com.typesafe.config.Config import com.typesafe.config.ConfigFactory class ConfigSubstitutionTest extends TestUtils { private def resolveWithoutFallbacks(v: AbstractConfigObject) = { val options = ConfigResolveOptions.noSystem() ResolveContext.resolve(v, v, options).asInstanceOf[AbstractConfigObject].toConfig } private def resolveWithoutFallbacks(s: AbstractConfigValue, root: AbstractConfigObject) = { val options = ConfigResolveOptions.noSystem() ResolveContext.resolve(s, root, options) } private def resolve(v: AbstractConfigObject) = { val options = ConfigResolveOptions.defaults() ResolveContext.resolve(v, v, options).asInstanceOf[AbstractConfigObject].toConfig } private def resolve(s: AbstractConfigValue, root: AbstractConfigObject) = { val options = ConfigResolveOptions.defaults() ResolveContext.resolve(s, root, options) } private val simpleObject = { parseObject(""" { "foo" : 42, "bar" : { "int" : 43, "bool" : true, "null" : null, "string" : "hello", "double" : 3.14 } } """) } @Test def resolveTrivialKey() { val s = subst("foo") val v = resolveWithoutFallbacks(s, simpleObject) assertEquals(intValue(42), v) } @Test def resolveTrivialPath() { val s = subst("bar.int") val v = resolveWithoutFallbacks(s, simpleObject) assertEquals(intValue(43), v) } @Test def resolveInt() { val s = subst("bar.int") val v = resolveWithoutFallbacks(s, simpleObject) assertEquals(intValue(43), v) } @Test def resolveBool() { val s = subst("bar.bool") val v = resolveWithoutFallbacks(s, simpleObject) assertEquals(boolValue(true), v) } @Test def resolveNull() { val s = subst("bar.null") val v = resolveWithoutFallbacks(s, simpleObject) assertEquals(nullValue(), v) } @Test def resolveString() { val s = subst("bar.string") val v = resolveWithoutFallbacks(s, simpleObject) assertEquals(stringValue("hello"), v) } @Test def resolveDouble() { val s = subst("bar.double") val v = resolveWithoutFallbacks(s, simpleObject) assertEquals(doubleValue(3.14), v) } @Test def resolveMissingThrows() { val e = intercept[ConfigException.UnresolvedSubstitution] { val s = subst("bar.missing") val v = resolveWithoutFallbacks(s, simpleObject) } assertTrue("wrong exception: " + e.getMessage, !e.getMessage.contains("cycle")) } @Test def resolveIntInString() { val s = substInString("bar.int") val v = resolveWithoutFallbacks(s, simpleObject) assertEquals(stringValue("start<43>end"), v) } @Test def resolveNullInString() { val s = substInString("bar.null") val v = resolveWithoutFallbacks(s, simpleObject) assertEquals(stringValue("startend"), v) // when null is NOT a subst, it should also not become empty val o = parseConfig("""{ "a" : null foo bar }""") assertEquals("null foo bar", o.getString("a")) } @Test def resolveMissingInString() { val s = substInString("bar.missing", true /* optional */ ) val v = resolveWithoutFallbacks(s, simpleObject) // absent object becomes empty string assertEquals(stringValue("start<>end"), v) intercept[ConfigException.UnresolvedSubstitution] { val s2 = substInString("bar.missing", false /* optional */ ) resolveWithoutFallbacks(s2, simpleObject) } } @Test def resolveBoolInString() { val s = substInString("bar.bool") val v = resolveWithoutFallbacks(s, simpleObject) assertEquals(stringValue("startend"), v) } @Test def resolveStringInString() { val s = substInString("bar.string") val v = resolveWithoutFallbacks(s, simpleObject) assertEquals(stringValue("startend"), v) } @Test def resolveDoubleInString() { val s = substInString("bar.double") val v = resolveWithoutFallbacks(s, simpleObject) assertEquals(stringValue("start<3.14>end"), v) } @Test def missingInArray() { import scala.collection.JavaConverters._ val obj = parseObject(""" a : [ ${?missing}, ${?also.missing} ] """) val resolved = resolve(obj) assertEquals(Seq(), resolved.getList("a").asScala) } @Test def missingInObject() { import scala.collection.JavaConverters._ val obj = parseObject(""" a : ${?missing}, b : ${?also.missing}, c : ${?b}, d : ${?c} """) val resolved = resolve(obj) assertTrue(resolved.isEmpty()) } private val substChainObject = { parseObject(""" { "foo" : ${bar}, "bar" : ${a.b.c}, "a" : { "b" : { "c" : 57 } } } """) } @Test def chainSubstitutions() { val s = subst("foo") val v = resolveWithoutFallbacks(s, substChainObject) assertEquals(intValue(57), v) } @Test def substitutionsLookForward() { val obj = parseObject("""a=1,b=${a},a=2""") val resolved = resolve(obj) assertEquals(2, resolved.getInt("b")) } @Test def throwOnIncrediblyTrivialCycle() { val s = subst("a") val e = intercept[ConfigException.UnresolvedSubstitution] { val v = resolveWithoutFallbacks(s, parseObject("a: ${a}")) } assertTrue("Wrong exception: " + e.getMessage, e.getMessage().contains("cycle")) assertTrue("Wrong exception: " + e.getMessage, e.getMessage().contains("${a}")) } private val substCycleObject = { parseObject(""" { "foo" : ${bar}, "bar" : ${a.b.c}, "a" : { "b" : { "c" : ${foo} } } } """) } @Test def throwOnCycles() { val s = subst("foo") val e = intercept[ConfigException.UnresolvedSubstitution] { val v = resolveWithoutFallbacks(s, substCycleObject) } assertTrue("Wrong exception: " + e.getMessage, e.getMessage().contains("cycle")) assertTrue("Wrong exception: " + e.getMessage, e.getMessage().contains("${foo}, ${bar}, ${a.b.c}, ${foo}")) } @Test def throwOnOptionalReferenceToNonOptionalCycle() { // we look up ${?foo}, but the cycle has hard // non-optional links in it so still has to throw. val s = subst("foo", optional = true) val e = intercept[ConfigException.UnresolvedSubstitution] { val v = resolveWithoutFallbacks(s, substCycleObject) } assertTrue("Wrong exception: " + e.getMessage, e.getMessage().contains("cycle")) } // ALL the links have to be optional here for the cycle to be ignored private val substCycleObjectOptionalLink = { parseObject(""" { "foo" : ${?bar}, "bar" : ${?a.b.c}, "a" : { "b" : { "c" : ${?foo} } } } """) } @Test def optionalLinkCyclesActLikeUndefined() { val s = subst("foo", optional = true) val v = resolveWithoutFallbacks(s, substCycleObjectOptionalLink) assertNull("Cycle with optional links in it resolves to null if it's a cycle", v) } @Test def throwOnTwoKeyCycle() { val obj = parseObject("""a:${b},b:${a}""") val e = intercept[ConfigException.UnresolvedSubstitution] { resolve(obj) } assertTrue("Wrong exception: " + e.getMessage, e.getMessage().contains("cycle")) } @Test def throwOnFourKeyCycle() { val obj = parseObject("""a:${b},b:${c},c:${d},d:${a}""") val e = intercept[ConfigException.UnresolvedSubstitution] { resolve(obj) } assertTrue("Wrong exception: " + e.getMessage, e.getMessage().contains("cycle")) } @Test def resolveObject() { val resolved = resolveWithoutFallbacks(substChainObject) assertEquals(57, resolved.getInt("foo")) assertEquals(57, resolved.getInt("bar")) assertEquals(57, resolved.getInt("a.b.c")) } private val substSideEffectCycle = { parseObject(""" { "foo" : ${a.b.c}, "a" : { "b" : { "c" : 42, "cycle" : ${foo} }, "cycle" : ${foo} } } """) } @Test def avoidSideEffectCycles() { // The point of this test is that in traversing objects // to resolve a path, we need to avoid resolving // substitutions that are in the traversed objects but // are not directly required to resolve the path. // i.e. there should not be a cycle in this test. val resolved = resolveWithoutFallbacks(substSideEffectCycle) assertEquals(42, resolved.getInt("foo")) assertEquals(42, resolved.getInt("a.b.cycle")) assertEquals(42, resolved.getInt("a.cycle")) } @Test def ignoreHiddenUndefinedSubst() { // if a substitution is overridden then it shouldn't matter that it's undefined val obj = parseObject("""a=${nonexistent},a=42""") val resolved = resolve(obj) assertEquals(42, resolved.getInt("a")) } @Test def objectDoesNotHideUndefinedSubst() { // if a substitution is overridden by an object we still need to // evaluate the substitution val obj = parseObject("""a=${nonexistent},a={ b : 42 }""") val e = intercept[ConfigException.UnresolvedSubstitution] { resolve(obj) } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("Could not resolve")) } @Test def ignoreHiddenCircularSubst() { // if a substitution is overridden then it shouldn't matter that it's circular val obj = parseObject("""a=${a},a=42""") val resolved = resolve(obj) assertEquals(42, resolved.getInt("a")) } private val delayedMergeObjectResolveProblem1 = { parseObject(""" defaults { a = 1 b = 2 } // make item1 into a ConfigDelayedMergeObject item1 = ${defaults} // note that we'll resolve to a non-object value // so item1.b will ignoreFallbacks and not depend on // ${defaults} item1.b = 3 // be sure we can resolve a substitution to a value in // a delayed-merge object. item2.b = ${item1.b} """) } @Test def avoidDelayedMergeObjectResolveProblem1() { assertTrue(delayedMergeObjectResolveProblem1.attemptPeekWithPartialResolve("item1").isInstanceOf[ConfigDelayedMergeObject]) val resolved = resolveWithoutFallbacks(delayedMergeObjectResolveProblem1) assertEquals(3, resolved.getInt("item1.b")) assertEquals(3, resolved.getInt("item2.b")) } private val delayedMergeObjectResolveProblem2 = { parseObject(""" defaults { a = 1 b = 2 } // make item1 into a ConfigDelayedMergeObject item1 = ${defaults} // note that we'll resolve to an object value // so item1.b will depend on also looking up ${defaults} item1.b = { c : 43 } // be sure we can resolve a substitution to a value in // a delayed-merge object. item2.b = ${item1.b} """) } @Test def avoidDelayedMergeObjectResolveProblem2() { assertTrue(delayedMergeObjectResolveProblem2.attemptPeekWithPartialResolve("item1").isInstanceOf[ConfigDelayedMergeObject]) val resolved = resolveWithoutFallbacks(delayedMergeObjectResolveProblem2) assertEquals(parseObject("{ c : 43 }"), resolved.getObject("item1.b")) assertEquals(43, resolved.getInt("item1.b.c")) assertEquals(43, resolved.getInt("item2.b.c")) } // in this case, item1 is self-referential because // it refers to ${defaults} which refers back to // ${item1}. When self-referencing, only the // value of ${item1} "looking back" should be // visible. This is really a test of the // self-referencing semantics. private val delayedMergeObjectResolveProblem3 = { parseObject(""" item1.b.c = 100 defaults { // we depend on item1.b.c a = ${item1.b.c} b = 2 } // make item1 into a ConfigDelayedMergeObject item1 = ${defaults} // the ${item1.b.c} above in ${defaults} should ignore // this because it only looks back item1.b = { c : 43 } // be sure we can resolve a substitution to a value in // a delayed-merge object. item2.b = ${item1.b} """) } @Test def avoidDelayedMergeObjectResolveProblem3() { assertTrue(delayedMergeObjectResolveProblem3.attemptPeekWithPartialResolve("item1").isInstanceOf[ConfigDelayedMergeObject]) val resolved = resolveWithoutFallbacks(delayedMergeObjectResolveProblem3) assertEquals(parseObject("{ c : 43 }"), resolved.getObject("item1.b")) assertEquals(43, resolved.getInt("item1.b.c")) assertEquals(43, resolved.getInt("item2.b.c")) assertEquals(100, resolved.getInt("defaults.a")) } private val delayedMergeObjectResolveProblem4 = { parseObject(""" defaults { a = 1 b = 2 } item1.b = 7 // make item1 into a ConfigDelayedMerge item1 = ${defaults} // be sure we can resolve a substitution to a value in // a delayed-merge object. item2.b = ${item1.b} """) } @Test def avoidDelayedMergeObjectResolveProblem4() { // in this case we have a ConfigDelayedMerge not a ConfigDelayedMergeObject assertTrue(delayedMergeObjectResolveProblem4.attemptPeekWithPartialResolve("item1").isInstanceOf[ConfigDelayedMerge]) val resolved = resolveWithoutFallbacks(delayedMergeObjectResolveProblem4) assertEquals(2, resolved.getInt("item1.b")) assertEquals(2, resolved.getInt("item2.b")) } private val delayedMergeObjectResolveProblem5 = { parseObject(""" defaults { a = ${item1.b} // tricky cycle - we won't see ${defaults} // as we resolve this b = 2 } item1.b = 7 // make item1 into a ConfigDelayedMerge item1 = ${defaults} // be sure we can resolve a substitution to a value in // a delayed-merge object. item2.b = ${item1.b} """) } @Test def avoidDelayedMergeObjectResolveProblem5() { // in this case we have a ConfigDelayedMerge not a ConfigDelayedMergeObject assertTrue(delayedMergeObjectResolveProblem5.attemptPeekWithPartialResolve("item1").isInstanceOf[ConfigDelayedMerge]) val resolved = resolveWithoutFallbacks(delayedMergeObjectResolveProblem5) assertEquals("item1.b", 2, resolved.getInt("item1.b")) assertEquals("item2.b", 2, resolved.getInt("item2.b")) assertEquals("defaults.a", 7, resolved.getInt("defaults.a")) } private val delayedMergeObjectResolveProblem6 = { parseObject(""" z = 15 defaults-defaults-defaults { m = ${z} n.o.p = ${z} } defaults-defaults { x = 10 y = 11 asdf = ${z} } defaults { a = 1 b = 2 } defaults-alias = ${defaults} // make item1 into a ConfigDelayedMergeObject several layers deep // that will NOT become resolved just because we resolve one path // through it. item1 = 345 item1 = ${?NONEXISTENT} item1 = ${defaults-defaults-defaults} item1 = {} item1 = ${defaults-defaults} item1 = ${defaults-alias} item1 = ${defaults} item1.b = { c : 43 } item1.xyz = 101 // be sure we can resolve a substitution to a value in // a delayed-merge object. item2.b = ${item1.b} """) } @Test def avoidDelayedMergeObjectResolveProblem6() { assertTrue(delayedMergeObjectResolveProblem6.attemptPeekWithPartialResolve("item1").isInstanceOf[ConfigDelayedMergeObject]) // should be able to attemptPeekWithPartialResolve() a known non-object without resolving assertEquals(101, delayedMergeObjectResolveProblem6.toConfig().getObject("item1").attemptPeekWithPartialResolve("xyz").unwrapped()) val resolved = resolveWithoutFallbacks(delayedMergeObjectResolveProblem6) assertEquals(parseObject("{ c : 43 }"), resolved.getObject("item1.b")) assertEquals(43, resolved.getInt("item1.b.c")) assertEquals(43, resolved.getInt("item2.b.c")) assertEquals(15, resolved.getInt("item1.n.o.p")) } private val delayedMergeObjectWithKnownValue = { parseObject(""" defaults { a = 1 b = 2 } // make item1 into a ConfigDelayedMergeObject item1 = ${defaults} // note that we'll resolve to a non-object value // so item1.b will ignoreFallbacks and not depend on // ${defaults} item1.b = 3 """) } @Test def fetchKnownValueFromDelayedMergeObject() { assertTrue(delayedMergeObjectWithKnownValue.attemptPeekWithPartialResolve("item1").isInstanceOf[ConfigDelayedMergeObject]) assertEquals(3, delayedMergeObjectWithKnownValue.toConfig.getConfig("item1").getInt("b")) } private val delayedMergeObjectNeedsFullResolve = { parseObject(""" defaults { a = 1 b = { c : 31 } } item1 = ${defaults} // because b is an object, fetching it requires resolving ${defaults} above // to see if there are more keys to merge with b. item1.b = { c : 41 } """) } @Test def failToFetchFromDelayedMergeObjectNeedsFullResolve() { assertTrue(delayedMergeObjectWithKnownValue.attemptPeekWithPartialResolve("item1").isInstanceOf[ConfigDelayedMergeObject]) val e = intercept[ConfigException.NotResolved] { delayedMergeObjectNeedsFullResolve.toConfig().getObject("item1.b") } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("item1.b")) } // objects that mutually refer to each other private val delayedMergeObjectEmbrace = { parseObject(""" defaults { a = 1 b = 2 } item1 = ${defaults} // item1.c refers to a field in item2 that refers to item1 item1.c = ${item2.d} // item1.x refers to a field in item2 that doesn't go back to item1 item1.x = ${item2.y} item2 = ${defaults} // item2.d refers to a field in item1 item2.d = ${item1.a} item2.y = 15 """) } @Test def resolveDelayedMergeObjectEmbrace() { assertTrue(delayedMergeObjectEmbrace.attemptPeekWithPartialResolve("item1").isInstanceOf[ConfigDelayedMergeObject]) assertTrue(delayedMergeObjectEmbrace.attemptPeekWithPartialResolve("item2").isInstanceOf[ConfigDelayedMergeObject]) val resolved = delayedMergeObjectEmbrace.toConfig.resolve() assertEquals(1, resolved.getInt("item1.c")) assertEquals(1, resolved.getInt("item2.d")) assertEquals(15, resolved.getInt("item1.x")) } // objects that mutually refer to each other private val plainObjectEmbrace = { parseObject(""" item1.a = 10 item1.b = ${item2.d} item2.c = 12 item2.d = 14 item2.e = ${item1.a} item2.f = ${item1.b} // item1.b goes back to item2 item2.g = ${item2.f} // goes back to ourselves """) } @Test def resolvePlainObjectEmbrace() { assertTrue(plainObjectEmbrace.attemptPeekWithPartialResolve("item1").isInstanceOf[SimpleConfigObject]) assertTrue(plainObjectEmbrace.attemptPeekWithPartialResolve("item2").isInstanceOf[SimpleConfigObject]) val resolved = plainObjectEmbrace.toConfig.resolve() assertEquals(14, resolved.getInt("item1.b")) assertEquals(10, resolved.getInt("item2.e")) assertEquals(14, resolved.getInt("item2.f")) assertEquals(14, resolved.getInt("item2.g")) } @Test def useRelativeToSameFileWhenRelativized() { val child = parseObject("""foo=in child,bar=${foo}""") val values = new java.util.HashMap[String, AbstractConfigValue]() values.put("a", child.relativized(new Path("a"))) // this "foo" should NOT be used. values.put("foo", stringValue("in parent")); val resolved = resolve(new SimpleConfigObject(fakeOrigin(), values)); assertEquals("in child", resolved.getString("a.bar")) } @Test def useRelativeToRootWhenRelativized() { // here, "foo" is not defined in the child val child = parseObject("""bar=${foo}""") val values = new java.util.HashMap[String, AbstractConfigValue]() values.put("a", child.relativized(new Path("a"))) // so this "foo" SHOULD be used values.put("foo", stringValue("in parent")); val resolved = resolve(new SimpleConfigObject(fakeOrigin(), values)); assertEquals("in parent", resolved.getString("a.bar")) } private val substComplexObject = { parseObject(""" { "foo" : ${bar}, "bar" : ${a.b.c}, "a" : { "b" : { "c" : 57, "d" : ${foo}, "e" : { "f" : ${foo} } } }, "objA" : ${a}, "objB" : ${a.b}, "objE" : ${a.b.e}, "foo.bar" : 37, "arr" : [ ${foo}, ${a.b.c}, ${"foo.bar"}, ${objB.d}, ${objA.b.e.f}, ${objE.f} ], "ptrToArr" : ${arr}, "x" : { "y" : { "ptrToPtrToArr" : ${ptrToArr} } } } """) } @Test def complexResolve() { import scala.collection.JavaConverters._ val resolved = resolveWithoutFallbacks(substComplexObject) assertEquals(57, resolved.getInt("foo")) assertEquals(57, resolved.getInt("bar")) assertEquals(57, resolved.getInt("a.b.c")) assertEquals(57, resolved.getInt("a.b.d")) assertEquals(57, resolved.getInt("objB.d")) assertEquals(Seq(57, 57, 37, 57, 57, 57), resolved.getIntList("arr").asScala) assertEquals(Seq(57, 57, 37, 57, 57, 57), resolved.getIntList("ptrToArr").asScala) assertEquals(Seq(57, 57, 37, 57, 57, 57), resolved.getIntList("x.y.ptrToPtrToArr").asScala) } private val substSystemPropsObject = { parseObject(""" { "a" : ${configtest.a}, "b" : ${configtest.b} } """) } @Test def doNotSerializeUnresolvedObject() { checkNotSerializable(substComplexObject) } // this is a weird test, it used to test fallback to system props which made more sense. // Now it just tests that if you override with system props, you can use system props // in substitutions. @Test def overrideWithSystemProps() { System.setProperty("configtest.a", "1234") System.setProperty("configtest.b", "5678") ConfigImpl.reloadSystemPropertiesConfig() val resolved = resolve(ConfigFactory.systemProperties().withFallback(substSystemPropsObject).root.asInstanceOf[AbstractConfigObject]) assertEquals("1234", resolved.getString("a")) assertEquals("5678", resolved.getString("b")) } private val substEnvVarObject = { // prefix the names of keys with "key_" to allow us to embed a case sensitive env var name // in the key that wont therefore risk a naming collision with env vars themselves parseObject(""" { "key_HOME" : ${?HOME}, "key_PWD" : ${?PWD}, "key_SHELL" : ${?SHELL}, "key_LANG" : ${?LANG}, "key_PATH" : ${?PATH}, "key_Path" : ${?Path}, // many windows machines use Path rather than PATH "key_NOT_HERE" : ${?NOT_HERE} } """) } @Test def fallbackToEnv() { import scala.collection.JavaConverters._ val resolved = resolve(substEnvVarObject) var existed = 0 for (k <- resolved.root.keySet().asScala) { val envVarName = k.replace("key_", "") val e = System.getenv(envVarName) if (e != null) { existed += 1 assertEquals(e, resolved.getString(k)) } else { assertNull(resolved.root.get(k)) } } if (existed == 0) { throw new Exception("None of the env vars we tried to use for testing were set") } } @Test def noFallbackToEnvIfValuesAreNull() { import scala.collection.JavaConverters._ // create a fallback object with all the env var names // set to null. we want to be sure this blocks // lookup in the environment. i.e. if there is a // { HOME : null } then ${HOME} should be null. val nullsMap = new java.util.HashMap[String, Object] for (k <- substEnvVarObject.keySet().asScala) { val envVarName = k.replace("key_", "") nullsMap.put(envVarName, null) } val nulls = ConfigFactory.parseMap(nullsMap, "nulls map") val resolved = resolve(substEnvVarObject.withFallback(nulls)) for (k <- resolved.root.keySet().asScala) { assertNotNull(resolved.root.get(k)) assertEquals(nullValue, resolved.root.get(k)) } } @Test def fallbackToEnvWhenRelativized() { import scala.collection.JavaConverters._ val values = new java.util.HashMap[String, AbstractConfigValue]() values.put("a", substEnvVarObject.relativized(new Path("a"))) val resolved = resolve(new SimpleConfigObject(fakeOrigin(), values)) var existed = 0 for (k <- resolved.getObject("a").keySet().asScala) { val envVarName = k.replace("key_", "") val e = System.getenv(envVarName) if (e != null) { existed += 1 assertEquals(e, resolved.getConfig("a").getString(k)) } else { assertNull(resolved.getObject("a").get(k)) } } if (existed == 0) { throw new Exception("None of the env vars we tried to use for testing were set") } } @Test def throwWhenEnvNotFound() { val obj = parseObject("""{ a : ${NOT_HERE} }""") intercept[ConfigException.UnresolvedSubstitution] { resolve(obj) } } @Test def optionalOverrideNotProvided() { val obj = parseObject("""{ a: 42, a : ${?NOT_HERE} }""") val resolved = resolve(obj) assertEquals(42, resolved.getInt("a")) } @Test def optionalOverrideProvided() { val obj = parseObject("""{ HERE : 43, a: 42, a : ${?HERE} }""") val resolved = resolve(obj) assertEquals(43, resolved.getInt("a")) } @Test def optionalOverrideOfObjectNotProvided() { val obj = parseObject("""{ a: { b : 42 }, a : ${?NOT_HERE} }""") val resolved = resolve(obj) assertEquals(42, resolved.getInt("a.b")) } @Test def optionalOverrideOfObjectProvided() { val obj = parseObject("""{ HERE : 43, a: { b : 42 }, a : ${?HERE} }""") val resolved = resolve(obj) assertEquals(43, resolved.getInt("a")) assertFalse(resolved.hasPath("a.b")) } @Test def optionalVanishesFromArray() { import scala.collection.JavaConverters._ val obj = parseObject("""{ a : [ 1, 2, 3, ${?NOT_HERE} ] }""") val resolved = resolve(obj) assertEquals(Seq(1, 2, 3), resolved.getIntList("a").asScala) } @Test def optionalUsedInArray() { import scala.collection.JavaConverters._ val obj = parseObject("""{ HERE: 4, a : [ 1, 2, 3, ${?HERE} ] }""") val resolved = resolve(obj) assertEquals(Seq(1, 2, 3, 4), resolved.getIntList("a").asScala) } @Test def substSelfReference() { val obj = parseObject("""a=1, a=${a}""") val resolved = resolve(obj) assertEquals(1, resolved.getInt("a")) } @Test def substSelfReferenceUndefined() { val obj = parseObject("""a=${a}""") val e = intercept[ConfigException.UnresolvedSubstitution] { resolve(obj) } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("cycle")) } @Test def substSelfReferenceOptional() { val obj = parseObject("""a=${?a}""") val resolved = resolve(obj) assertEquals("optional self reference disappears", 0, resolved.root.size) } @Test def substSelfReferenceAlongPath() { val obj = parseObject("""a.b=1, a.b=${a.b}""") val resolved = resolve(obj) assertEquals(1, resolved.getInt("a.b")) } @Test def substSelfReferenceAlongLongerPath() { val obj = parseObject("""a.b.c=1, a.b.c=${a.b.c}""") val resolved = resolve(obj) assertEquals(1, resolved.getInt("a.b.c")) } @Test def substSelfReferenceAlongPathMoreComplex() { // this is an example from the spec val obj = parseObject(""" foo : { a : { c : 1 } } foo : ${foo.a} foo : { a : 2 } """) val resolved = resolve(obj) assertEquals(1, resolved.getInt("foo.c")) assertEquals(2, resolved.getInt("foo.a")) } @Test def substSelfReferenceIndirect() { // this has two possible outcomes depending on whether // we resolve and memoize a first or b first. currently // java 8's hash table makes it resolve OK, but // it's also allowed to throw an exception. val obj = parseObject("""a=1, b=${a}, a=${b}""") val resolved = resolve(obj) assertEquals(1, resolved.getInt("a")) } @Test def substSelfReferenceDoubleIndirect() { // this has two possible outcomes depending on whether we // resolve and memoize a, b, or c first. currently java // 8's hash table makes it resolve OK, but it's also // allowed to throw an exception. val obj = parseObject("""a=1, b=${c}, c=${a}, a=${b}""") val resolved = resolve(obj) assertEquals(1, resolved.getInt("a")) } @Test def substSelfReferenceIndirectStackCycle() { // this situation is undefined, depends on // whether we resolve a or b first. val obj = parseObject("""a=1, b={c=5}, b=${a}, a=${b}""") val resolved = resolve(obj) val option1 = parseObject(""" b={c=5}, a={c=5} """).toConfig() val option2 = parseObject(""" b=1, a=1 """).toConfig() assertTrue("not an expected possibility: " + resolved + " expected 1: " + option1 + " or 2: " + option2, resolved == option1 || resolved == option2) } @Test def substSelfReferenceObject() { val obj = parseObject("""a={b=5}, a=${a}""") val resolved = resolve(obj) assertEquals(5, resolved.getInt("a.b")) } @Test def substSelfReferenceObjectAlongPath() { val obj = parseObject("""a.b={c=5}, a.b=${a.b}""") val resolved = resolve(obj) assertEquals(5, resolved.getInt("a.b.c")) } @Test def substSelfReferenceInConcat() { val obj = parseObject("""a=1, a=${a}foo""") val resolved = resolve(obj) assertEquals("1foo", resolved.getString("a")) } @Test def substSelfReferenceIndirectInConcat() { // this situation is undefined, depends on // whether we resolve a or b first. If b first // then there's an error because ${a} is undefined. // if a first then b=1foo and a=1foo. val obj = parseObject("""a=1, b=${a}foo, a=${b}""") val either = try { Left(resolve(obj)) } catch { case e: ConfigException.UnresolvedSubstitution => Right(e) } val option1 = Left(parseObject("""a:1foo,b:1foo""").toConfig) assertTrue("not an expected possibility: " + either + " expected value " + option1 + " or an exception", either == option1 || either.isRight) } @Test def substOptionalSelfReferenceInConcat() { val obj = parseObject("""a=${?a}foo""") val resolved = resolve(obj) assertEquals("foo", resolved.getString("a")) } @Test def substOptionalIndirectSelfReferenceInConcat() { val obj = parseObject("""a=${?b}foo,b=${?a}""") val resolved = resolve(obj) assertEquals("foo", resolved.getString("a")) } @Test def substTwoOptionalSelfReferencesInConcat() { val obj = parseObject("""a=${?a}foo${?a}""") val resolved = resolve(obj) assertEquals("foo", resolved.getString("a")) } @Test def substTwoOptionalSelfReferencesInConcatWithPriorValue() { val obj = parseObject("""a=1,a=${?a}foo${?a}""") val resolved = resolve(obj) assertEquals("1foo1", resolved.getString("a")) } @Test def substSelfReferenceMiddleOfStack() { val obj = parseObject("""a=1, a=${a}, a=2""") val resolved = resolve(obj) // the substitution would be 1, but then 2 overrides assertEquals(2, resolved.getInt("a")) } @Test def substSelfReferenceObjectMiddleOfStack() { val obj = parseObject("""a={b=5}, a=${a}, a={c=6}""") val resolved = resolve(obj) assertEquals(5, resolved.getInt("a.b")) assertEquals(6, resolved.getInt("a.c")) } @Test def substOptionalSelfReferenceMiddleOfStack() { val obj = parseObject("""a=1, a=${?a}, a=2""") val resolved = resolve(obj) // the substitution would be 1, but then 2 overrides assertEquals(2, resolved.getInt("a")) } @Test def substSelfReferenceBottomOfStack() { // self-reference should just be ignored since it's // overridden val obj = parseObject("""a=${a}, a=1, a=2""") val resolved = resolve(obj) assertEquals(2, resolved.getInt("a")) } @Test def substOptionalSelfReferenceBottomOfStack() { val obj = parseObject("""a=${?a}, a=1, a=2""") val resolved = resolve(obj) assertEquals(2, resolved.getInt("a")) } @Test def substSelfReferenceTopOfStack() { val obj = parseObject("""a=1, a=2, a=${a}""") val resolved = resolve(obj) assertEquals(2, resolved.getInt("a")) } @Test def substOptionalSelfReferenceTopOfStack() { val obj = parseObject("""a=1, a=2, a=${?a}""") val resolved = resolve(obj) assertEquals(2, resolved.getInt("a")) } @Test def substSelfReferenceAlongAPath() { // ${a} in the middle of the stack means "${a} in the stack // below us" and so ${a.b} means b inside the "${a} below us" // not b inside the final "${a}" val obj = parseObject("""a={b={c=5}}, a=${a.b}, a={b=2}""") val resolved = resolve(obj) assertEquals(5, resolved.getInt("a.c")) } @Test def substSelfReferenceAlongAPathInsideObject() { // if the ${a.b} is _inside_ a field value instead of // _being_ the field value, it does not look backward. val obj = parseObject("""a={b={c=5}}, a={ x : ${a.b} }, a={b=2}""") val resolved = resolve(obj) assertEquals(2, resolved.getInt("a.x")) } @Test def substInChildFieldNotASelfReference1() { // here, ${bar.foo} is not a self reference because // it's the value of a child field of bar, not bar // itself; so we use bar's current value, rather than // looking back in the merge stack val obj = parseObject(""" bar : { foo : 42, baz : ${bar.foo} } """) val resolved = resolve(obj) assertEquals(42, resolved.getInt("bar.baz")) assertEquals(42, resolved.getInt("bar.foo")) } @Test def substInChildFieldNotASelfReference2() { // checking that having bar.foo later in the stack // doesn't break the behavior val obj = parseObject(""" bar : { foo : 42, baz : ${bar.foo} } bar : { foo : 43 } """) val resolved = resolve(obj) assertEquals(43, resolved.getInt("bar.baz")) assertEquals(43, resolved.getInt("bar.foo")) } @Test def substInChildFieldNotASelfReference3() { // checking that having bar.foo earlier in the merge // stack doesn't break the behavior. val obj = parseObject(""" bar : { foo : 43 } bar : { foo : 42, baz : ${bar.foo} } """) val resolved = resolve(obj) assertEquals(42, resolved.getInt("bar.baz")) assertEquals(42, resolved.getInt("bar.foo")) } @Test def substInChildFieldNotASelfReference4() { // checking that having bar set to non-object earlier // doesn't break the behavior. val obj = parseObject(""" bar : 101 bar : { foo : 42, baz : ${bar.foo} } """) val resolved = resolve(obj) assertEquals(42, resolved.getInt("bar.baz")) assertEquals(42, resolved.getInt("bar.foo")) } @Test def substInChildFieldNotASelfReference5() { // checking that having bar set to unresolved array earlier // doesn't break the behavior. val obj = parseObject(""" x : 0 bar : [ ${x}, 1, 2, 3 ] bar : { foo : 42, baz : ${bar.foo} } """) val resolved = resolve(obj) assertEquals(42, resolved.getInt("bar.baz")) assertEquals(42, resolved.getInt("bar.foo")) } @Test def mutuallyReferringNotASelfReference() { val obj = parseObject(""" // bar.a should end up as 4 bar : { a : ${foo.d}, b : 1 } bar.b = 3 // foo.c should end up as 3 foo : { c : ${bar.b}, d : 2 } foo.d = 4 """) val resolved = resolve(obj) assertEquals(4, resolved.getInt("bar.a")) assertEquals(3, resolved.getInt("foo.c")) } @Test def substSelfReferenceMultipleTimes() { val obj = parseObject("""a=1,a=${a},a=${a},a=${a}""") val resolved = resolve(obj) assertEquals(1, resolved.getInt("a")) } @Test def substSelfReferenceInConcatMultipleTimes() { val obj = parseObject("""a=1,a=${a}x,a=${a}y,a=${a}z""") val resolved = resolve(obj) assertEquals("1xyz", resolved.getString("a")) } @Test def substSelfReferenceInArray() { // never "look back" from "inside" an array val obj = parseObject("""a=1,a=[${a}, 2]""") val e = intercept[ConfigException.UnresolvedSubstitution] { resolve(obj) } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("cycle") && e.getMessage.contains("${a}")) } @Test def substSelfReferenceInObject() { // never "look back" from "inside" an object val obj = parseObject("""a=1,a={ x : ${a} }""") val e = intercept[ConfigException.UnresolvedSubstitution] { resolve(obj) } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("cycle") && e.getMessage.contains("${a}")) } @Test def selfReferentialObjectNotAffectedByOverriding() { // this is testing that we can still refer to another // field in the same object, even though we are overriding // an earlier object. val obj = parseObject("""a={ x : 42, y : ${a.x} }""") val resolved = resolve(obj) assertEquals(parseObject("{ x : 42, y : 42 }"), resolved.getConfig("a").root) // this is expected because if adding "a=1" here affects the outcome, // it would be flat-out bizarre. val obj2 = parseObject("""a=1, a={ x : 42, y : ${a.x} }""") val resolved2 = resolve(obj2) assertEquals(parseObject("{ x : 42, y : 42 }"), resolved2.getConfig("a").root) } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala000066400000000000000000001462741277147274600267740ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ import com.typesafe.config._ import java.util.concurrent.TimeUnit import scala.collection.JavaConverters._ import com.typesafe.config.ConfigResolveOptions import java.util.concurrent.TimeUnit.{ SECONDS, NANOSECONDS, MICROSECONDS, MILLISECONDS, MINUTES, DAYS, HOURS } class ConfigTest extends TestUtils { private def resolveNoSystem(v: AbstractConfigValue, root: AbstractConfigObject) = { ResolveContext.resolve(v, root, ConfigResolveOptions.noSystem()) } private def resolveNoSystem(v: SimpleConfig, root: SimpleConfig) = { ResolveContext.resolve(v.root, root.root, ConfigResolveOptions.noSystem()).asInstanceOf[AbstractConfigObject].toConfig } def mergeUnresolved(toMerge: AbstractConfigObject*) = { if (toMerge.isEmpty) { SimpleConfigObject.empty() } else { toMerge.reduce((first, second) => first.withFallback(second)) } } def merge(toMerge: AbstractConfigObject*) = { val obj = mergeUnresolved(toMerge: _*) resolveNoSystem(obj, obj) match { case x: AbstractConfigObject => x } } // Merging should always be associative (same results however the values are grouped, // as long as they remain in the same order) private def associativeMerge(allObjects: Seq[AbstractConfigObject])(assertions: SimpleConfig => Unit) { def makeTrees(objects: Seq[AbstractConfigObject]): Iterator[AbstractConfigObject] = { objects.length match { case 0 => Iterator.empty case 1 => { Iterator(objects(0)) } case 2 => { Iterator(objects(0).withFallback(objects(1))) } case n => { val leftSplits = for { i <- (1 until n) pair = objects.splitAt(i) first = pair._1.reduceLeft(_.withFallback(_)) second = pair._2.reduceLeft(_.withFallback(_)) } yield first.withFallback(second) val rightSplits = for { i <- (1 until n) pair = objects.splitAt(i) first = pair._1.reduceRight(_.withFallback(_)) second = pair._2.reduceRight(_.withFallback(_)) } yield first.withFallback(second) leftSplits.iterator ++ rightSplits.iterator } } } val trees = makeTrees(allObjects).toSeq for (tree <- trees) { // if this fails, we were not associative. if (!trees(0).equals(tree)) throw new AssertionError("Merge was not associative, " + "verify that it should not be, then don't use associativeMerge " + "for this one. two results were: \none: " + trees(0) + "\ntwo: " + tree + "\noriginal list: " + allObjects) } for (tree <- trees) { assertions(tree.toConfig) } } @Test def mergeTrivial() { val obj1 = parseObject("""{ "a" : 1 }""") val obj2 = parseObject("""{ "b" : 2 }""") val merged = merge(obj1, obj2).toConfig assertEquals(1, merged.getInt("a")) assertEquals(2, merged.getInt("b")) assertEquals(2, merged.root.size) } @Test def mergeEmpty() { val merged = merge().toConfig assertEquals(0, merged.root.size) } @Test def mergeOne() { val obj1 = parseObject("""{ "a" : 1 }""") val merged = merge(obj1).toConfig assertEquals(1, merged.getInt("a")) assertEquals(1, merged.root.size) } @Test def mergeOverride() { val obj1 = parseObject("""{ "a" : 1 }""") val obj2 = parseObject("""{ "a" : 2 }""") val merged = merge(obj1, obj2).toConfig assertEquals(1, merged.getInt("a")) assertEquals(1, merged.root.size) val merged2 = merge(obj2, obj1).toConfig assertEquals(2, merged2.getInt("a")) assertEquals(1, merged2.root.size) } @Test def mergeN() { val obj1 = parseObject("""{ "a" : 1 }""") val obj2 = parseObject("""{ "b" : 2 }""") val obj3 = parseObject("""{ "c" : 3 }""") val obj4 = parseObject("""{ "d" : 4 }""") associativeMerge(Seq(obj1, obj2, obj3, obj4)) { merged => assertEquals(1, merged.getInt("a")) assertEquals(2, merged.getInt("b")) assertEquals(3, merged.getInt("c")) assertEquals(4, merged.getInt("d")) assertEquals(4, merged.root.size) } } @Test def mergeOverrideN() { val obj1 = parseObject("""{ "a" : 1 }""") val obj2 = parseObject("""{ "a" : 2 }""") val obj3 = parseObject("""{ "a" : 3 }""") val obj4 = parseObject("""{ "a" : 4 }""") associativeMerge(Seq(obj1, obj2, obj3, obj4)) { merged => assertEquals(1, merged.getInt("a")) assertEquals(1, merged.root.size) } associativeMerge(Seq(obj4, obj3, obj2, obj1)) { merged2 => assertEquals(4, merged2.getInt("a")) assertEquals(1, merged2.root.size) } } @Test def mergeNested() { val obj1 = parseObject("""{ "root" : { "a" : 1, "z" : 101 } }""") val obj2 = parseObject("""{ "root" : { "b" : 2, "z" : 102 } }""") val merged = merge(obj1, obj2).toConfig assertEquals(1, merged.getInt("root.a")) assertEquals(2, merged.getInt("root.b")) assertEquals(101, merged.getInt("root.z")) assertEquals(1, merged.root.size) assertEquals(3, merged.getConfig("root").root.size) } @Test def mergeWithEmpty() { val obj1 = parseObject("""{ "a" : 1 }""") val obj2 = parseObject("""{ }""") val merged = merge(obj1, obj2).toConfig assertEquals(1, merged.getInt("a")) assertEquals(1, merged.root.size) val merged2 = merge(obj2, obj1).toConfig assertEquals(1, merged2.getInt("a")) assertEquals(1, merged2.root.size) } @Test def mergeOverrideObjectAndPrimitive() { val obj1 = parseObject("""{ "a" : 1 }""") val obj2 = parseObject("""{ "a" : { "b" : 42 } }""") val merged = merge(obj1, obj2).toConfig assertEquals(1, merged.getInt("a")) assertEquals(1, merged.root.size) val merged2 = merge(obj2, obj1).toConfig assertEquals(42, merged2.getConfig("a").getInt("b")) assertEquals(42, merged2.getInt("a.b")) assertEquals(1, merged2.root.size) assertEquals(1, merged2.getObject("a").size) } @Test def mergeOverrideObjectAndSubstitution() { val obj1 = parseObject("""{ "a" : 1 }""") val obj2 = parseObject("""{ "a" : { "b" : ${c} }, "c" : 42 }""") val merged = merge(obj1, obj2).toConfig assertEquals(1, merged.getInt("a")) assertEquals(2, merged.root.size) val merged2 = merge(obj2, obj1).toConfig assertEquals(42, merged2.getConfig("a").getInt("b")) assertEquals(42, merged2.getInt("a.b")) assertEquals(2, merged2.root.size) assertEquals(1, merged2.getObject("a").size) } @Test def mergeObjectThenPrimitiveThenObject() { // the semantic here is that the primitive blocks the // object that occurs at lower priority. This is consistent // with duplicate keys in the same file. val obj1 = parseObject("""{ "a" : { "b" : 42 } }""") val obj2 = parseObject("""{ "a" : 2 }""") val obj3 = parseObject("""{ "a" : { "b" : 43, "c" : 44 } }""") associativeMerge(Seq(obj1, obj2, obj3)) { merged => assertEquals(42, merged.getInt("a.b")) assertEquals(1, merged.root.size) assertEquals(1, merged.getObject("a").size()) } associativeMerge(Seq(obj3, obj2, obj1)) { merged2 => assertEquals(43, merged2.getInt("a.b")) assertEquals(44, merged2.getInt("a.c")) assertEquals(1, merged2.root.size) assertEquals(2, merged2.getObject("a").size()) } } @Test def mergeObjectThenSubstitutionThenObject() { // the semantic here is that the primitive blocks the // object that occurs at lower priority. This is consistent // with duplicate keys in the same file. val obj1 = parseObject("""{ "a" : { "b" : ${f} } }""") val obj2 = parseObject("""{ "a" : 2 }""") val obj3 = parseObject("""{ "a" : { "b" : ${d}, "c" : ${e} }, "d" : 43, "e" : 44, "f" : 42 }""") associativeMerge(Seq(obj1, obj2, obj3)) { unresolved => val merged = resolveNoSystem(unresolved, unresolved) assertEquals(42, merged.getInt("a.b")) assertEquals(4, merged.root.size) assertEquals(1, merged.getObject("a").size()) } associativeMerge(Seq(obj3, obj2, obj1)) { unresolved => val merged2 = resolveNoSystem(unresolved, unresolved) assertEquals(43, merged2.getInt("a.b")) assertEquals(44, merged2.getInt("a.c")) assertEquals(4, merged2.root.size) assertEquals(2, merged2.getObject("a").size()) } } @Test def mergePrimitiveThenObjectThenPrimitive() { // the primitive should override the object val obj1 = parseObject("""{ "a" : 1 }""") val obj2 = parseObject("""{ "a" : { "b" : 42 } }""") val obj3 = parseObject("""{ "a" : 3 }""") associativeMerge(Seq(obj1, obj2, obj3)) { merged => assertEquals(1, merged.getInt("a")) assertEquals(1, merged.root.size) } } @Test def mergeSubstitutionThenObjectThenSubstitution() { // the substitution should override the object val obj1 = parseObject("""{ "a" : ${b}, "b" : 1 }""") val obj2 = parseObject("""{ "a" : { "b" : 42 } }""") val obj3 = parseObject("""{ "a" : ${c}, "c" : 2 }""") associativeMerge(Seq(obj1, obj2, obj3)) { merged => val resolved = resolveNoSystem(merged, merged) assertEquals(1, resolved.getInt("a")) assertEquals(3, resolved.root.size) } } @Test def mergeSubstitutedValues() { val obj1 = parseObject("""{ "a" : { "x" : 1, "z" : 4 }, "c" : ${a} }""") val obj2 = parseObject("""{ "b" : { "y" : 2, "z" : 5 }, "c" : ${b} }""") val resolved = merge(obj1, obj2).toConfig assertEquals(3, resolved.getObject("c").size()) assertEquals(1, resolved.getInt("c.x")) assertEquals(2, resolved.getInt("c.y")) assertEquals(4, resolved.getInt("c.z")) } @Test def mergeObjectWithSubstituted() { val obj1 = parseObject("""{ "a" : { "x" : 1, "z" : 4 }, "c" : { "z" : 42 } }""") val obj2 = parseObject("""{ "b" : { "y" : 2, "z" : 5 }, "c" : ${b} }""") val resolved = merge(obj1, obj2).toConfig assertEquals(2, resolved.getObject("c").size()) assertEquals(2, resolved.getInt("c.y")) assertEquals(42, resolved.getInt("c.z")) val resolved2 = merge(obj2, obj1).toConfig assertEquals(2, resolved2.getObject("c").size()) assertEquals(2, resolved2.getInt("c.y")) assertEquals(5, resolved2.getInt("c.z")) } private val cycleObject = { parseObject(""" { "foo" : ${bar}, "bar" : ${a.b.c}, "a" : { "b" : { "c" : ${foo} } } } """) } @Test def mergeHidesCycles() { // the point here is that we should not try to evaluate a substitution // that's been overridden, and thus not end up with a cycle as long // as we override the problematic link in the cycle. val e = intercept[ConfigException.UnresolvedSubstitution] { val v = resolveNoSystem(subst("foo"), cycleObject) } assertTrue("wrong exception: " + e.getMessage, e.getMessage().contains("cycle")) val fixUpCycle = parseObject(""" { "a" : { "b" : { "c" : 57 } } } """) val merged = mergeUnresolved(fixUpCycle, cycleObject) val v = resolveNoSystem(subst("foo"), merged) assertEquals(intValue(57), v); } @Test def mergeWithObjectInFrontKeepsCycles() { // the point here is that if our eventual value will be an object, then // we have to evaluate the substitution to see if it's an object to merge, // so we don't avoid the cycle. val e = intercept[ConfigException.UnresolvedSubstitution] { val v = resolveNoSystem(subst("foo"), cycleObject) } assertTrue("wrong exception: " + e.getMessage, e.getMessage().contains("cycle")) val fixUpCycle = parseObject(""" { "a" : { "b" : { "c" : { "q" : "u" } } } } """) val merged = mergeUnresolved(fixUpCycle, cycleObject) val e2 = intercept[ConfigException.UnresolvedSubstitution] { val v = resolveNoSystem(subst("foo"), merged) } // TODO: it would be nicer if the above threw BadValue with an // explanation about the cycle. //assertTrue(e2.getMessage().contains("cycle")) } @Test def mergeSeriesOfSubstitutions() { val obj1 = parseObject("""{ "a" : { "x" : 1, "q" : 4 }, "j" : ${a} }""") val obj2 = parseObject("""{ "b" : { "y" : 2, "q" : 5 }, "j" : ${b} }""") val obj3 = parseObject("""{ "c" : { "z" : 3, "q" : 6 }, "j" : ${c} }""") associativeMerge(Seq(obj1, obj2, obj3)) { merged => val resolved = resolveNoSystem(merged, merged) assertEquals(4, resolved.getObject("j").size()) assertEquals(1, resolved.getInt("j.x")) assertEquals(2, resolved.getInt("j.y")) assertEquals(3, resolved.getInt("j.z")) assertEquals(4, resolved.getInt("j.q")) } } @Test def mergePrimitiveAndTwoSubstitutions() { val obj1 = parseObject("""{ "j" : 42 }""") val obj2 = parseObject("""{ "b" : { "y" : 2, "q" : 5 }, "j" : ${b} }""") val obj3 = parseObject("""{ "c" : { "z" : 3, "q" : 6 }, "j" : ${c} }""") associativeMerge(Seq(obj1, obj2, obj3)) { merged => val resolved = resolveNoSystem(merged, merged) assertEquals(3, resolved.root.size()) assertEquals(42, resolved.getInt("j")); assertEquals(2, resolved.getInt("b.y")) assertEquals(3, resolved.getInt("c.z")) } } @Test def mergeObjectAndTwoSubstitutions() { val obj1 = parseObject("""{ "j" : { "x" : 1, "q" : 4 } }""") val obj2 = parseObject("""{ "b" : { "y" : 2, "q" : 5 }, "j" : ${b} }""") val obj3 = parseObject("""{ "c" : { "z" : 3, "q" : 6 }, "j" : ${c} }""") associativeMerge(Seq(obj1, obj2, obj3)) { merged => val resolved = resolveNoSystem(merged, merged) assertEquals(4, resolved.getObject("j").size()) assertEquals(1, resolved.getInt("j.x")) assertEquals(2, resolved.getInt("j.y")) assertEquals(3, resolved.getInt("j.z")) assertEquals(4, resolved.getInt("j.q")) } } @Test def mergeObjectSubstitutionObjectSubstitution() { val obj1 = parseObject("""{ "j" : { "w" : 1, "q" : 5 } }""") val obj2 = parseObject("""{ "b" : { "x" : 2, "q" : 6 }, "j" : ${b} }""") val obj3 = parseObject("""{ "j" : { "y" : 3, "q" : 7 } }""") val obj4 = parseObject("""{ "c" : { "z" : 4, "q" : 8 }, "j" : ${c} }""") associativeMerge(Seq(obj1, obj2, obj3, obj4)) { merged => val resolved = resolveNoSystem(merged, merged) assertEquals(5, resolved.getObject("j").size()) assertEquals(1, resolved.getInt("j.w")) assertEquals(2, resolved.getInt("j.x")) assertEquals(3, resolved.getInt("j.y")) assertEquals(4, resolved.getInt("j.z")) assertEquals(5, resolved.getInt("j.q")) } } private def ignoresFallbacks(m: ConfigMergeable) = { m match { case v: AbstractConfigValue => v.ignoresFallbacks() case c: SimpleConfig => c.root.ignoresFallbacks() } } private def testIgnoredMergesDoNothing(nonEmpty: ConfigMergeable) { // falling back to a primitive once should switch us to "ignoreFallbacks" mode // and then twice should "return this". Falling back to an empty object should // return this unless the empty object was ignoreFallbacks and then we should // "catch" its ignoreFallbacks. // some of what this tests is just optimization, not API contract (withFallback // can return a new object anytime it likes) but want to be sure we do the // optimizations. val empty = SimpleConfigObject.empty(null) val primitive = intValue(42) val emptyIgnoringFallbacks = empty.withFallback(primitive) val nonEmptyIgnoringFallbacks = nonEmpty.withFallback(primitive) assertEquals(false, empty.ignoresFallbacks()) assertEquals(true, primitive.ignoresFallbacks()) assertEquals(true, emptyIgnoringFallbacks.ignoresFallbacks()) assertEquals(false, ignoresFallbacks(nonEmpty)) assertEquals(true, ignoresFallbacks(nonEmptyIgnoringFallbacks)) assertTrue(nonEmpty ne nonEmptyIgnoringFallbacks) assertTrue(empty ne emptyIgnoringFallbacks) // falling back from one object to another should not make us ignore fallbacks assertEquals(false, ignoresFallbacks(nonEmpty.withFallback(empty))) assertEquals(false, ignoresFallbacks(empty.withFallback(nonEmpty))) assertEquals(false, ignoresFallbacks(empty.withFallback(empty))) assertEquals(false, ignoresFallbacks(nonEmpty.withFallback(nonEmpty))) // falling back from primitive just returns this assertTrue(primitive eq primitive.withFallback(empty)) assertTrue(primitive eq primitive.withFallback(nonEmpty)) assertTrue(primitive eq primitive.withFallback(nonEmptyIgnoringFallbacks)) // falling back again from an ignoreFallbacks should be a no-op, return this assertTrue(nonEmptyIgnoringFallbacks eq nonEmptyIgnoringFallbacks.withFallback(empty)) assertTrue(nonEmptyIgnoringFallbacks eq nonEmptyIgnoringFallbacks.withFallback(primitive)) assertTrue(emptyIgnoringFallbacks eq emptyIgnoringFallbacks.withFallback(empty)) assertTrue(emptyIgnoringFallbacks eq emptyIgnoringFallbacks.withFallback(primitive)) } @Test def ignoredMergesDoNothing() { val conf = parseConfig("{ a : 1 }") testIgnoredMergesDoNothing(conf) } @Test def testNoMergeAcrossArray() { val conf = parseConfig("a: {b:1}, a: [2,3], a:{c:4}") assertFalse("a.b found in: " + conf, conf.hasPath("a.b")) assertTrue("a.c not found in: " + conf, conf.hasPath("a.c")) } @Test def testNoMergeAcrossUnresolvedArray() { val conf = parseConfig("a: {b:1}, a: [2,${x}], a:{c:4}, x: 42") assertFalse("a.b found in: " + conf, conf.hasPath("a.b")) assertTrue("a.c not found in: " + conf, conf.hasPath("a.c")) } @Test def testNoMergeLists() { val conf = parseConfig("a: [1,2], a: [3,4]") assertEquals("lists did not merge", Seq(3, 4), conf.getIntList("a").asScala) } @Test def testListsWithFallback() { val list1 = ConfigValueFactory.fromIterable(Seq(1, 2, 3).asJava) val list2 = ConfigValueFactory.fromIterable(Seq(4, 5, 6).asJava) val merged1 = list1.withFallback(list2) val merged2 = list2.withFallback(list1) assertEquals("lists did not merge 1", list1, merged1) assertEquals("lists did not merge 2", list2, merged2) assertFalse("equals is working on these", list1 == list2) assertFalse("equals is working on these", list1 == merged2) assertFalse("equals is working on these", list2 == merged1) } @Test def integerRangeChecks() { val conf = parseConfig("{ tooNegative: " + (Integer.MIN_VALUE - 1L) + ", tooPositive: " + (Integer.MAX_VALUE + 1L) + "}") val en = intercept[ConfigException.WrongType] { conf.getInt("tooNegative") } assertTrue(en.getMessage.contains("range")) val ep = intercept[ConfigException.WrongType] { conf.getInt("tooPositive") } assertTrue(ep.getMessage.contains("range")) } @Test def test01Getting() { val conf = ConfigFactory.load("test01") // get all the primitive types assertEquals(42, conf.getInt("ints.fortyTwo")) assertEquals(42, conf.getInt("ints.fortyTwoAgain")) assertEquals(42L, conf.getLong("ints.fortyTwoAgain")) assertEquals(42.1, conf.getDouble("floats.fortyTwoPointOne"), 1e-6) assertEquals(42.1, conf.getDouble("floats.fortyTwoPointOneAgain"), 1e-6) assertEquals(0.33, conf.getDouble("floats.pointThirtyThree"), 1e-6) assertEquals(0.33, conf.getDouble("floats.pointThirtyThreeAgain"), 1e-6) assertEquals("abcd", conf.getString("strings.abcd")) assertEquals("abcd", conf.getString("strings.abcdAgain")) assertEquals("null bar 42 baz true 3.14 hi", conf.getString("strings.concatenated")) assertEquals(true, conf.getBoolean("booleans.trueAgain")) assertEquals(false, conf.getBoolean("booleans.falseAgain")) // to get null we have to use the get() method from Map, // which takes a key and not a path assertEquals(nullValue(), conf.getObject("nulls").get("null")) assertNull(conf.root.get("notinthefile")) // get stuff with getValue assertEquals(intValue(42), conf.getValue("ints.fortyTwo")) assertEquals(stringValue("abcd"), conf.getValue("strings.abcd")) // get stuff with getAny assertEquals(42, conf.getAnyRef("ints.fortyTwo")) assertEquals("abcd", conf.getAnyRef("strings.abcd")) assertEquals(false, conf.getAnyRef("booleans.falseAgain")) // get empty array as any type of array assertEquals(Seq(), conf.getAnyRefList("arrays.empty").asScala) assertEquals(Seq(), conf.getIntList("arrays.empty").asScala) assertEquals(Seq(), conf.getLongList("arrays.empty").asScala) assertEquals(Seq(), conf.getStringList("arrays.empty").asScala) assertEquals(Seq(), conf.getLongList("arrays.empty").asScala) assertEquals(Seq(), conf.getDoubleList("arrays.empty").asScala) assertEquals(Seq(), conf.getObjectList("arrays.empty").asScala) assertEquals(Seq(), conf.getBooleanList("arrays.empty").asScala) assertEquals(Seq(), conf.getNumberList("arrays.empty").asScala) assertEquals(Seq(), conf.getList("arrays.empty").asScala) // get typed arrays assertEquals(Seq(1, 2, 3), conf.getIntList("arrays.ofInt").asScala) assertEquals(Seq(1L, 2L, 3L), conf.getLongList("arrays.ofInt").asScala) assertEquals(Seq("a", "b", "c"), conf.getStringList("arrays.ofString").asScala) assertEquals(Seq(3.14, 4.14, 5.14), conf.getDoubleList("arrays.ofDouble").asScala) assertEquals(Seq(null, null, null), conf.getAnyRefList("arrays.ofNull").asScala) assertEquals(Seq(true, false), conf.getBooleanList("arrays.ofBoolean").asScala) val listOfLists = conf.getAnyRefList("arrays.ofArray").asScala map { _.asInstanceOf[java.util.List[_]].asScala } assertEquals(Seq(Seq("a", "b", "c"), Seq("a", "b", "c"), Seq("a", "b", "c")), listOfLists) assertEquals(3, conf.getObjectList("arrays.ofObject").asScala.length) assertEquals(Seq("a", "b"), conf.getStringList("arrays.firstElementNotASubst").asScala) // plain getList should work assertEquals(Seq(intValue(1), intValue(2), intValue(3)), conf.getList("arrays.ofInt").asScala) assertEquals(Seq(stringValue("a"), stringValue("b"), stringValue("c")), conf.getList("arrays.ofString").asScala) // make sure floats starting with a '.' are parsed as strings (they will be converted to double on demand) assertEquals(ConfigValueType.STRING, conf.getValue("floats.pointThirtyThree").valueType()) } @Test def test01Exceptions() { val conf = ConfigFactory.load("test01") // should throw Missing if key doesn't exist intercept[ConfigException.Missing] { conf.getInt("doesnotexist") } // should throw Null if key is null intercept[ConfigException.Null] { conf.getInt("nulls.null") } intercept[ConfigException.Null] { conf.getIntList("nulls.null") } intercept[ConfigException.Null] { conf.getMilliseconds("nulls.null") } intercept[ConfigException.Null] { conf.getNanoseconds("nulls.null") } intercept[ConfigException.Null] { conf.getBytes("nulls.null") } intercept[ConfigException.Null] { conf.getMemorySize("nulls.null") } // should throw WrongType if key is wrong type and not convertible intercept[ConfigException.WrongType] { conf.getInt("booleans.trueAgain") } intercept[ConfigException.WrongType] { conf.getBooleanList("arrays.ofInt") } intercept[ConfigException.WrongType] { conf.getIntList("arrays.ofBoolean") } intercept[ConfigException.WrongType] { conf.getObjectList("arrays.ofInt") } intercept[ConfigException.WrongType] { conf.getMilliseconds("ints") } intercept[ConfigException.WrongType] { conf.getNanoseconds("ints") } intercept[ConfigException.WrongType] { conf.getBytes("ints") } intercept[ConfigException.WrongType] { conf.getMemorySize("ints") } // should throw BadPath on various bad paths intercept[ConfigException.BadPath] { conf.getInt(".bad") } intercept[ConfigException.BadPath] { conf.getInt("bad.") } intercept[ConfigException.BadPath] { conf.getInt("bad..bad") } // should throw BadValue on things that don't parse // as durations and sizes intercept[ConfigException.BadValue] { conf.getMilliseconds("strings.a") } intercept[ConfigException.BadValue] { conf.getNanoseconds("strings.a") } intercept[ConfigException.BadValue] { conf.getBytes("strings.a") } intercept[ConfigException.BadValue] { conf.getMemorySize("strings.a") } } @Test def test01Conversions() { val conf = ConfigFactory.load("test01") // should convert numbers to string assertEquals("42", conf.getString("ints.fortyTwo")) assertEquals("42.1", conf.getString("floats.fortyTwoPointOne")) assertEquals(".33", conf.getString("floats.pointThirtyThree")) // should convert string to number assertEquals(57, conf.getInt("strings.number")) assertEquals(3.14, conf.getDouble("strings.double"), 1e-6) assertEquals(0.33, conf.getDouble("strings.doubleStartingWithDot"), 1e-6) // should convert strings to boolean assertEquals(true, conf.getBoolean("strings.true")) assertEquals(true, conf.getBoolean("strings.yes")) assertEquals(false, conf.getBoolean("strings.false")) assertEquals(false, conf.getBoolean("strings.no")) // converting some random string to boolean fails though intercept[ConfigException.WrongType] { conf.getBoolean("strings.abcd") } // FIXME test convert string "null" to a null value // should not convert strings to object or list intercept[ConfigException.WrongType] { conf.getObject("strings.a") } intercept[ConfigException.WrongType] { conf.getList("strings.a") } // should not convert object or list to string intercept[ConfigException.WrongType] { conf.getString("ints") } intercept[ConfigException.WrongType] { conf.getString("arrays.ofInt") } // should get durations def asNanos(secs: Int) = TimeUnit.SECONDS.toNanos(secs) assertEquals(1000L, conf.getMilliseconds("durations.second")) assertEquals(asNanos(1), conf.getNanoseconds("durations.second")) assertEquals(1000L, conf.getMilliseconds("durations.secondAsNumber")) assertEquals(asNanos(1), conf.getNanoseconds("durations.secondAsNumber")) assertEquals(Seq(1000L, 2000L, 3000L, 4000L), conf.getMillisecondsList("durations.secondsList").asScala) assertEquals(Seq(asNanos(1), asNanos(2), asNanos(3), asNanos(4)), conf.getNanosecondsList("durations.secondsList").asScala) assertEquals(500L, conf.getMilliseconds("durations.halfSecond")) assertEquals(4878955355435272204L, conf.getNanoseconds("durations.largeNanos")) assertEquals(4878955355435272204L, conf.getNanoseconds("durations.plusLargeNanos")) assertEquals(-4878955355435272204L, conf.getNanoseconds("durations.minusLargeNanos")) // get durations as java.time.Duration assertEquals(1000L, conf.getDuration("durations.second").toMillis) assertEquals(asNanos(1), conf.getDuration("durations.second").toNanos) assertEquals(1000L, conf.getDuration("durations.secondAsNumber").toMillis) assertEquals(asNanos(1), conf.getDuration("durations.secondAsNumber").toNanos) assertEquals(Seq(1000L, 2000L, 3000L, 4000L), conf.getDurationList("durations.secondsList").asScala.map(_.toMillis)) assertEquals(Seq(asNanos(1), asNanos(2), asNanos(3), asNanos(4)), conf.getDurationList("durations.secondsList").asScala.map(_.toNanos)) assertEquals(500L, conf.getDuration("durations.halfSecond").toMillis) assertEquals(4878955355435272204L, conf.getDuration("durations.largeNanos").toNanos) assertEquals(4878955355435272204L, conf.getDuration("durations.plusLargeNanos").toNanos) assertEquals(-4878955355435272204L, conf.getDuration("durations.minusLargeNanos").toNanos) def assertDurationAsTimeUnit(unit: TimeUnit): Unit = { def ns2unit(l: Long) = unit.convert(l, NANOSECONDS) def ms2unit(l: Long) = unit.convert(l, MILLISECONDS) def s2unit(i: Int) = unit.convert(i, SECONDS) assertEquals(ms2unit(1000L), conf.getDuration("durations.second", unit)) assertEquals(s2unit(1), conf.getDuration("durations.second", unit)) assertEquals(ms2unit(1000L), conf.getDuration("durations.secondAsNumber", unit)) assertEquals(s2unit(1), conf.getDuration("durations.secondAsNumber", unit)) assertEquals(Seq(1000L, 2000L, 3000L, 4000L) map ms2unit, conf.getDurationList("durations.secondsList", unit).asScala) assertEquals(Seq(1, 2, 3, 4) map s2unit, conf.getDurationList("durations.secondsList", unit).asScala) assertEquals(ms2unit(500L), conf.getDuration("durations.halfSecond", unit)) assertEquals(ms2unit(1L), conf.getDuration("durations.millis", unit)) assertEquals(ms2unit(2L), conf.getDuration("durations.micros", unit)) assertEquals(ns2unit(4878955355435272204L), conf.getDuration("durations.largeNanos", unit)) assertEquals(ns2unit(4878955355435272204L), conf.getDuration("durations.plusLargeNanos", unit)) assertEquals(ns2unit(-4878955355435272204L), conf.getDuration("durations.minusLargeNanos", unit)) } assertDurationAsTimeUnit(NANOSECONDS) assertDurationAsTimeUnit(MICROSECONDS) assertDurationAsTimeUnit(MILLISECONDS) assertDurationAsTimeUnit(SECONDS) assertDurationAsTimeUnit(MINUTES) assertDurationAsTimeUnit(HOURS) assertDurationAsTimeUnit(DAYS) // should get size in bytes assertEquals(1024 * 1024L, conf.getBytes("memsizes.meg")) assertEquals(1024 * 1024L, conf.getBytes("memsizes.megAsNumber")) assertEquals(Seq(1024 * 1024L, 1024 * 1024L, 1024L * 1024L), conf.getBytesList("memsizes.megsList").asScala) assertEquals(512 * 1024L, conf.getBytes("memsizes.halfMeg")) // should get size as a ConfigMemorySize assertEquals(1024 * 1024L, conf.getMemorySize("memsizes.meg").toBytes) assertEquals(1024 * 1024L, conf.getMemorySize("memsizes.megAsNumber").toBytes) assertEquals(Seq(1024 * 1024L, 1024 * 1024L, 1024L * 1024L), conf.getMemorySizeList("memsizes.megsList").asScala.map(_.toBytes)) assertEquals(512 * 1024L, conf.getMemorySize("memsizes.halfMeg").toBytes) } @Test def test01MergingOtherFormats() { val conf = ConfigFactory.load("test01") // should have loaded stuff from .json assertEquals(1, conf.getInt("fromJson1")) assertEquals("A", conf.getString("fromJsonA")) // should have loaded stuff from .properties assertEquals("abc", conf.getString("fromProps.abc")) assertEquals(1, conf.getInt("fromProps.one")) assertEquals(true, conf.getBoolean("fromProps.bool")) } @Test def test01ToString() { val conf = ConfigFactory.load("test01") // toString() on conf objects doesn't throw (toString is just a debug string so not testing its result) conf.toString() } @Test def test01SystemFallbacks() { val conf = ConfigFactory.load("test01") val jv = System.getProperty("java.version") assertNotNull(jv) assertEquals(jv, conf.getString("system.javaversion")) val home = System.getenv("HOME") if (home != null) { assertEquals(home, conf.getString("system.home")) } else { assertEquals(null, conf.getObject("system").get("home")) } } @Test def test01Origins() { val conf = ConfigFactory.load("test01") val o1 = conf.getValue("ints.fortyTwo").origin() // the checkout directory would be in between this startsWith and endsWith assertTrue("description starts with resource '" + o1.description + "'", o1.description.startsWith("test01.conf @")) assertTrue("description ends with url and line '" + o1.description + "'", o1.description.endsWith("/config/target/test-classes/test01.conf: 3")) assertEquals("test01.conf", o1.resource) assertTrue("url ends with resource file", o1.url.getPath.endsWith("/config/target/test-classes/test01.conf")) assertEquals(3, o1.lineNumber) val o2 = conf.getValue("fromJson1").origin() // the checkout directory would be in between this startsWith and endsWith assertTrue("description starts with json resource '" + o2.description + "'", o2.description.startsWith("test01.json @")) assertTrue("description of json resource ends with url and line '" + o2.description + "'", o2.description.endsWith("/config/target/test-classes/test01.json: 2")) assertEquals("test01.json", o2.resource) assertTrue("url ends with json resource file", o2.url.getPath.endsWith("/config/target/test-classes/test01.json")) assertEquals(2, o2.lineNumber) val o3 = conf.getValue("fromProps.bool").origin() // the checkout directory would be in between this startsWith and endsWith assertTrue("description starts with props resource '" + o3.description + "'", o3.description.startsWith("test01.properties @")) assertTrue("description of props resource ends with url '" + o3.description + "'", o3.description.endsWith("/config/target/test-classes/test01.properties")) assertEquals("test01.properties", o3.resource) assertTrue("url ends with props resource file", o3.url.getPath.endsWith("/config/target/test-classes/test01.properties")) // we don't have line numbers for properties files assertEquals(-1, o3.lineNumber) } @Test def test01EntrySet() { val conf = ConfigFactory.load("test01") val javaEntries = conf.entrySet() val entries = Map((javaEntries.asScala.toSeq map { e => (e.getKey(), e.getValue()) }): _*) assertEquals(Some(intValue(42)), entries.get("ints.fortyTwo")) assertEquals(None, entries.get("nulls.null")) } @Test def test01Serializable() { // we can't ever test an expected serialization here because it // will have system props in it that vary by test system, // and the ConfigOrigin in there will also vary by test system val conf = ConfigFactory.load("test01") val confCopy = checkSerializable(conf) } @Test def test02SubstitutionsWithWeirdPaths() { val conf = ConfigFactory.load("test02") assertEquals(42, conf.getInt("42_a")) assertEquals(42, conf.getInt("42_b")) assertEquals(57, conf.getInt("57_a")) assertEquals(57, conf.getInt("57_b")) assertEquals(103, conf.getInt("103_a")) } @Test def test02UseWeirdPathsWithConfigObject() { val conf = ConfigFactory.load("test02") // we're checking that the getters in ConfigObject support // these weird path expressions assertEquals(42, conf.getInt(""" "".""."" """)) assertEquals(57, conf.getInt("a.b.c")) assertEquals(57, conf.getInt(""" "a"."b"."c" """)) assertEquals(103, conf.getInt(""" "a.b.c" """)) } @Test def test03Includes() { val conf = ConfigFactory.load("test03") // include should have overridden the "ints" value in test03 assertEquals(42, conf.getInt("test01.ints.fortyTwo")) // include should have been overridden by 42 assertEquals(42, conf.getInt("test01.booleans")); assertEquals(42, conf.getInt("test01.booleans")); // include should have gotten .properties and .json also assertEquals("abc", conf.getString("test01.fromProps.abc")) assertEquals("A", conf.getString("test01.fromJsonA")) // test02 was included assertEquals(57, conf.getInt("test02.a.b.c")) // equiv01/original.json was included (it has a slash in the name) assertEquals("a", conf.getString("equiv01.strings.a")) // Now check that substitutions still work assertEquals(42, conf.getInt("test01.ints.fortyTwoAgain")) assertEquals(Seq("a", "b", "c"), conf.getStringList("test01.arrays.ofString").asScala) assertEquals(103, conf.getInt("test02.103_a")) // and system fallbacks still work val jv = System.getProperty("java.version") assertNotNull(jv) assertEquals(jv, conf.getString("test01.system.javaversion")) val home = System.getenv("HOME") if (home != null) { assertEquals(home, conf.getString("test01.system.home")) } else { assertEquals(null, conf.getObject("test01.system").get("home")) } val concatenated = conf.getString("test01.system.concatenated") assertTrue(concatenated.contains("Your Java version")) assertTrue(concatenated.contains(jv)) assertTrue(concatenated.contains(conf.getString("test01.system.userhome"))) // check that includes into the root object work and that // "substitutions look relative-to-included-file first then at root second" works assertEquals("This is in the included file", conf.getString("a")); assertEquals("This is in the including file", conf.getString("b")); assertEquals("This is in the included file", conf.getString("subtree.a")); assertEquals("This is in the including file", conf.getString("subtree.b")); } @Test def test04LoadAkkaReference() { val conf = ConfigFactory.load("test04") // Note, test04 is an unmodified old-style akka.conf, // which means it has an outer akka{} namespace. // that namespace wouldn't normally be used with // this library because the conf object is not global, // it's per-module already. assertEquals("2.0-SNAPSHOT", conf.getString("akka.version")) assertEquals(8, conf.getInt("akka.event-handler-dispatcher.max-pool-size")) assertEquals("round-robin", conf.getString("akka.actor.deployment.\"/app/service-ping\".router")) assertEquals(true, conf.getBoolean("akka.stm.quick-release")) } @Test def test05LoadPlayApplicationConf() { val conf = ConfigFactory.load("test05") assertEquals("prod", conf.getString("%prod.application.mode")) assertEquals("Yet another blog", conf.getString("blog.title")) } @Test def test06Merge() { // test06 mostly exists because its render() round trip is tricky val conf = ConfigFactory.load("test06") assertEquals(2, conf.getInt("x")) assertEquals(10, conf.getInt("y.foo")) assertEquals("world", conf.getString("y.hello")) } @Test def test07IncludingResourcesFromFiles() { // first, check that when loading from classpath we include another classpath resource val fromClasspath = ConfigFactory.parseResources(classOf[ConfigTest], "/test07.conf") assertEquals("This is to test classpath searches.", fromClasspath.getString("test-lib.description")) // second, check that when loading from a file it falls back to classpath val fromFile = ConfigFactory.parseFile(resourceFile("test07.conf")) assertEquals("This is to test classpath searches.", fromFile.getString("test-lib.description")) // third, check that a file: URL is the same val fromURL = ConfigFactory.parseURL(resourceFile("test07.conf").toURI.toURL) assertEquals("This is to test classpath searches.", fromURL.getString("test-lib.description")) } @Test def test08IncludingSlashPrefixedResources() { // first, check that when loading from classpath we include another classpath resource val fromClasspath = ConfigFactory.parseResources(classOf[ConfigTest], "/test08.conf") assertEquals("This is to test classpath searches.", fromClasspath.getString("test-lib.description")) // second, check that when loading from a file it falls back to classpath val fromFile = ConfigFactory.parseFile(resourceFile("test08.conf")) assertEquals("This is to test classpath searches.", fromFile.getString("test-lib.description")) // third, check that a file: URL is the same val fromURL = ConfigFactory.parseURL(resourceFile("test08.conf").toURI.toURL) assertEquals("This is to test classpath searches.", fromURL.getString("test-lib.description")) } @Test def test09DelayedMerge() { val conf = ConfigFactory.parseResources(classOf[ConfigTest], "/test09.conf") assertEquals(classOf[ConfigDelayedMergeObject].getSimpleName, conf.root.get("a").getClass.getSimpleName) assertEquals(classOf[ConfigDelayedMerge].getSimpleName, conf.root.get("b").getClass.getSimpleName) // a.c should work without resolving because no more merging is needed to compute it assertEquals(3, conf.getInt("a.c")) intercept[ConfigException.NotResolved] { conf.getInt("a.q") } // be sure resolving doesn't throw val resolved = conf.resolve() assertEquals(3, resolved.getInt("a.c")) assertEquals(5, resolved.getInt("b")) assertEquals(10, resolved.getInt("a.q")) } @Test def test10DelayedMergeRelativizing() { val conf = ConfigFactory.parseResources(classOf[ConfigTest], "/test10.conf") val resolved = conf.resolve() assertEquals(3, resolved.getInt("foo.a.c")) assertEquals(5, resolved.getInt("foo.b")) assertEquals(10, resolved.getInt("foo.a.q")) assertEquals(3, resolved.getInt("bar.nested.a.c")) assertEquals(5, resolved.getInt("bar.nested.b")) assertEquals(10, resolved.getInt("bar.nested.a.q")) } @Test def renderRoundTrip() { val allBooleans = true :: false :: Nil val optionsCombos = { for ( formatted <- allBooleans; originComments <- allBooleans; comments <- allBooleans; json <- allBooleans ) yield ConfigRenderOptions.defaults() .setFormatted(formatted) .setOriginComments(originComments) .setComments(comments) .setJson(json) }.toSeq for (i <- 1 to 10) { val numString = i.toString val name = "/test" + { if (numString.size == 1) "0" else "" } + numString val conf = ConfigFactory.parseResourcesAnySyntax(classOf[ConfigTest], name, ConfigParseOptions.defaults().setAllowMissing(false)) for (renderOptions <- optionsCombos) { val unresolvedRender = conf.root.render(renderOptions) val resolved = conf.resolve() val resolvedRender = resolved.root.render(renderOptions) val unresolvedParsed = ConfigFactory.parseString(unresolvedRender, ConfigParseOptions.defaults()) val resolvedParsed = ConfigFactory.parseString(resolvedRender, ConfigParseOptions.defaults()) try { assertEquals("unresolved options=" + renderOptions, conf.root, unresolvedParsed.root) assertEquals("resolved options=" + renderOptions, resolved.root, resolvedParsed.root) } catch { case e: Throwable => System.err.println("UNRESOLVED diff:") showDiff(conf.root, unresolvedParsed.root) System.err.println("RESOLVED diff:") showDiff(resolved.root, resolvedParsed.root) throw e } if (renderOptions.getJson() && !(renderOptions.getComments() || renderOptions.getOriginComments())) { // should get valid JSON if we don't have comments and are resolved val json = try { ConfigFactory.parseString(resolvedRender, ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)); } catch { case e: Exception => System.err.println("resolvedRender is not valid json: " + resolvedRender) throw e } } // rendering repeatedly should not make the file different (e.g. shouldn't make it longer) // unless the debug comments are in there if (!renderOptions.getOriginComments()) { val renderedAgain = resolvedParsed.root.render(renderOptions) // TODO the strings should be THE SAME not just the same length, // but there's a bug right now that sometimes object keys seem to // be re-ordered. Need to fix. assertEquals("render changed, resolved options=" + renderOptions, resolvedRender.length, renderedAgain.length) } } } } @Test def serializeRoundTrip() { for (i <- 1 to 10) { val numString = i.toString val name = "/test" + { if (numString.size == 1) "0" else "" } + numString val conf = ConfigFactory.parseResourcesAnySyntax(classOf[ConfigTest], name, ConfigParseOptions.defaults().setAllowMissing(false)) val resolved = conf.resolve() checkSerializable(resolved) } } @Test def isResolvedWorks() { val resolved = ConfigFactory.parseString("foo = 1") assertTrue("config with no substitutions starts as resolved", resolved.isResolved) val unresolved = ConfigFactory.parseString("foo = ${a}, a=42") assertFalse("config with substitutions starts as not resolved", unresolved.isResolved) val resolved2 = unresolved.resolve() assertTrue("after resolution, config is now resolved", resolved2.isResolved) } @Test def allowUnresolvedDoesAllowUnresolvedArrayElements() { val values = ConfigFactory.parseString("unknown = [someVal], known = 42") val unresolved = ConfigFactory.parseString("concat = [${unknown}[]], sibling = [${unknown}, ${known}]") unresolved.resolve(ConfigResolveOptions.defaults().setAllowUnresolved(true)) unresolved.withFallback(values).resolve() unresolved.resolveWith(values) } @Test def allowUnresolvedDoesAllowUnresolved() { val values = ConfigFactory.parseString("{ foo = 1, bar = 2, m = 3, n = 4}") assertTrue("config with no substitutions starts as resolved", values.isResolved) val unresolved = ConfigFactory.parseString("a = ${foo}, b = ${bar}, c { x = ${m}, y = ${n}, z = foo${m}bar }, alwaysResolveable=${alwaysValue}, alwaysValue=42") assertFalse("config with substitutions starts as not resolved", unresolved.isResolved) // resolve() by default throws with unresolveable substs intercept[ConfigException.UnresolvedSubstitution] { unresolved.resolve(ConfigResolveOptions.defaults()) } // we shouldn't be able to get a value without resolving it intercept[ConfigException.NotResolved] { unresolved.getInt("alwaysResolveable") } val allowedUnresolved = unresolved.resolve(ConfigResolveOptions.defaults().setAllowUnresolved(true)) // when we partially-resolve we should still resolve what we can assertEquals("we resolved the resolveable", 42, allowedUnresolved.getInt("alwaysResolveable")) // but unresolved should still all throw for (k <- Seq("a", "b", "c.x", "c.y")) { intercept[ConfigException.NotResolved] { allowedUnresolved.getInt(k) } } intercept[ConfigException.NotResolved] { allowedUnresolved.getString("c.z") } // and the partially-resolved thing is not resolved assertFalse("partially-resolved object is not resolved", allowedUnresolved.isResolved) // scope "val resolved" { // and given the values for the resolve, we should be able to val resolved = allowedUnresolved.withFallback(values).resolve() for (kv <- Seq("a" -> 1, "b" -> 2, "c.x" -> 3, "c.y" -> 4)) { assertEquals(kv._2, resolved.getInt(kv._1)) } assertEquals("foo3bar", resolved.getString("c.z")) assertTrue("fully resolved object is resolved", resolved.isResolved) } // we should also be able to use resolveWith { val resolved = allowedUnresolved.resolveWith(values) for (kv <- Seq("a" -> 1, "b" -> 2, "c.x" -> 3, "c.y" -> 4)) { assertEquals(kv._2, resolved.getInt(kv._1)) } assertEquals("foo3bar", resolved.getString("c.z")) assertTrue("fully resolved object is resolved", resolved.isResolved) } } @Test def resolveWithWorks(): Unit = { // the a=42 is present here to be sure it gets ignored when we resolveWith val unresolved = ConfigFactory.parseString("foo = ${a}, a = 42") assertEquals(42, unresolved.resolve().getInt("foo")) val source = ConfigFactory.parseString("a = 43") val resolved = unresolved.resolveWith(source) assertEquals(43, resolved.getInt("foo")) } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/ConfigValueTest.scala000066400000000000000000001136161277147274600277630ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ import com.typesafe.config.ConfigValue import java.util.Collections import java.net.URL import scala.collection.JavaConverters._ import com.typesafe.config.ConfigObject import com.typesafe.config.ConfigList import com.typesafe.config.ConfigException import com.typesafe.config.ConfigValueType import com.typesafe.config.ConfigRenderOptions import com.typesafe.config.ConfigValueFactory import com.typesafe.config.ConfigFactory class ConfigValueTest extends TestUtils { @Test def configOriginEquality() { val a = SimpleConfigOrigin.newSimple("foo") val sameAsA = SimpleConfigOrigin.newSimple("foo") val b = SimpleConfigOrigin.newSimple("bar") checkEqualObjects(a, a) checkEqualObjects(a, sameAsA) checkNotEqualObjects(a, b) } @Test def configOriginNotSerializable() { val a = SimpleConfigOrigin.newSimple("foo") checkNotSerializable(a) } @Test def configIntEquality() { val a = intValue(42) val sameAsA = intValue(42) val b = intValue(43) checkEqualObjects(a, a) checkEqualObjects(a, sameAsA) checkNotEqualObjects(a, b) } @Test def configIntSerializable() { val expectedSerialization = "" + "ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" + "_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_902000000_-050000001906" + "0000000D000B_f_a_k_e_ _o_r_i_g_i_n090000000100010400000009020000002A0002_4_20103" + "000000010001_x" val a = intValue(42) val b = checkSerializable(expectedSerialization, a) assertEquals(42, b.unwrapped) } @Test def configLongEquality() { val a = longValue(Integer.MAX_VALUE + 42L) val sameAsA = longValue(Integer.MAX_VALUE + 42L) val b = longValue(Integer.MAX_VALUE + 43L) checkEqualObjects(a, a) checkEqualObjects(a, sameAsA) checkNotEqualObjects(a, b) } @Test def configLongSerializable() { val expectedSerialization = "" + "ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" + "_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_E02000000_9050000001906" + "0000000D000B_f_a_k_e_ _o_r_i_g_i_n090000000100010400000015030000000080000029000A" + "_2_1_4_7_4_8_3_6_8_90103000000010001_x" val a = longValue(Integer.MAX_VALUE + 42L) val b = checkSerializable(expectedSerialization, a) assertEquals(Integer.MAX_VALUE + 42L, b.unwrapped) } @Test def configIntAndLongEquality() { val longVal = longValue(42L) val intValue = longValue(42) val longValueB = longValue(43L) val intValueB = longValue(43) checkEqualObjects(intValue, longVal) checkEqualObjects(intValueB, longValueB) checkNotEqualObjects(intValue, longValueB) checkNotEqualObjects(intValueB, longVal) } @Test def configDoubleEquality() { val a = doubleValue(3.14) val sameAsA = doubleValue(3.14) val b = doubleValue(4.14) checkEqualObjects(a, a) checkEqualObjects(a, sameAsA) checkNotEqualObjects(a, b) } @Test def configDoubleSerializable() { val expectedSerialization = "" + "ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" + "_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w3F02000000_3050000001906" + "0000000D000B_f_a_k_e_ _o_r_i_g_i_n09000000010001040000000F0440091EB8_QEB851F0004" + "_3_._1_40103000000010001_x" val a = doubleValue(3.14) val b = checkSerializable(expectedSerialization, a) assertEquals(3.14, b.unwrapped) } @Test def configIntAndDoubleEquality() { val doubleVal = doubleValue(3.0) val intValue = longValue(3) val doubleValueB = doubleValue(4.0) val intValueB = doubleValue(4) checkEqualObjects(intValue, doubleVal) checkEqualObjects(intValueB, doubleValueB) checkNotEqualObjects(intValue, doubleValueB) checkNotEqualObjects(intValueB, doubleVal) } @Test def configNullSerializable() { val expectedSerialization = "" + "ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" + "_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_10200000025050000001906" + "0000000D000B_f_a_k_e_ _o_r_i_g_i_n090000000100010400000001000103000000010001_x" val a = nullValue() val b = checkSerializable(expectedSerialization, a) assertNull("b is null", b.unwrapped) } @Test def configBooleanSerializable() { val expectedSerialization = "" + "ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" + "_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_20200000026050000001906" + "0000000D000B_f_a_k_e_ _o_r_i_g_i_n09000000010001040000000201010103000000010001_x" val a = boolValue(true) val b = checkSerializable(expectedSerialization, a) assertEquals(true, b.unwrapped) } @Test def configStringSerializable() { val expectedSerialization = "" + "ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" + "_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_F02000000_:050000001906" + "0000000D000B_f_a_k_e_ _o_r_i_g_i_n090000000100010400000016050013_T_h_e_ _q_u_i_c" + "_k_ _b_r_o_w_n_ _f_o_x0103000000010001_x" val a = stringValue("The quick brown fox") val b = checkSerializable(expectedSerialization, a) assertEquals("The quick brown fox", b.unwrapped) } private def configMap(pairs: (String, Int)*): java.util.Map[String, AbstractConfigValue] = { val m = new java.util.HashMap[String, AbstractConfigValue]() for (p <- pairs) { m.put(p._1, intValue(p._2)) } m } @Test def configObjectEquality() { val aMap = configMap("a" -> 1, "b" -> 2, "c" -> 3) val sameAsAMap = configMap("a" -> 1, "b" -> 2, "c" -> 3) val bMap = configMap("a" -> 3, "b" -> 4, "c" -> 5) // different keys is a different case in the equals implementation val cMap = configMap("x" -> 3, "y" -> 4, "z" -> 5) val a = new SimpleConfigObject(fakeOrigin(), aMap) val sameAsA = new SimpleConfigObject(fakeOrigin(), sameAsAMap) val b = new SimpleConfigObject(fakeOrigin(), bMap) val c = new SimpleConfigObject(fakeOrigin(), cMap) checkEqualObjects(a, a) checkEqualObjects(a, sameAsA) checkEqualObjects(b, b) checkEqualObjects(c, c) checkNotEqualObjects(a, b) checkNotEqualObjects(a, c) checkNotEqualObjects(b, c) // the config for an equal object is also equal val config = a.toConfig() checkEqualObjects(config, config) checkEqualObjects(config, sameAsA.toConfig()) checkEqualObjects(a.toConfig(), config) checkNotEqualObjects(config, b.toConfig()) checkNotEqualObjects(config, c.toConfig()) // configs are not equal to objects checkNotEqualObjects(a, a.toConfig()) checkNotEqualObjects(b, b.toConfig()) } @Test def java6ConfigObjectSerializable() { val expectedSerialization = "" + "ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" + "_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_z02000000_n050000001906" + "0000000D000B_f_a_k_e_ _o_r_i_g_i_n0900000001000104000000_J07000000030001_a050000" + "000101040000000802000000010001_1010001_c050000000101040000000802000000030001_301" + "0001_b050000000101040000000802000000020001_2010103000000010001_x" val aMap = configMap("a" -> 1, "b" -> 2, "c" -> 3) val a = new SimpleConfigObject(fakeOrigin(), aMap) val b = checkSerializableOldFormat(expectedSerialization, a) assertEquals(1, b.toConfig.getInt("a")) // check that deserialized Config and ConfigObject refer to each other assertTrue(b.toConfig.root eq b) } @Test def java6ConfigConfigSerializable() { val expectedSerialization = "" + "ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" + "_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_z02000000_n050000001906" + "0000000D000B_f_a_k_e_ _o_r_i_g_i_n0900000001000104000000_J07000000030001_a050000" + "000101040000000802000000010001_1010001_c050000000101040000000802000000030001_301" + "0001_b050000000101040000000802000000020001_2010103000000010101_x" val aMap = configMap("a" -> 1, "b" -> 2, "c" -> 3) val a = new SimpleConfigObject(fakeOrigin(), aMap) val b = checkSerializableOldFormat(expectedSerialization, a.toConfig()) assertEquals(1, b.getInt("a")) // check that deserialized Config and ConfigObject refer to each other assertTrue(b.root.toConfig eq b) } @Test def configObjectSerializable() { val expectedSerialization = "" + "ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" + "_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_z02000000_n050000001906" + "0000000D000B_f_a_k_e_ _o_r_i_g_i_n0900000001000104000000_J07000000030001_a050000" + "000101040000000802000000010001_1010001_b050000000101040000000802000000020001_201" + "0001_c050000000101040000000802000000030001_3010103000000010001_x" val aMap = configMap("a" -> 1, "b" -> 2, "c" -> 3) val a = new SimpleConfigObject(fakeOrigin(), aMap) val b = checkSerializable(expectedSerialization, a) assertEquals(1, b.toConfig.getInt("a")) // check that deserialized Config and ConfigObject refer to each other assertTrue(b.toConfig.root eq b) } @Test def configConfigSerializable() { val expectedSerialization = "" + "ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" + "_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_z02000000_n050000001906" + "0000000D000B_f_a_k_e_ _o_r_i_g_i_n0900000001000104000000_J07000000030001_a050000" + "000101040000000802000000010001_1010001_b050000000101040000000802000000020001_201" + "0001_c050000000101040000000802000000030001_3010103000000010101_x" val aMap = configMap("a" -> 1, "b" -> 2, "c" -> 3) val a = new SimpleConfigObject(fakeOrigin(), aMap) val b = checkSerializable(expectedSerialization, a.toConfig()) assertEquals(1, b.getInt("a")) // check that deserialized Config and ConfigObject refer to each other assertTrue(b.root.toConfig eq b) } @Test def configListEquality() { val aScalaSeq = Seq(1, 2, 3) map { intValue(_): AbstractConfigValue } val aList = new SimpleConfigList(fakeOrigin(), aScalaSeq.asJava) val sameAsAList = new SimpleConfigList(fakeOrigin(), aScalaSeq.asJava) val bScalaSeq = Seq(4, 5, 6) map { intValue(_): AbstractConfigValue } val bList = new SimpleConfigList(fakeOrigin(), bScalaSeq.asJava) checkEqualObjects(aList, aList) checkEqualObjects(aList, sameAsAList) checkNotEqualObjects(aList, bList) } @Test def configListSerializable() { val expectedSerialization = "" + "ACED0005_s_r00_._c_o_m_._t_y_p_e_s_a_f_e_._c_o_n_f_i_g_._i_m_p_l_._S_e_r_i_a_l_i" + "_z_e_d_C_o_n_f_i_g_V_a_l_u_e00000000000000010C0000_x_p_w_q02000000_e050000001906" + "0000000D000B_f_a_k_e_ _o_r_i_g_i_n0900000001000104000000_A0600000003050000000101" + "040000000802000000010001_101050000000101040000000802000000020001_201050000000101" + "040000000802000000030001_3010103000000010001_x" val aScalaSeq = Seq(1, 2, 3) map { intValue(_): AbstractConfigValue } val aList = new SimpleConfigList(fakeOrigin(), aScalaSeq.asJava) val bList = checkSerializable(expectedSerialization, aList) assertEquals(1, bList.get(0).unwrapped()) } @Test def configReferenceEquality() { val a = subst("foo") val sameAsA = subst("foo") val b = subst("bar") val c = subst("foo", optional = true) assertTrue("wrong type " + a, a.isInstanceOf[ConfigReference]) assertTrue("wrong type " + b, b.isInstanceOf[ConfigReference]) assertTrue("wrong type " + c, c.isInstanceOf[ConfigReference]) checkEqualObjects(a, a) checkEqualObjects(a, sameAsA) checkNotEqualObjects(a, b) checkNotEqualObjects(a, c) } @Test def configReferenceNotSerializable() { val a = subst("foo") assertTrue("wrong type " + a, a.isInstanceOf[ConfigReference]) checkNotSerializable(a) } @Test def configConcatenationEquality() { val a = substInString("foo") val sameAsA = substInString("foo") val b = substInString("bar") val c = substInString("foo", optional = true) assertTrue("wrong type " + a, a.isInstanceOf[ConfigConcatenation]) assertTrue("wrong type " + b, b.isInstanceOf[ConfigConcatenation]) assertTrue("wrong type " + c, c.isInstanceOf[ConfigConcatenation]) checkEqualObjects(a, a) checkEqualObjects(a, sameAsA) checkNotEqualObjects(a, b) checkNotEqualObjects(a, c) } @Test def configConcatenationNotSerializable() { val a = substInString("foo") assertTrue("wrong type " + a, a.isInstanceOf[ConfigConcatenation]) checkNotSerializable(a) } @Test def configDelayedMergeEquality() { val s1 = subst("foo") val s2 = subst("bar") val a = new ConfigDelayedMerge(fakeOrigin(), List[AbstractConfigValue](s1, s2).asJava) val sameAsA = new ConfigDelayedMerge(fakeOrigin(), List[AbstractConfigValue](s1, s2).asJava) val b = new ConfigDelayedMerge(fakeOrigin(), List[AbstractConfigValue](s2, s1).asJava) checkEqualObjects(a, a) checkEqualObjects(a, sameAsA) checkNotEqualObjects(a, b) } @Test def configDelayedMergeNotSerializable() { val s1 = subst("foo") val s2 = subst("bar") val a = new ConfigDelayedMerge(fakeOrigin(), List[AbstractConfigValue](s1, s2).asJava) checkNotSerializable(a) } @Test def configDelayedMergeObjectEquality() { val empty = SimpleConfigObject.empty() val s1 = subst("foo") val s2 = subst("bar") val a = new ConfigDelayedMergeObject(fakeOrigin(), List[AbstractConfigValue](empty, s1, s2).asJava) val sameAsA = new ConfigDelayedMergeObject(fakeOrigin(), List[AbstractConfigValue](empty, s1, s2).asJava) val b = new ConfigDelayedMergeObject(fakeOrigin(), List[AbstractConfigValue](empty, s2, s1).asJava) checkEqualObjects(a, a) checkEqualObjects(a, sameAsA) checkNotEqualObjects(a, b) } @Test def configDelayedMergeObjectNotSerializable() { val empty = SimpleConfigObject.empty() val s1 = subst("foo") val s2 = subst("bar") val a = new ConfigDelayedMergeObject(fakeOrigin(), List[AbstractConfigValue](empty, s1, s2).asJava) checkNotSerializable(a) } @Test def valuesToString() { // just check that these don't throw, the exact output // isn't super important since it's just for debugging intValue(10).toString() longValue(11).toString() doubleValue(3.14).toString() stringValue("hi").toString() nullValue().toString() boolValue(true).toString() val emptyObj = SimpleConfigObject.empty() emptyObj.toString() (new SimpleConfigList(fakeOrigin(), Collections.emptyList[AbstractConfigValue]())).toString() subst("a").toString() substInString("b").toString() val dm = new ConfigDelayedMerge(fakeOrigin(), List[AbstractConfigValue](subst("a"), subst("b")).asJava) dm.toString() val dmo = new ConfigDelayedMergeObject(fakeOrigin(), List[AbstractConfigValue](emptyObj, subst("a"), subst("b")).asJava) dmo.toString() fakeOrigin().toString() } private def unsupported(body: => Unit) { intercept[UnsupportedOperationException] { body } } @Test def configObjectUnwraps() { val m = new SimpleConfigObject(fakeOrigin(), configMap("a" -> 1, "b" -> 2, "c" -> 3)) assertEquals(Map("a" -> 1, "b" -> 2, "c" -> 3), m.unwrapped().asScala) } @Test def configObjectImplementsMap() { val m: ConfigObject = new SimpleConfigObject(fakeOrigin(), configMap("a" -> 1, "b" -> 2, "c" -> 3)) assertEquals(intValue(1), m.get("a")) assertEquals(intValue(2), m.get("b")) assertEquals(intValue(3), m.get("c")) assertNull(m.get("d")) // get can take a non-string assertNull(m.get(new Object())) assertTrue(m.containsKey("a")) assertFalse(m.containsKey("z")) // containsKey can take a non-string assertFalse(m.containsKey(new Object())) assertTrue(m.containsValue(intValue(1))) assertFalse(m.containsValue(intValue(10))) // can take a non-ConfigValue assertFalse(m.containsValue(new Object())) assertFalse(m.isEmpty()) assertEquals(3, m.size()) val values = Set(intValue(1), intValue(2), intValue(3)) assertEquals(values, m.values().asScala.toSet) assertEquals(values, (m.entrySet().asScala map { _.getValue() }).toSet) val keys = Set("a", "b", "c") assertEquals(keys, m.keySet().asScala.toSet) assertEquals(keys, (m.entrySet().asScala map { _.getKey() }).toSet) unsupported { m.clear() } unsupported { m.put("hello", intValue(42)) } unsupported { m.putAll(Collections.emptyMap[String, AbstractConfigValue]()) } unsupported { m.remove("a") } } @Test def configListImplementsList() { val scalaSeq = Seq[AbstractConfigValue](stringValue("a"), stringValue("b"), stringValue("c")) val l: ConfigList = new SimpleConfigList(fakeOrigin(), scalaSeq.asJava) assertEquals(scalaSeq(0), l.get(0)) assertEquals(scalaSeq(1), l.get(1)) assertEquals(scalaSeq(2), l.get(2)) assertTrue(l.contains(stringValue("a"))) assertTrue(l.containsAll(List[AbstractConfigValue](stringValue("b")).asJava)) assertFalse(l.containsAll(List[AbstractConfigValue](stringValue("d")).asJava)) assertEquals(1, l.indexOf(scalaSeq(1))) assertFalse(l.isEmpty()); assertEquals(scalaSeq, l.iterator().asScala.toSeq) unsupported { l.iterator().remove() } assertEquals(1, l.lastIndexOf(scalaSeq(1))) val li = l.listIterator() var i = 0 while (li.hasNext()) { assertEquals(i > 0, li.hasPrevious()) assertEquals(i, li.nextIndex()) assertEquals(i - 1, li.previousIndex()) unsupported { li.remove() } unsupported { li.add(intValue(3)) } unsupported { li.set(stringValue("foo")) } val v = li.next() assertEquals(l.get(i), v) if (li.hasPrevious()) { // go backward assertEquals(scalaSeq(i), li.previous()) // go back forward li.next() } i += 1 } l.listIterator(1) // doesn't throw! assertEquals(3, l.size()) assertEquals(scalaSeq.tail, l.subList(1, l.size()).asScala) assertEquals(scalaSeq, l.toArray.toList) assertEquals(scalaSeq, l.toArray(new Array[ConfigValue](l.size())).toList) unsupported { l.add(intValue(3)) } unsupported { l.add(1, intValue(4)) } unsupported { l.addAll(List[ConfigValue]().asJava) } unsupported { l.addAll(1, List[ConfigValue]().asJava) } unsupported { l.clear() } unsupported { l.remove(intValue(2)) } unsupported { l.remove(1) } unsupported { l.removeAll(List[ConfigValue](intValue(1)).asJava) } unsupported { l.retainAll(List[ConfigValue](intValue(1)).asJava) } unsupported { l.set(0, intValue(42)) } } private def unresolved(body: => Unit) { intercept[ConfigException.NotResolved] { body } } @Test def notResolvedThrown() { // ConfigSubstitution unresolved { subst("foo").valueType() } unresolved { subst("foo").unwrapped() } // ConfigDelayedMerge val dm = new ConfigDelayedMerge(fakeOrigin(), List[AbstractConfigValue](subst("a"), subst("b")).asJava) unresolved { dm.valueType() } unresolved { dm.unwrapped() } // ConfigDelayedMergeObject val emptyObj = SimpleConfigObject.empty() val dmo = new ConfigDelayedMergeObject(fakeOrigin(), List[AbstractConfigValue](emptyObj, subst("a"), subst("b")).asJava) assertEquals(ConfigValueType.OBJECT, dmo.valueType()) unresolved { dmo.unwrapped() } unresolved { dmo.get("foo") } unresolved { dmo.containsKey(null) } unresolved { dmo.containsValue(null) } unresolved { dmo.entrySet() } unresolved { dmo.isEmpty() } unresolved { dmo.keySet() } unresolved { dmo.size() } unresolved { dmo.values() } unresolved { dmo.toConfig.getInt("foo") } } @Test def roundTripNumbersThroughString() { // formats rounded off with E notation val a = "132454454354353245.3254652656454808909932874873298473298472" // formats as 100000.0 val b = "1e6" // formats as 5.0E-5 val c = "0.00005" // formats as 1E100 (capital E) val d = "1e100" val obj = parseConfig("{ a : " + a + ", b : " + b + ", c : " + c + ", d : " + d + "}") assertEquals(Seq(a, b, c, d), Seq("a", "b", "c", "d") map { obj.getString(_) }) // make sure it still works if we're doing concatenation val obj2 = parseConfig("{ a : xx " + a + " yy, b : xx " + b + " yy, c : xx " + c + " yy, d : xx " + d + " yy}") assertEquals(Seq(a, b, c, d) map { "xx " + _ + " yy" }, Seq("a", "b", "c", "d") map { obj2.getString(_) }) } @Test def mergeOriginsWorks() { def o(desc: String, empty: Boolean) = { val values = new java.util.HashMap[String, AbstractConfigValue]() if (!empty) values.put("hello", intValue(37)) new SimpleConfigObject(SimpleConfigOrigin.newSimple(desc), values); } def m(values: AbstractConfigObject*) = { AbstractConfigObject.mergeOrigins(values: _*).description() } // simplest case assertEquals("merge of a,b", m(o("a", false), o("b", false))) // combine duplicate "merge of" assertEquals("merge of a,x,y", m(o("a", false), o("merge of x,y", false))) assertEquals("merge of a,b,x,y", m(o("merge of a,b", false), o("merge of x,y", false))) // ignore empty objects assertEquals("a", m(o("foo", true), o("a", false))) // unless they are all empty, pick the first one assertEquals("foo", m(o("foo", true), o("a", true))) // merge just one assertEquals("foo", m(o("foo", false))) // merge three assertEquals("merge of a,b,c", m(o("a", false), o("b", false), o("c", false))) } @Test def hasPathWorks() { val empty = parseConfig("{}") assertFalse(empty.hasPath("foo")) val obj = parseConfig("a=null, b.c.d=11, foo=bar") // returns true for the non-null values assertTrue(obj.hasPath("foo")) assertTrue(obj.hasPath("b.c.d")) assertTrue(obj.hasPath("b.c")) assertTrue(obj.hasPath("b")) // hasPath() is false for null values but containsKey is true assertEquals(nullValue(), obj.root.get("a")) assertTrue(obj.root.containsKey("a")) assertFalse(obj.hasPath("a")) // false for totally absent values assertFalse(obj.root.containsKey("notinhere")) assertFalse(obj.hasPath("notinhere")) // throws proper exceptions intercept[ConfigException.BadPath] { empty.hasPath("a.") } intercept[ConfigException.BadPath] { empty.hasPath("..") } } @Test def newNumberWorks() { def nL(v: Long) = ConfigNumber.newNumber(fakeOrigin(), v, null) def nD(v: Double) = ConfigNumber.newNumber(fakeOrigin(), v, null) // the general idea is that the destination type should depend // only on the actual numeric value, not on the type of the source // value. assertEquals(3.14, nD(3.14).unwrapped()) assertEquals(1, nL(1).unwrapped()) assertEquals(1, nD(1.0).unwrapped()) assertEquals(Int.MaxValue + 1L, nL(Int.MaxValue + 1L).unwrapped()) assertEquals(Int.MinValue - 1L, nL(Int.MinValue - 1L).unwrapped()) assertEquals(Int.MaxValue + 1L, nD(Int.MaxValue + 1.0).unwrapped()) assertEquals(Int.MinValue - 1L, nD(Int.MinValue - 1.0).unwrapped()) } @Test def automaticBooleanConversions() { val trues = parseObject("{ a=true, b=yes, c=on }").toConfig assertEquals(true, trues.getBoolean("a")) assertEquals(true, trues.getBoolean("b")) assertEquals(true, trues.getBoolean("c")) val falses = parseObject("{ a=false, b=no, c=off }").toConfig assertEquals(false, falses.getBoolean("a")) assertEquals(false, falses.getBoolean("b")) assertEquals(false, falses.getBoolean("c")) } @Test def configOriginFileAndLine() { val hasFilename = SimpleConfigOrigin.newFile("foo") val noFilename = SimpleConfigOrigin.newSimple("bar") val filenameWithLine = hasFilename.withLineNumber(3) val noFilenameWithLine = noFilename.withLineNumber(4) assertEquals("foo", hasFilename.filename()) assertEquals("foo", filenameWithLine.filename()) assertNull(noFilename.filename()) assertNull(noFilenameWithLine.filename()) assertEquals("foo", hasFilename.description()) assertEquals("bar", noFilename.description()) assertEquals(-1, hasFilename.lineNumber()) assertEquals(-1, noFilename.lineNumber()) assertEquals("foo: 3", filenameWithLine.description()) assertEquals("bar: 4", noFilenameWithLine.description()); assertEquals(3, filenameWithLine.lineNumber()) assertEquals(4, noFilenameWithLine.lineNumber()) // the filename is made absolute when converting to url assertTrue(hasFilename.url.toExternalForm.contains("foo")) assertNull(noFilename.url) val rootFile = SimpleConfigOrigin.newFile("/baz") val rootFileURL = if (isWindows) s"file:/$userDrive/baz" else "file:/baz" assertEquals(rootFileURL, rootFile.url.toExternalForm) val urlOrigin = SimpleConfigOrigin.newURL(new URL("file:/foo")) assertEquals("/foo", urlOrigin.filename) assertEquals("file:/foo", urlOrigin.url.toExternalForm) } @Test def withOnly() { val obj = parseObject("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }") assertEquals("keep only a", parseObject("{ a=1 }"), obj.withOnlyKey("a")) assertEquals("keep only e", parseObject("{ e.f.g=4 }"), obj.withOnlyKey("e")) assertEquals("keep only c.d", parseObject("{ c.d.y=3, c.d.z=5 }"), obj.toConfig.withOnlyPath("c.d").root) assertEquals("keep only c.d.z", parseObject("{ c.d.z=5 }"), obj.toConfig.withOnlyPath("c.d.z").root) assertEquals("keep nonexistent key", parseObject("{ }"), obj.withOnlyKey("nope")) assertEquals("keep nonexistent path", parseObject("{ }"), obj.toConfig.withOnlyPath("q.w.e.r.t.y").root) assertEquals("keep only nonexistent underneath non-object", parseObject("{ }"), obj.toConfig.withOnlyPath("a.nonexistent").root) assertEquals("keep only nonexistent underneath nested non-object", parseObject("{ }"), obj.toConfig.withOnlyPath("c.d.z.nonexistent").root) } @Test def withOnlyInvolvingUnresolved() { val obj = parseObject("{ a = {}, a=${x}, b=${y}, b=${z}, x={asf:1}, y=2, z=3 }") assertEquals("keep only a.asf", parseObject("{ a={asf:1} }"), obj.toConfig.resolve.withOnlyPath("a.asf").root) intercept[ConfigException.UnresolvedSubstitution] { obj.withOnlyKey("a").toConfig.resolve } intercept[ConfigException.UnresolvedSubstitution] { obj.withOnlyKey("b").toConfig.resolve } assertEquals(ResolveStatus.UNRESOLVED, obj.resolveStatus()) assertEquals(ResolveStatus.RESOLVED, obj.withOnlyKey("z").resolveStatus()) } @Test def without() { val obj = parseObject("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }") assertEquals("without a", parseObject("{ b=2, c.d.y=3, e.f.g=4, c.d.z=5 }"), obj.withoutKey("a")) assertEquals("without c", parseObject("{ a=1, b=2, e.f.g=4 }"), obj.withoutKey("c")) assertEquals("without c.d", parseObject("{ a=1, b=2, e.f.g=4, c={} }"), obj.toConfig.withoutPath("c.d").root) assertEquals("without c.d.z", parseObject("{ a=1, b=2, c.d.y=3, e.f.g=4 }"), obj.toConfig.withoutPath("c.d.z").root) assertEquals("without nonexistent key", parseObject("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }"), obj.withoutKey("nonexistent")) assertEquals("without nonexistent path", parseObject("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }"), obj.toConfig.withoutPath("q.w.e.r.t.y").root) assertEquals("without nonexistent path with existing prefix", parseObject("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }"), obj.toConfig.withoutPath("a.foo").root) } @Test def withoutInvolvingUnresolved() { val obj = parseObject("{ a = {}, a=${x}, b=${y}, b=${z}, x={asf:1}, y=2, z=3 }") assertEquals("without a.asf", parseObject("{ a={}, b=3, x={asf:1}, y=2, z=3 }"), obj.toConfig.resolve.withoutPath("a.asf").root) intercept[ConfigException.UnresolvedSubstitution] { obj.withoutKey("x").toConfig.resolve } intercept[ConfigException.UnresolvedSubstitution] { obj.withoutKey("z").toConfig.resolve } assertEquals(ResolveStatus.UNRESOLVED, obj.resolveStatus()) assertEquals(ResolveStatus.UNRESOLVED, obj.withoutKey("a").resolveStatus()) assertEquals(ResolveStatus.RESOLVED, obj.withoutKey("a").withoutKey("b").resolveStatus()) } @Test def atPathWorksOneElement() { val v = ConfigValueFactory.fromAnyRef(42) val config = v.atPath("a") assertEquals(parseConfig("a=42"), config) assertTrue(config.getValue("a") eq v) assertTrue(config.origin.description.contains("atPath")) } @Test def atPathWorksTwoElements() { val v = ConfigValueFactory.fromAnyRef(42) val config = v.atPath("a.b") assertEquals(parseConfig("a.b=42"), config) assertTrue(config.getValue("a.b") eq v) assertTrue(config.origin.description.contains("atPath")) } @Test def atPathWorksFourElements() { val v = ConfigValueFactory.fromAnyRef(42) val config = v.atPath("a.b.c.d") assertEquals(parseConfig("a.b.c.d=42"), config) assertTrue(config.getValue("a.b.c.d") eq v) assertTrue(config.origin.description.contains("atPath")) } @Test def atKeyWorks() { val v = ConfigValueFactory.fromAnyRef(42) val config = v.atKey("a") assertEquals(parseConfig("a=42"), config) assertTrue(config.getValue("a") eq v) assertTrue(config.origin.description.contains("atKey")) } @Test def withValueDepth1FromEmpty() { val v = ConfigValueFactory.fromAnyRef(42) val config = ConfigFactory.empty.withValue("a", v) assertEquals(parseConfig("a=42"), config) assertTrue(config.getValue("a") eq v) } @Test def withValueDepth2FromEmpty() { val v = ConfigValueFactory.fromAnyRef(42) val config = ConfigFactory.empty.withValue("a.b", v) assertEquals(parseConfig("a.b=42"), config) assertTrue(config.getValue("a.b") eq v) } @Test def withValueDepth3FromEmpty() { val v = ConfigValueFactory.fromAnyRef(42) val config = ConfigFactory.empty.withValue("a.b.c", v) assertEquals(parseConfig("a.b.c=42"), config) assertTrue(config.getValue("a.b.c") eq v) } @Test def withValueDepth1OverwritesExisting() { val v = ConfigValueFactory.fromAnyRef(47) val old = v.atPath("a") val config = old.withValue("a", ConfigValueFactory.fromAnyRef(42)) assertEquals(parseConfig("a=42"), config) assertEquals(42, config.getInt("a")) } @Test def withValueDepth2OverwritesExisting() { val v = ConfigValueFactory.fromAnyRef(47) val old = v.atPath("a.b") val config = old.withValue("a.b", ConfigValueFactory.fromAnyRef(42)) assertEquals(parseConfig("a.b=42"), config) assertEquals(42, config.getInt("a.b")) } @Test def withValueInsideExistingObject() { val v = ConfigValueFactory.fromAnyRef(47) val old = v.atPath("a.c") val config = old.withValue("a.b", ConfigValueFactory.fromAnyRef(42)) assertEquals(parseConfig("a.b=42,a.c=47"), config) assertEquals(42, config.getInt("a.b")) assertEquals(47, config.getInt("a.c")) } @Test def withValueBuildComplexConfig() { val v1 = ConfigValueFactory.fromAnyRef(1) val v2 = ConfigValueFactory.fromAnyRef(2) val v3 = ConfigValueFactory.fromAnyRef(3) val v4 = ConfigValueFactory.fromAnyRef(4) val config = ConfigFactory.empty .withValue("a", v1) .withValue("b.c", v2) .withValue("b.d", v3) .withValue("x.y.z", v4) assertEquals(parseConfig("a=1,b.c=2,b.d=3,x.y.z=4"), config) } @Test def configOriginsInSerialization() { import scala.collection.JavaConverters._ val bases = Seq( SimpleConfigOrigin.newSimple("foo"), SimpleConfigOrigin.newFile("/tmp/blahblah"), SimpleConfigOrigin.newURL(new URL("http://example.com")), SimpleConfigOrigin.newResource("myresource"), SimpleConfigOrigin.newResource("myresource", new URL("file://foo/bar"))) val combos = bases.flatMap({ base => Seq( (base, base.withComments(Seq("this is a comment", "another one").asJava)), (base, base.withComments(null)), (base, base.withLineNumber(41)), (base, SimpleConfigOrigin.mergeOrigins(base.withLineNumber(10), base.withLineNumber(20)))) }) ++ bases.sliding(2).map({ seq => (seq.head, seq.tail.head) }) ++ bases.sliding(3).map({ seq => (seq.head, seq.tail.tail.head) }) ++ bases.sliding(4).map({ seq => (seq.head, seq.tail.tail.tail.head) }) val withFlipped = combos ++ combos.map(_.swap) val withDuplicate = withFlipped ++ withFlipped.map(p => (p._1, p._1)) val values = withDuplicate.flatMap({ combo => Seq( // second inside first new SimpleConfigList(combo._1, Seq[AbstractConfigValue](new ConfigInt(combo._2, 42, "42")).asJava), // triple-nested means we have to null then un-null then null, which is a tricky case // in the origin-serialization code. new SimpleConfigList(combo._1, Seq[AbstractConfigValue](new SimpleConfigList(combo._2, Seq[AbstractConfigValue](new ConfigInt(combo._1, 42, "42")).asJava)).asJava)) }) def top(v: SimpleConfigList) = v.origin def middle(v: SimpleConfigList) = v.get(0).origin def bottom(v: SimpleConfigList) = if (v.get(0).isInstanceOf[ConfigList]) Some(v.get(0).asInstanceOf[ConfigList].get(0).origin) else None //System.err.println("values=\n " + values.map(v => top(v).description + ", " + middle(v).description + ", " + bottom(v).map(_.description)).mkString("\n ")) for (v <- values) { val deserialized = checkSerializable(v) // double-check that checkSerializable verified the origins assertEquals(top(v), top(deserialized)) assertEquals(middle(v), middle(deserialized)) assertEquals(bottom(v), bottom(deserialized)) } } @Test def renderWithNewlinesInDescription(): Unit = { val v = ConfigValueFactory.fromAnyRef(89, "this is a description\nwith some\nnewlines") val list = new SimpleConfigList(SimpleConfigOrigin.newSimple("\n5\n6\n7\n"), java.util.Collections.singletonList(v.asInstanceOf[AbstractConfigValue])) val conf = ConfigFactory.empty().withValue("bar", list) val rendered = conf.root.render() def assertHas(s: String): Unit = assertTrue(s"has ${s.replace("\n", "\\n")} in it", rendered.contains(s)) assertHas("is a description\n") assertHas("with some\n") assertHas("newlines\n") assertHas("#\n") assertHas("5\n") assertHas("6\n") assertHas("7\n") val parsed = ConfigFactory.parseString(rendered) assertEquals(conf, parsed) } @Test def renderSorting(): Unit = { val config = parseConfig("""0=a,1=b,2=c,3=d,10=e,20=f,30=g""") val rendered = config.root.render(ConfigRenderOptions.concise()) assertEquals("""{"0":"a","1":"b","2":"c","3":"d","10":"e","20":"f","30":"g"}""", rendered) } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/EquivalentsTest.scala000066400000000000000000000074311277147274600300560ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ import net.liftweb.{ json => lift } import java.io.Reader import java.io.StringReader import com.typesafe.config._ import java.util.HashMap import java.io.File import org.junit.runner.RunWith import org.junit.runners.AllTests class EquivalentsTest extends TestUtils { private def equivDirs() = { val rawEquivs = resourceDir.listFiles() val equivs = rawEquivs.filter({ f => f.getName().startsWith("equiv") }) equivs } private def filesForEquiv(equiv: File) = { val rawFiles = equiv.listFiles() val files = rawFiles.filter({ f => f.getName().endsWith(".json") || f.getName().endsWith(".conf") }) files } private def postParse(value: ConfigValue) = { value match { case v: AbstractConfigObject => // for purposes of these tests, substitutions are only // against the same file's root, and without looking at // system prop or env variable fallbacks. ResolveContext.resolve(v, v, ConfigResolveOptions.noSystem()) case v => v } } private def parse(flavor: ConfigSyntax, f: File) = { val options = ConfigParseOptions.defaults().setSyntax(flavor) postParse(ConfigFactory.parseFile(f, options).root) } private def parse(f: File) = { val options = ConfigParseOptions.defaults() postParse(ConfigFactory.parseFile(f, options).root) } // would like each "equivNN" directory to be a suite and each file in the dir // to be a test, but not sure how to convince junit to do that. @Test def testEquivalents() { var dirCount = 0 var fileCount = 0 for (equiv <- equivDirs()) { dirCount += 1 val files = filesForEquiv(equiv) val (originals, others) = files.partition({ f => f.getName().startsWith("original.") }) if (originals.isEmpty) throw new RuntimeException("Need a file named 'original' in " + equiv.getPath()) if (originals.size > 1) throw new RuntimeException("Multiple 'original' files in " + equiv.getPath() + ": " + originals) val original = parse(originals(0)) for (testFile <- others) { fileCount += 1 val value = parse(testFile) describeFailure(testFile.getPath()) { try { assertEquals(original, value) } catch { case e: Throwable => showDiff(original, value) throw e } } // check that all .json files can be parsed as .conf, // i.e. .conf must be a superset of JSON if (testFile.getName().endsWith(".json")) { val parsedAsConf = parse(ConfigSyntax.CONF, testFile) describeFailure(testFile.getPath() + " parsed as .conf") { try { assertEquals(original, parsedAsConf) } catch { case e: Throwable => showDiff(original, parsedAsConf) throw e } } } } } // This is a little "checksum" to be sure we really tested what we were expecting. // it breaks every time you add a file, so you have to update it. assertEquals(5, dirCount) // this is the number of files not named original.* assertEquals(15, fileCount) } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/HttpTest.scala000066400000000000000000000120041277147274600264650ustar00rootroot00000000000000package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ import org.junit.BeforeClass import java.net.URL import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigSyntax import com.typesafe.config.ConfigException class HttpTest extends TestUtils { import HttpTest._ private def foreachSyntax(body: Option[ConfigSyntax] => Unit): Unit = { for (syntax <- Seq(Some(ConfigSyntax.JSON), Some(ConfigSyntax.CONF), Some(ConfigSyntax.PROPERTIES), None)) body(syntax) } private def foreachSyntaxOptions(body: ConfigParseOptions => Unit): Unit = foreachSyntax { syntaxOption => val options = syntaxOption map { syntax => ConfigParseOptions.defaults.setSyntax(syntax) } getOrElse { ConfigParseOptions.defaults() } body(options) } def url(path: String): URL = new URL(s"$baseUrl/$path") @Test def parseEmpty(): Unit = { foreachSyntaxOptions { options => val conf = ConfigFactory.parseURL(url("empty"), options) assertTrue("empty conf was parsed", conf.root.isEmpty) } } @Test def parseFooIs42(): Unit = { foreachSyntaxOptions { options => val conf = ConfigFactory.parseURL(url("foo"), options) assertEquals(42, conf.getInt("foo")) } } @Test def notFoundThrowsIO(): Unit = { val e = intercept[ConfigException.IO] { ConfigFactory.parseURL(url("notfound"), ConfigParseOptions.defaults().setAllowMissing(false)) } assertTrue(s"expected different exception for notfound, got $e", e.getMessage.contains("/notfound")) } @Test def internalErrorThrowsBroken(): Unit = { val e = intercept[ConfigException.BugOrBroken] { ConfigFactory.parseURL(url("error"), ConfigParseOptions.defaults().setAllowMissing(false)) } assertTrue(s"expected different exception for error url, got $e", e.getMessage.contains("/error")) } @Test def notFoundDoesNotThrowIfAllowingMissing(): Unit = { val conf = ConfigFactory.parseURL(url("notfound"), ConfigParseOptions.defaults().setAllowMissing(true)) assertEquals(0, conf.root.size) } @Test def internalErrorThrowsEvenIfAllowingMissing(): Unit = { val e = intercept[ConfigException.BugOrBroken] { ConfigFactory.parseURL(url("error"), ConfigParseOptions.defaults().setAllowMissing(true)) } assertTrue(s"expected different exception for error url when allowing missing, got $e", e.getMessage.contains("/error")) } @Test def relativeInclude(): Unit = { val conf = ConfigFactory.parseURL(url("includes-a-friend")) assertEquals(42, conf.getInt("foo")) assertEquals(43, conf.getInt("bar")) } } object HttpTest { import ToyHttp.{ Request, Response } final val jsonContentType = "application/json" final val propertiesContentType = "text/x-java-properties" final val hoconContentType = "application/hocon" private var server: Option[ToyHttp] = None def port = server.map(_.port).getOrElse(throw new Exception("http server isn't running")) def baseUrl = s"http://127.0.0.1:$port" private def handleThreeTypes(request: Request, json: String, props: String, hocon: String): Response = { request.headers.get("accept") match { case Some(`jsonContentType`) | None => Response(200, jsonContentType, json) case Some(`propertiesContentType`) => Response(200, propertiesContentType, props) case Some(`hoconContentType`) => Response(200, hoconContentType, hocon) case Some(other) => Response(500, "text/plain", s"bad content type '$other'") } } private def handleRequest(request: Request): Response = { request.path match { case "/empty" => handleThreeTypes(request, "{}", "", "") case "/foo" | "/foo.conf" => handleThreeTypes(request, "{ \"foo\" : 42 }", "foo:42", "foo=42") case "/notfound" => Response(404, "text/plain", "This is never found") case "/error" => Response(500, "text/plain", "This is always an error") case "/includes-a-friend" => // currently, a suffix-less include like this will cause // us to search for foo.conf, foo.json, foo.properties, but // not load plain foo. Response(200, hoconContentType, """ include "foo" include "foo/bar" """) case "/foo/bar.conf" => Response(200, hoconContentType, "{ bar = 43 }") case path => Response(404, "text/plain", s"Never heard of '$path'") } } @BeforeClass def startServer(): Unit = { server = Some(ToyHttp(handleRequest)) } @AfterClass def stopServer(): Unit = { server.foreach(_.stop()) } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/JsonTest.scala000066400000000000000000000161261277147274600264700ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ import net.liftweb.{ json => lift } import java.io.Reader import java.io.StringReader import com.typesafe.config._ import java.util.HashMap import java.util.Collections class JsonTest extends TestUtils { def parse(s: String): ConfigValue = { val options = ConfigParseOptions.defaults(). setOriginDescription("test json string"). setSyntax(ConfigSyntax.JSON); Parseable.newString(s, options).parseValue(); } def parseAsConf(s: String): ConfigValue = { val options = ConfigParseOptions.defaults(). setOriginDescription("test conf string"). setSyntax(ConfigSyntax.CONF); Parseable.newString(s, options).parseValue(); } private[this] def toLift(value: ConfigValue): lift.JValue = { import scala.collection.JavaConverters._ value match { case v: ConfigObject => lift.JObject(v.keySet().asScala.map({ k => lift.JField(k, toLift(v.get(k))) }).toList) case v: ConfigList => lift.JArray(v.asScala.toList.map({ elem => toLift(elem) })) case v: ConfigBoolean => lift.JBool(v.unwrapped()) case v: ConfigInt => lift.JInt(BigInt(v.unwrapped())) case v: ConfigLong => lift.JInt(BigInt(v.unwrapped())) case v: ConfigDouble => lift.JDouble(v.unwrapped()) case v: ConfigString => lift.JString(v.unwrapped()) case v: ConfigNull => lift.JNull } } private[this] def fromLift(liftValue: lift.JValue): AbstractConfigValue = { import scala.collection.JavaConverters._ liftValue match { case lift.JObject(fields) => val m = new HashMap[String, AbstractConfigValue]() fields.foreach({ field => m.put(field.name, fromLift(field.value)) }) new SimpleConfigObject(fakeOrigin(), m) case lift.JArray(values) => new SimpleConfigList(fakeOrigin(), values.map(fromLift(_)).asJava) case lift.JField(name, value) => throw new IllegalStateException("either JField was a toplevel from lift-json or this function is buggy") case lift.JInt(i) => if (i.isValidInt) intValue(i.intValue) else longValue(i.longValue) case lift.JBool(b) => new ConfigBoolean(fakeOrigin(), b) case lift.JDouble(d) => doubleValue(d) case lift.JString(s) => new ConfigString.Quoted(fakeOrigin(), s) case lift.JNull => new ConfigNull(fakeOrigin()) case lift.JNothing => throw new ConfigException.BugOrBroken("Lift returned JNothing, probably an empty document (?)") } } private def withLiftExceptionsConverted[T](block: => T): T = { try { block } catch { case e: lift.JsonParser.ParseException => throw new ConfigException.Parse(SimpleConfigOrigin.newSimple("lift parser"), e.getMessage(), e) } } // parse a string using Lift's AST. We then test by ensuring we have the same results as // lift for a variety of JSON strings. private def fromJsonWithLiftParser(json: String): ConfigValue = { withLiftExceptionsConverted(fromLift(lift.JsonParser.parse(json))) } private def fromJsonWithLiftParser(json: Reader): ConfigValue = { withLiftExceptionsConverted(fromLift(lift.JsonParser.parse(json))) } // For string quoting, check behavior of escaping a random character instead of one on the list; // lift-json seems to oddly treat that as a \ literal @Test def invalidJsonThrows(): Unit = { var tested = 0 // be sure Lift throws on the string for (invalid <- whitespaceVariations(invalidJson, false)) { if (invalid.liftBehaviorUnexpected) { // lift unexpectedly doesn't throw, confirm that addOffendingJsonToException("lift-nonthrowing", invalid.test) { fromJsonWithLiftParser(invalid.test) fromJsonWithLiftParser(new java.io.StringReader(invalid.test)) } } else { addOffendingJsonToException("lift", invalid.test) { intercept[ConfigException] { fromJsonWithLiftParser(invalid.test) } intercept[ConfigException] { fromJsonWithLiftParser(new java.io.StringReader(invalid.test)) } tested += 1 } } } assertTrue(tested > 100) // just checking we ran a bunch of tests tested = 0 // be sure we also throw for (invalid <- whitespaceVariations(invalidJson, false)) { addOffendingJsonToException("config", invalid.test) { intercept[ConfigException] { parse(invalid.test) } tested += 1 } } assertTrue(tested > 100) } @Test def validJsonWorks(): Unit = { var tested = 0 // be sure we do the same thing as Lift when we build our JSON "DOM" for (valid <- whitespaceVariations(validJson, true)) { val liftAST = if (valid.liftBehaviorUnexpected) { SimpleConfigObject.empty() } else { addOffendingJsonToException("lift", valid.test) { fromJsonWithLiftParser(valid.test) } } val ourAST = addOffendingJsonToException("config-json", valid.test) { parse(valid.test) } val ourConfAST = addOffendingJsonToException("config-conf", valid.test) { parseAsConf(valid.test) } if (valid.liftBehaviorUnexpected) { // ignore this for now } else { addOffendingJsonToException("config", valid.test) { assertEquals(liftAST, ourAST) } } // check that our parser gives the same result in JSON mode and ".conf" mode. // i.e. this tests that ".conf" format is a superset of JSON. addOffendingJsonToException("config", valid.test) { assertEquals(ourAST, ourConfAST) } tested += 1 } assertTrue(tested > 100) // just verify we ran a lot of tests } @Test def renderingJsonStrings() { def r(s: String) = ConfigImplUtil.renderJsonString(s) assertEquals(""""abcdefg"""", r("""abcdefg""")) assertEquals(""""\" \\ \n \b \f \r \t"""", r("\" \\ \n \b \f \r \t")) // control characters are escaped. Remember that unicode escapes // are weird and happen on the source file before doing other processing. assertEquals("\"\\" + "u001f\"", r("\u001f")) } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/PathTest.scala000066400000000000000000000106211277147274600264450ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ import scala.collection.JavaConverters._ import com.typesafe.config.ConfigException class PathTest extends TestUtils { @Test def pathEquality() { // note: foo.bar is a single key here val a = Path.newKey("foo.bar") // check that newKey worked assertEquals(path("foo.bar"), a); val sameAsA = Path.newKey("foo.bar") val differentKey = Path.newKey("hello") // here foo.bar is two elements val twoElements = Path.newPath("foo.bar") // check that newPath worked assertEquals(path("foo", "bar"), twoElements); val sameAsTwoElements = Path.newPath("foo.bar") checkEqualObjects(a, a) checkEqualObjects(a, sameAsA) checkNotEqualObjects(a, differentKey) checkNotEqualObjects(a, twoElements) checkEqualObjects(twoElements, sameAsTwoElements) } @Test def pathToString() { assertEquals("Path(foo)", path("foo").toString()) assertEquals("Path(foo.bar)", path("foo", "bar").toString()) assertEquals("Path(foo.\"bar*\")", path("foo", "bar*").toString()) assertEquals("Path(\"foo.bar\")", path("foo.bar").toString()) } @Test def pathRender() { case class RenderTest(expected: String, path: Path) val tests = Seq( // simple one-element case RenderTest("foo", path("foo")), // simple two-element case RenderTest("foo.bar", path("foo", "bar")), // non-safe-char in an element RenderTest("foo.\"bar*\"", path("foo", "bar*")), // period in an element RenderTest("\"foo.bar\"", path("foo.bar")), // hyphen and underscore RenderTest("foo-bar", path("foo-bar")), RenderTest("foo_bar", path("foo_bar")), // starts with hyphen RenderTest("-foo", path("-foo")), // starts with number RenderTest("10foo", path("10foo")), // empty elements RenderTest("\"\".\"\"", path("", "")), // internal space RenderTest("\"foo bar\"", path("foo bar")), // leading and trailing spaces RenderTest("\" foo \"", path(" foo ")), // trailing space only RenderTest("\"foo \"", path("foo ")), // numbers with decimal points RenderTest("1.2", path("1", "2")), RenderTest("1.2.3.4", path("1", "2", "3", "4"))) for (t <- tests) { assertEquals(t.expected, t.path.render()) assertEquals(t.path, PathParser.parsePath(t.expected)) assertEquals(t.path, PathParser.parsePath(t.path.render())) } } @Test def pathFromPathList() { assertEquals(path("foo"), new Path(List(path("foo")).asJava)) assertEquals(path("foo", "bar", "baz", "boo"), new Path(List(path("foo", "bar"), path("baz", "boo")).asJava)) } @Test def pathPrepend() { assertEquals(path("foo", "bar"), path("bar").prepend(path("foo"))) assertEquals(path("a", "b", "c", "d"), path("c", "d").prepend(path("a", "b"))) } @Test def pathLength() { assertEquals(1, path("foo").length()) assertEquals(2, path("foo", "bar").length()) } @Test def pathParent() { assertNull(path("a").parent()) assertEquals(path("a"), path("a", "b").parent()) assertEquals(path("a", "b"), path("a", "b", "c").parent()) } @Test def pathLast() { assertEquals("a", path("a").last()) assertEquals("b", path("a", "b").last()) } @Test def pathStartsWith() { assertTrue(path("a", "b", "c", "d").startsWith(path("a", "b"))) assertTrue(path("a", "b", "c", "d").startsWith(path("a", "b", "c", "d"))) assertFalse(path("a", "b", "c", "d").startsWith(path("b", "c", "d"))) assertFalse(path("a", "b", "c", "d").startsWith(path("invalidpath"))) } @Test def pathsAreInvalid() { // this test is just of the Path.newPath() wrapper, the extensive // test of different paths is over in ConfParserTest intercept[ConfigException.BadPath] { Path.newPath("") } intercept[ConfigException.BadPath] { Path.newPath("..") } } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/PropertiesTest.scala000066400000000000000000000140071277147274600277070ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ import java.util.{ Date, Properties } import com.typesafe.config.Config import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigException import com.typesafe.config.ConfigResolveOptions class PropertiesTest extends TestUtils { @Test def pathSplitting() { def last(s: String) = PropertiesParser.lastElement(s) def exceptLast(s: String) = PropertiesParser.exceptLastElement(s) assertEquals("a", last("a")) assertNull(exceptLast("a")) assertEquals("b", last("a.b")) assertEquals("a", exceptLast("a.b")) assertEquals("c", last("a.b.c")) assertEquals("a.b", exceptLast("a.b.c")) assertEquals("", last("")) assertNull(null, exceptLast("")) assertEquals("", last(".")) assertEquals("", exceptLast(".")) assertEquals("", last("..")) assertEquals(".", exceptLast("..")) assertEquals("", last("...")) assertEquals("..", exceptLast("...")) } @Test def pathObjectCreating() { def p(key: String) = PropertiesParser.pathFromPropertyKey(key) assertEquals(path("a"), p("a")) assertEquals(path("a", "b"), p("a.b")) assertEquals(path(""), p("")) } @Test def funkyPathsInProperties() { def testPath(propsPath: String, confPath: String) { val props = new Properties() props.setProperty(propsPath, propsPath) val conf = ConfigFactory.parseProperties(props, ConfigParseOptions.defaults()) assertEquals(propsPath, conf.getString(confPath)) } // the easy ones testPath("x", "x") testPath("y.z", "y.z") testPath("q.r.s", "q.r.s") // weird empty path element stuff testPath("", "\"\"") testPath(".", "\"\".\"\"") testPath("..", "\"\".\"\".\"\"") testPath("a.", "a.\"\"") testPath(".b", "\"\".b") // quotes in .properties testPath("\"", "\"\\\"\"") } @Test def objectsWinOverStrings() { val props = new Properties() props.setProperty("a.b", "foo") props.setProperty("a", "bar") props.setProperty("x", "baz") props.setProperty("x.y", "bar") props.setProperty("x.y.z", "foo") val conf = ConfigFactory.parseProperties(props, ConfigParseOptions.defaults()) assertEquals(2, conf.root.size()) assertEquals("foo", conf.getString("a.b")) assertEquals("foo", conf.getString("x.y.z")) } @Test def makeListWithNumericKeys() { import scala.collection.JavaConverters._ val props = new Properties() props.setProperty("a.0", "0") props.setProperty("a.1", "1") props.setProperty("a.2", "2") props.setProperty("a.3", "3") props.setProperty("a.4", "4") val conf = ConfigFactory.parseProperties(props, ConfigParseOptions.defaults()) val reference = ConfigFactory.parseString("{ a : [0,1,2,3,4] }") assertEquals(Seq(0, 1, 2, 3, 4), conf.getIntList("a").asScala.toSeq) conf.checkValid(reference) } @Test def makeListWithNumericKeysWithGaps() { import scala.collection.JavaConverters._ val props = new Properties() props.setProperty("a.1", "0") props.setProperty("a.2", "1") props.setProperty("a.4", "2") val conf = ConfigFactory.parseProperties(props, ConfigParseOptions.defaults()) val reference = ConfigFactory.parseString("{ a : [0,1,2] }") assertEquals(Seq(0, 1, 2), conf.getIntList("a").asScala.toSeq) conf.checkValid(reference) } @Test def makeListWithNumericKeysWithNoise() { import scala.collection.JavaConverters._ val props = new Properties() props.setProperty("a.-1", "-1") props.setProperty("a.foo", "-2") props.setProperty("a.0", "0") props.setProperty("a.1", "1") props.setProperty("a.2", "2") props.setProperty("a.3", "3") props.setProperty("a.4", "4") val conf = ConfigFactory.parseProperties(props, ConfigParseOptions.defaults()) val reference = ConfigFactory.parseString("{ a : [0,1,2,3,4] }") assertEquals(Seq(0, 1, 2, 3, 4), conf.getIntList("a").asScala.toSeq) conf.checkValid(reference) } @Test def noNumericKeysAsListFails() { import scala.collection.JavaConverters._ val props = new Properties() props.setProperty("a.bar", "0") val conf = ConfigFactory.parseProperties(props, ConfigParseOptions.defaults()) val e = intercept[ConfigException.WrongType] { conf.getList("a") } assertTrue("expected exception thrown", e.getMessage.contains("LIST")) } @Test def makeListWithNumericKeysAndMerge() { import scala.collection.JavaConverters._ val props = new Properties() props.setProperty("a.0", "0") props.setProperty("a.1", "1") props.setProperty("a.2", "2") val conf1 = ConfigFactory.parseProperties(props, ConfigParseOptions.defaults()) assertEquals(Seq(0, 1, 2), conf1.getIntList("a").asScala.toSeq) val conf2 = ConfigFactory.parseString(""" a += 3 a += 4 a = ${a} [ 5, 6 ] a = [-2, -1] ${a} """) val conf = conf2.withFallback(conf1).resolve() val reference = ConfigFactory.parseString("{ a : [-2,-1,0,1,2,3,4,5,6] }") assertEquals(Seq(-2, -1, 0, 1, 2, 3, 4, 5, 6), conf.getIntList("a").asScala.toSeq) conf.checkValid(reference) } @Test def skipNonStringsInProperties() { val props = new Properties() props.put("a", new ThreadLocal[String]()) props.put("b", new Date()) val conf = ConfigFactory.parseProperties(props) assertEquals(0, conf.root().size()) } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala000066400000000000000000001404241277147274600274260ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ import scala.collection.JavaConverters._ import com.typesafe.config._ import java.util.Collections import java.util.TreeSet import java.io.File import scala.collection.mutable import equiv03.SomethingInEquiv03 import java.io.StringReader import java.net.URL import java.time.Duration class PublicApiTest extends TestUtils { @Test def basicLoadAndGet() { val conf = ConfigFactory.load("test01") val a = conf.getInt("ints.fortyTwo") val child = conf.getConfig("ints") val c = child.getInt("fortyTwo") val ms = conf.getMilliseconds("durations.halfSecond") // should have used system variables if (System.getenv("HOME") != null) assertEquals(System.getenv("HOME"), conf.getString("system.home")) assertEquals(System.getProperty("java.version"), conf.getString("system.javaversion")) } @Test def noSystemVariables() { // should not have used system variables val conf = ConfigFactory.parseResourcesAnySyntax(classOf[PublicApiTest], "/test01") .resolve(ConfigResolveOptions.noSystem()) intercept[ConfigException.Missing] { conf.getString("system.home") } intercept[ConfigException.Missing] { conf.getString("system.javaversion") } } @Test def canLimitLoadToJson { val options = ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON); val conf = ConfigFactory.load("test01", options, ConfigResolveOptions.defaults()) assertEquals(1, conf.getInt("fromJson1")) intercept[ConfigException.Missing] { conf.getInt("ints.fortyTwo") } } @Test def canLimitLoadToProperties { val options = ConfigParseOptions.defaults().setSyntax(ConfigSyntax.PROPERTIES); val conf = ConfigFactory.load("test01", options, ConfigResolveOptions.defaults()) assertEquals(1, conf.getInt("fromProps.one")) intercept[ConfigException.Missing] { conf.getInt("ints.fortyTwo") } } @Test def canLimitLoadToConf { val options = ConfigParseOptions.defaults().setSyntax(ConfigSyntax.CONF); val conf = ConfigFactory.load("test01", options, ConfigResolveOptions.defaults()) assertEquals(42, conf.getInt("ints.fortyTwo")) intercept[ConfigException.Missing] { conf.getInt("fromJson1") } intercept[ConfigException.Missing] { conf.getInt("fromProps.one") } } @Test def emptyConfigs() { assertTrue(ConfigFactory.empty().isEmpty()) assertEquals("empty config", ConfigFactory.empty().origin().description()) assertTrue(ConfigFactory.empty("foo").isEmpty()) assertEquals("foo", ConfigFactory.empty("foo").origin().description()) } private val defaultValueDesc = "hardcoded value"; private def testFromValue(expectedValue: ConfigValue, createFrom: AnyRef) { assertEquals(expectedValue, ConfigValueFactory.fromAnyRef(createFrom)) assertEquals(expectedValue, ConfigValueFactory.fromAnyRef(createFrom, "foo")) // description is ignored for createFrom that is already a ConfigValue createFrom match { case c: ConfigValue => assertEquals(c.origin().description(), ConfigValueFactory.fromAnyRef(createFrom).origin().description()) case _ => assertEquals(defaultValueDesc, ConfigValueFactory.fromAnyRef(createFrom).origin().description()) assertEquals("foo", ConfigValueFactory.fromAnyRef(createFrom, "foo").origin().description()) } } @Test def fromJavaBoolean() { testFromValue(boolValue(true), true: java.lang.Boolean) testFromValue(boolValue(false), false: java.lang.Boolean) } @Test def fromJavaNull() { testFromValue(nullValue, null); } @Test def fromJavaNumbers() { testFromValue(intValue(5), 5: java.lang.Integer) testFromValue(longValue(6), 6: java.lang.Long) testFromValue(doubleValue(3.14), 3.14: java.lang.Double) class WeirdNumber(v: Double) extends java.lang.Number { override def doubleValue = v override def intValue = v.intValue override def longValue = v.longValue override def floatValue = v.floatValue } val weirdNumber = new WeirdNumber(5.1); testFromValue(doubleValue(5.1), weirdNumber) } @Test def fromJavaString() { testFromValue(stringValue("hello world"), "hello world") } @Test def fromJavaMap() { val emptyMapValue = Collections.emptyMap[String, AbstractConfigValue] val aMapValue = Map("a" -> 1, "b" -> 2, "c" -> 3).mapValues(intValue(_): AbstractConfigValue).asJava testFromValue(new SimpleConfigObject(fakeOrigin(), emptyMapValue), Collections.emptyMap[String, Int]) testFromValue(new SimpleConfigObject(fakeOrigin(), aMapValue), Map("a" -> 1, "b" -> 2, "c" -> 3).asJava) assertEquals("hardcoded value", ConfigValueFactory.fromMap(Map("a" -> 1, "b" -> 2, "c" -> 3).asJava).origin().description()) assertEquals("foo", ConfigValueFactory.fromMap(Map("a" -> 1, "b" -> 2, "c" -> 3).asJava, "foo").origin().description()) } @Test def fromJavaCollection() { val emptyListValue = Collections.emptyList[AbstractConfigValue] val aListValue = List(1, 2, 3).map(intValue(_): AbstractConfigValue).asJava testFromValue(new SimpleConfigList(fakeOrigin(), emptyListValue), Nil.asJava) testFromValue(new SimpleConfigList(fakeOrigin(), aListValue), List(1, 2, 3).asJava) // test with a non-List (but has to be ordered) val treeSet = new TreeSet[Int](); treeSet.add(1) treeSet.add(2) treeSet.add(3) testFromValue(new SimpleConfigList(fakeOrigin(), emptyListValue), Set.empty[String].asJava) testFromValue(new SimpleConfigList(fakeOrigin(), aListValue), treeSet) // testFromValue doesn't test the fromIterable public wrapper around fromAnyRef, // do so here. assertEquals(new SimpleConfigList(fakeOrigin(), aListValue), ConfigValueFactory.fromIterable(List(1, 2, 3).asJava)) assertEquals(new SimpleConfigList(fakeOrigin(), aListValue), ConfigValueFactory.fromIterable(treeSet)) assertEquals("hardcoded value", ConfigValueFactory.fromIterable(List(1, 2, 3).asJava).origin().description()) assertEquals("foo", ConfigValueFactory.fromIterable(treeSet, "foo").origin().description()) } @Test def fromConfigMemorySize() { testFromValue(longValue(1024), ConfigMemorySize.ofBytes(1024)); testFromValue(longValue(512), ConfigMemorySize.ofBytes(512)); } @Test def fromDuration() { testFromValue(longValue(1000), Duration.ofMillis(1000)); testFromValue(longValue(1000 * 60 * 60 * 24), Duration.ofDays(1)); } @Test def fromExistingConfigValue() { testFromValue(longValue(1000), longValue(1000)); testFromValue(stringValue("foo"), stringValue("foo")); val aMapValue = new SimpleConfigObject(fakeOrigin(), Map("a" -> 1, "b" -> 2, "c" -> 3).mapValues(intValue(_): AbstractConfigValue).asJava) testFromValue(aMapValue, aMapValue) } @Test def fromExistingJavaListOfConfigValue() { // you can mix "unwrapped" List with ConfigValue elements val list = List(longValue(1), longValue(2), longValue(3)).asJava testFromValue(new SimpleConfigList(fakeOrigin(), List(longValue(1): AbstractConfigValue, longValue(2): AbstractConfigValue, longValue(3): AbstractConfigValue).asJava), list); } @Test def roundTripUnwrap() { val conf = ConfigFactory.load("test01") assertTrue(conf.root.size > 4) // "has a lot of stuff in it" val unwrapped = conf.root.unwrapped() val rewrapped = ConfigValueFactory.fromMap(unwrapped, conf.origin().description()) val reunwrapped = rewrapped.unwrapped() assertEquals(conf.root, rewrapped) assertEquals(reunwrapped, unwrapped) } private def testFromPathMap(expectedValue: ConfigObject, createFrom: java.util.Map[String, Object]) { assertEquals(expectedValue, ConfigFactory.parseMap(createFrom).root) assertEquals(defaultValueDesc, ConfigFactory.parseMap(createFrom).origin().description()) assertEquals(expectedValue, ConfigFactory.parseMap(createFrom, "foo").root) assertEquals("foo", ConfigFactory.parseMap(createFrom, "foo").origin().description()) } @Test def fromJavaPathMap() { // first the same tests as with fromMap, but use parseMap val emptyMapValue = Collections.emptyMap[String, AbstractConfigValue] val aMapValue = Map("a" -> 1, "b" -> 2, "c" -> 3).mapValues(intValue(_): AbstractConfigValue).asJava testFromPathMap(new SimpleConfigObject(fakeOrigin(), emptyMapValue), Collections.emptyMap[String, Object]) testFromPathMap(new SimpleConfigObject(fakeOrigin(), aMapValue), Map("a" -> 1, "b" -> 2, "c" -> 3).asInstanceOf[Map[String, AnyRef]].asJava) assertEquals("hardcoded value", ConfigFactory.parseMap(Map("a" -> 1, "b" -> 2, "c" -> 3).asJava).origin().description()) assertEquals("foo", ConfigFactory.parseMap(Map("a" -> 1, "b" -> 2, "c" -> 3).asJava, "foo").origin().description()) // now some tests with paths; be sure to test nested path maps val simplePathMapValue = Map("x.y" -> 4, "z" -> 5).asInstanceOf[Map[String, AnyRef]].asJava val pathMapValue = Map("a.c" -> 1, "b" -> simplePathMapValue).asInstanceOf[Map[String, AnyRef]].asJava val conf = ConfigFactory.parseMap(pathMapValue) assertEquals(2, conf.root.size) assertEquals(4, conf.getInt("b.x.y")) assertEquals(5, conf.getInt("b.z")) assertEquals(1, conf.getInt("a.c")) } @Test def brokenPathMap() { // "a" is both number 1 and an object val pathMapValue = Map("a" -> 1, "a.b" -> 2).asInstanceOf[Map[String, AnyRef]].asJava intercept[ConfigException.BugOrBroken] { ConfigFactory.parseMap(pathMapValue) } } @Test def defaultParseOptions() { val d = ConfigParseOptions.defaults() assertEquals(true, d.getAllowMissing()) assertNull(d.getIncluder()) assertNull(d.getOriginDescription()) assertNull(d.getSyntax()) } private def assertNotFound(e: ConfigException) { assertTrue("Message text: " + e.getMessage, e.getMessage.contains("No such") || e.getMessage.contains("not found") || e.getMessage.contains("were found") || e.getMessage.contains("java.io.FileNotFoundException")) } @Test def allowMissing() { val e = intercept[ConfigException.IO] { ConfigFactory.parseFile(resourceFile("nonexistent.conf"), ConfigParseOptions.defaults().setAllowMissing(false)) } assertNotFound(e) val conf = ConfigFactory.parseFile(resourceFile("nonexistent.conf"), ConfigParseOptions.defaults().setAllowMissing(true)) assertTrue("is empty", conf.isEmpty()) } @Test def allowMissingFileAnySyntax() { val e = intercept[ConfigException.IO] { ConfigFactory.parseFileAnySyntax(resourceFile("nonexistent"), ConfigParseOptions.defaults().setAllowMissing(false)) } assertNotFound(e) val conf = ConfigFactory.parseFileAnySyntax(resourceFile("nonexistent"), ConfigParseOptions.defaults().setAllowMissing(true)) assertTrue("is empty", conf.isEmpty()) } @Test def allowMissingResourcesAnySyntax() { val e = intercept[ConfigException.IO] { ConfigFactory.parseResourcesAnySyntax(classOf[PublicApiTest], "nonexistent", ConfigParseOptions.defaults().setAllowMissing(false)) } assertNotFound(e) val conf = ConfigFactory.parseResourcesAnySyntax(classOf[PublicApiTest], "nonexistent", ConfigParseOptions.defaults().setAllowMissing(true)) assertTrue("is empty", conf.isEmpty()) } @Test def includesCanBeMissingThoughFileCannot() { // test03.conf contains some nonexistent includes. check that // setAllowMissing on the file (which is not missing) doesn't // change that the includes are allowed to be missing. // This can break because some options might "propagate" through // to includes, but we don't want them all to do so. val conf = ConfigFactory.parseFile(resourceFile("test03.conf"), ConfigParseOptions.defaults().setAllowMissing(false)) assertEquals(42, conf.getInt("test01.booleans")) val conf2 = ConfigFactory.parseFile(resourceFile("test03.conf"), ConfigParseOptions.defaults().setAllowMissing(true)) assertEquals(conf, conf2) } sealed trait IncludeKind case object IncludeKindHeuristic extends IncludeKind; case object IncludeKindFile extends IncludeKind; case object IncludeKindURL extends IncludeKind; case object IncludeKindClasspath extends IncludeKind; case class Included(name: String, fallback: ConfigIncluder, kind: IncludeKind) class RecordingIncluder(val fallback: ConfigIncluder, val included: mutable.ListBuffer[Included]) extends ConfigIncluder { override def include(context: ConfigIncludeContext, name: String): ConfigObject = { included += Included(name, fallback, IncludeKindHeuristic) fallback.include(context, name) } override def withFallback(fallback: ConfigIncluder) = { if (this.fallback == fallback) { this; } else if (this.fallback == null) { new RecordingIncluder(fallback, included); } else { new RecordingIncluder(this.fallback.withFallback(fallback), included) } } } class RecordingFullIncluder(fallback: ConfigIncluder, included: mutable.ListBuffer[Included]) extends RecordingIncluder(fallback, included) with ConfigIncluderFile with ConfigIncluderURL with ConfigIncluderClasspath { override def includeFile(context: ConfigIncludeContext, file: File) = { included += Included("file(" + file.getName() + ")", fallback, IncludeKindFile) fallback.asInstanceOf[ConfigIncluderFile].includeFile(context, file) } override def includeURL(context: ConfigIncludeContext, url: URL) = { included += Included("url(" + url.toExternalForm() + ")", fallback, IncludeKindURL) fallback.asInstanceOf[ConfigIncluderURL].includeURL(context, url) } override def includeResources(context: ConfigIncludeContext, name: String) = { included += Included("classpath(" + name + ")", fallback, IncludeKindFile) fallback.asInstanceOf[ConfigIncluderClasspath].includeResources(context, name) } override def withFallback(fallback: ConfigIncluder) = { if (this.fallback == fallback) { this; } else if (this.fallback == null) { new RecordingFullIncluder(fallback, included); } else { new RecordingFullIncluder(this.fallback.withFallback(fallback), included) } } } private def whatWasIncluded(parser: ConfigParseOptions => Config): List[Included] = { val included = mutable.ListBuffer[Included]() val includer = new RecordingIncluder(null, included) val conf = parser(ConfigParseOptions.defaults().setIncluder(includer).setAllowMissing(false)) included.toList } private def whatWasIncludedFull(parser: ConfigParseOptions => Config): List[Included] = { val included = mutable.ListBuffer[Included]() val includer = new RecordingFullIncluder(null, included) val conf = parser(ConfigParseOptions.defaults().setIncluder(includer).setAllowMissing(false)) included.toList } @Test def includersAreUsedWithFiles() { val included = whatWasIncluded(ConfigFactory.parseFile(resourceFile("test03.conf"), _)) assertEquals(List("test01", "test02.conf", "equiv01/original.json", "nothere", "nothere.conf", "nothere.json", "nothere.properties", "test03-included.conf", "test03-included.conf"), included.map(_.name)) } @Test def includersAreUsedRecursivelyWithFiles() { // includes.conf has recursive includes in it val included = whatWasIncluded(ConfigFactory.parseFile(resourceFile("equiv03/includes.conf"), _)) assertEquals(List("letters/a.conf", "numbers/1.conf", "numbers/2", "letters/b.json", "letters/c", "root/foo.conf"), included.map(_.name)) } @Test def includersAreUsedRecursivelyWithString() { val included = whatWasIncluded(ConfigFactory.parseString(""" include "equiv03/includes.conf" """, _)) assertEquals(List("equiv03/includes.conf", "letters/a.conf", "numbers/1.conf", "numbers/2", "letters/b.json", "letters/c", "root/foo.conf"), included.map(_.name)) } // full includer should only be used with the file(), url(), classpath() syntax. @Test def fullIncluderNotUsedWithoutNewSyntax() { val included = whatWasIncluded(ConfigFactory.parseFile(resourceFile("equiv03/includes.conf"), _)) assertEquals(List("letters/a.conf", "numbers/1.conf", "numbers/2", "letters/b.json", "letters/c", "root/foo.conf"), included.map(_.name)) val includedFull = whatWasIncludedFull(ConfigFactory.parseFile(resourceFile("equiv03/includes.conf"), _)) assertEquals(included, includedFull) } @Test def includersAreUsedWithClasspath() { val included = whatWasIncluded(ConfigFactory.parseResources(classOf[PublicApiTest], "/test03.conf", _)) assertEquals(List("test01", "test02.conf", "equiv01/original.json", "nothere", "nothere.conf", "nothere.json", "nothere.properties", "test03-included.conf", "test03-included.conf"), included.map(_.name)) } @Test def includersAreUsedRecursivelyWithClasspath() { // includes.conf has recursive includes in it; here we look it up // with an "absolute" class path resource. val included = whatWasIncluded(ConfigFactory.parseResources(classOf[PublicApiTest], "/equiv03/includes.conf", _)) assertEquals(List("letters/a.conf", "numbers/1.conf", "numbers/2", "letters/b.json", "letters/c", "root/foo.conf"), included.map(_.name)) } @Test def includersAreUsedRecursivelyWithClasspathRelativeResource() { // includes.conf has recursive includes in it; here we look it up // with a "class-relative" class path resource val included = whatWasIncluded(ConfigFactory.parseResources(classOf[SomethingInEquiv03], "includes.conf", _)) assertEquals(List("letters/a.conf", "numbers/1.conf", "numbers/2", "letters/b.json", "letters/c", "root/foo.conf"), included.map(_.name)) } @Test def includersAreUsedRecursivelyWithURL() { // includes.conf has recursive includes in it; here we look it up // with a URL val included = whatWasIncluded(ConfigFactory.parseURL(resourceFile("/equiv03/includes.conf").toURI.toURL, _)) assertEquals(List("letters/a.conf", "numbers/1.conf", "numbers/2", "letters/b.json", "letters/c", "root/foo.conf"), included.map(_.name)) } @Test def fullIncluderUsed() { val included = whatWasIncludedFull(ConfigFactory.parseString(""" include "equiv03/includes.conf" include file("nonexistent") include url("file:/nonexistent") include classpath("nonexistent") """, _)) assertEquals(List("equiv03/includes.conf", "letters/a.conf", "numbers/1.conf", "numbers/2", "letters/b.json", "letters/c", "root/foo.conf", "file(nonexistent)", "url(file:/nonexistent)", "classpath(nonexistent)"), included.map(_.name)) } @Test def nonFullIncluderSurvivesNewStyleIncludes() { val included = whatWasIncluded(ConfigFactory.parseString(""" include "equiv03/includes.conf" include file("nonexistent") include url("file:/nonexistent") include classpath("nonexistent") """, _)) assertEquals(List("equiv03/includes.conf", "letters/a.conf", "numbers/1.conf", "numbers/2", "letters/b.json", "letters/c", "root/foo.conf"), included.map(_.name)) } @Test def stringParsing() { val conf = ConfigFactory.parseString("{ a : b }", ConfigParseOptions.defaults()) assertEquals("b", conf.getString("a")) } @Test def readerParsing() { val conf = ConfigFactory.parseReader(new StringReader("{ a : b }"), ConfigParseOptions.defaults()) assertEquals("b", conf.getString("a")) } @Test def anySyntax() { // test01 has all three syntaxes; first load with basename val conf = ConfigFactory.parseFileAnySyntax(resourceFile("test01"), ConfigParseOptions.defaults()) assertEquals(42, conf.getInt("ints.fortyTwo")) assertEquals("A", conf.getString("fromJsonA")) assertEquals("true", conf.getString("fromProps.bool")) // now include a suffix, should only load one of them val onlyProps = ConfigFactory.parseFileAnySyntax(resourceFile("test01.properties"), ConfigParseOptions.defaults()) assertFalse(onlyProps.hasPath("ints.fortyTwo")) assertFalse(onlyProps.hasPath("fromJsonA")) assertEquals("true", onlyProps.getString("fromProps.bool")) // force only one syntax via options val onlyPropsViaOptions = ConfigFactory.parseFileAnySyntax(resourceFile("test01.properties"), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.PROPERTIES)) assertFalse(onlyPropsViaOptions.hasPath("ints.fortyTwo")) assertFalse(onlyPropsViaOptions.hasPath("fromJsonA")) assertEquals("true", onlyPropsViaOptions.getString("fromProps.bool")) // make sure it works with resources too val fromResources = ConfigFactory.parseResourcesAnySyntax(classOf[PublicApiTest], "/test01", ConfigParseOptions.defaults()) assertEquals(42, fromResources.getInt("ints.fortyTwo")) assertEquals("A", fromResources.getString("fromJsonA")) assertEquals("true", fromResources.getString("fromProps.bool")) } @Test def resourceFromAnotherClasspath() { val conf = ConfigFactory.parseResources(classOf[PublicApiTest], "/test-lib.conf", ConfigParseOptions.defaults()) assertEquals("This is to test classpath searches.", conf.getString("test-lib.description")) } @Test def multipleResourcesUsed() { val conf = ConfigFactory.parseResources(classOf[PublicApiTest], "/test01.conf") assertEquals(42, conf.getInt("ints.fortyTwo")) assertEquals(true, conf.getBoolean("test-lib.fromTestLib")) // check that each value has its own ConfigOrigin val v1 = conf.getValue("ints.fortyTwo") val v2 = conf.getValue("test-lib.fromTestLib") assertEquals("v1 has right origin resource", "test01.conf", v1.origin.resource) assertEquals("v2 has right origin resource", "test01.conf", v2.origin.resource) assertEquals(v1.origin.resource, v2.origin.resource) 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 should 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")) } @Test def usesContextClassLoaderForReferenceConf() { val loaderA1 = new TestClassLoader(this.getClass().getClassLoader(), Map("reference.conf" -> resourceFile("a_1.conf").toURI.toURL())) val loaderB2 = new TestClassLoader(this.getClass().getClassLoader(), Map("reference.conf" -> resourceFile("b_2.conf").toURI.toURL())) val configA1 = withContextClassLoader(loaderA1) { ConfigFactory.load() } assertEquals(1, configA1.getInt("a")) assertFalse("no b", configA1.hasPath("b")) val configB2 = withContextClassLoader(loaderB2) { ConfigFactory.load() } assertEquals(2, configB2.getInt("b")) assertFalse("no a", configB2.hasPath("a")) val configPlain = ConfigFactory.load() assertFalse("no a", configPlain.hasPath("a")) assertFalse("no b", configPlain.hasPath("b")) } @Test def supportsConfigLoadingStrategyAlteration(): Unit = { assertEquals("config.strategy is not set", null, System.getProperty("config.strategy")) System.setProperty("config.strategy", classOf[TestStrategy].getCanonicalName) try { val incovationsBeforeTest = TestStrategy.getIncovations() val loaderA1 = new TestClassLoader(this.getClass().getClassLoader(), Map("reference.conf" -> resourceFile("a_1.conf").toURI.toURL())) val configA1 = withContextClassLoader(loaderA1) { ConfigFactory.load() } ConfigFactory.load() assertEquals(1, configA1.getInt("a")) assertEquals(2, TestStrategy.getIncovations() - incovationsBeforeTest) } finally { System.clearProperty("config.strategy") } } @Test def usesContextClassLoaderForApplicationConf() { val loaderA1 = new TestClassLoader(this.getClass().getClassLoader(), Map("application.conf" -> resourceFile("a_1.conf").toURI.toURL())) val loaderB2 = new TestClassLoader(this.getClass().getClassLoader(), Map("application.conf" -> resourceFile("b_2.conf").toURI.toURL())) val configA1 = withContextClassLoader(loaderA1) { ConfigFactory.load() } assertEquals(1, configA1.getInt("a")) assertFalse("no b", configA1.hasPath("b")) val configB2 = withContextClassLoader(loaderB2) { ConfigFactory.load() } assertEquals(2, configB2.getInt("b")) assertFalse("no a", configB2.hasPath("a")) val configPlain = ConfigFactory.load() assertFalse("no a", configPlain.hasPath("a")) assertFalse("no b", configPlain.hasPath("b")) } @Test def usesSuppliedClassLoaderForReferenceConf() { val loaderA1 = new TestClassLoader(this.getClass().getClassLoader(), Map("reference.conf" -> resourceFile("a_1.conf").toURI.toURL())) val loaderB2 = new TestClassLoader(this.getClass().getClassLoader(), Map("reference.conf" -> resourceFile("b_2.conf").toURI.toURL())) val configA1 = ConfigFactory.load(loaderA1) assertEquals(1, configA1.getInt("a")) assertFalse("no b", configA1.hasPath("b")) val configB2 = ConfigFactory.load(loaderB2) assertEquals(2, configB2.getInt("b")) assertFalse("no a", configB2.hasPath("a")) val configPlain = ConfigFactory.load() assertFalse("no a", configPlain.hasPath("a")) assertFalse("no b", configPlain.hasPath("b")) // check the various overloads that take a loader parameter for ( c <- Seq(ConfigFactory.parseResources(loaderA1, "reference.conf"), ConfigFactory.parseResourcesAnySyntax(loaderA1, "reference"), ConfigFactory.parseResources(loaderA1, "reference.conf", ConfigParseOptions.defaults()), ConfigFactory.parseResourcesAnySyntax(loaderA1, "reference", ConfigParseOptions.defaults()), ConfigFactory.load(loaderA1, "application"), ConfigFactory.load(loaderA1, "application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults()), ConfigFactory.load(loaderA1, "application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults()), ConfigFactory.load(loaderA1, ConfigFactory.parseString("")), ConfigFactory.load(loaderA1, ConfigFactory.parseString(""), ConfigResolveOptions.defaults()), ConfigFactory.defaultReference(loaderA1)) ) { assertEquals(1, c.getInt("a")) assertFalse("no b", c.hasPath("b")) } // check providing the loader via ConfigParseOptions val withLoader = ConfigParseOptions.defaults().setClassLoader(loaderA1); for ( c <- Seq(ConfigFactory.parseResources("reference.conf", withLoader), ConfigFactory.parseResourcesAnySyntax("reference", withLoader), ConfigFactory.load("application", withLoader, ConfigResolveOptions.defaults())) ) { assertEquals(1, c.getInt("a")) assertFalse("no b", c.hasPath("b")) } // check not providing the loader for ( c <- Seq(ConfigFactory.parseResources("reference.conf"), ConfigFactory.parseResourcesAnySyntax("reference"), ConfigFactory.parseResources("reference.conf", ConfigParseOptions.defaults()), ConfigFactory.parseResourcesAnySyntax("reference", ConfigParseOptions.defaults()), ConfigFactory.load("application"), ConfigFactory.load("application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults()), ConfigFactory.load(ConfigFactory.parseString("")), ConfigFactory.load(ConfigFactory.parseString(""), ConfigResolveOptions.defaults()), ConfigFactory.defaultReference()) ) { assertFalse("no a", c.hasPath("a")) assertFalse("no b", c.hasPath("b")) } // check providing the loader via current context withContextClassLoader(loaderA1) { for ( c <- Seq(ConfigFactory.parseResources("reference.conf"), ConfigFactory.parseResourcesAnySyntax("reference"), ConfigFactory.parseResources("reference.conf", ConfigParseOptions.defaults()), ConfigFactory.parseResourcesAnySyntax("reference", ConfigParseOptions.defaults()), ConfigFactory.load("application"), ConfigFactory.load("application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults()), ConfigFactory.load(ConfigFactory.parseString("")), ConfigFactory.load(ConfigFactory.parseString(""), ConfigResolveOptions.defaults()), ConfigFactory.defaultReference()) ) { assertEquals(1, c.getInt("a")) assertFalse("no b", c.hasPath("b")) } } } @Test def usesSuppliedClassLoaderForApplicationConf() { val loaderA1 = new TestClassLoader(this.getClass().getClassLoader(), Map("application.conf" -> resourceFile("a_1.conf").toURI.toURL())) val loaderB2 = new TestClassLoader(this.getClass().getClassLoader(), Map("application.conf" -> resourceFile("b_2.conf").toURI.toURL())) val configA1 = ConfigFactory.load(loaderA1) assertEquals(1, configA1.getInt("a")) assertFalse("no b", configA1.hasPath("b")) val configB2 = ConfigFactory.load(loaderB2) assertEquals(2, configB2.getInt("b")) assertFalse("no a", configB2.hasPath("a")) val configPlain = ConfigFactory.load() assertFalse("no a", configPlain.hasPath("a")) assertFalse("no b", configPlain.hasPath("b")) // check the various overloads that take a loader parameter for ( c <- Seq(ConfigFactory.parseResources(loaderA1, "application.conf"), ConfigFactory.parseResourcesAnySyntax(loaderA1, "application"), ConfigFactory.parseResources(loaderA1, "application.conf", ConfigParseOptions.defaults()), ConfigFactory.parseResourcesAnySyntax(loaderA1, "application", ConfigParseOptions.defaults()), ConfigFactory.load(loaderA1, "application"), ConfigFactory.load(loaderA1, "application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults()), ConfigFactory.defaultApplication(loaderA1)) ) { assertEquals(1, c.getInt("a")) assertFalse("no b", c.hasPath("b")) } // check providing the loader via ConfigParseOptions val withLoader = ConfigParseOptions.defaults().setClassLoader(loaderA1); for ( c <- Seq(ConfigFactory.parseResources("application.conf", withLoader), ConfigFactory.parseResourcesAnySyntax("application", withLoader), ConfigFactory.defaultApplication(withLoader), ConfigFactory.load(withLoader, ConfigResolveOptions.defaults()), ConfigFactory.load("application", withLoader, ConfigResolveOptions.defaults())) ) { assertEquals(1, c.getInt("a")) assertFalse("no b", c.hasPath("b")) } // check not providing the loader for ( c <- Seq(ConfigFactory.parseResources("application.conf"), ConfigFactory.parseResourcesAnySyntax("application"), ConfigFactory.parseResources("application.conf", ConfigParseOptions.defaults()), ConfigFactory.parseResourcesAnySyntax("application", ConfigParseOptions.defaults()), ConfigFactory.load("application"), ConfigFactory.defaultApplication(), ConfigFactory.load("application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults())) ) { assertFalse("no a", c.hasPath("a")) assertFalse("no b", c.hasPath("b")) } // check providing the loader via current context withContextClassLoader(loaderA1) { for ( c <- Seq(ConfigFactory.parseResources("application.conf"), ConfigFactory.parseResourcesAnySyntax("application"), ConfigFactory.parseResources("application.conf", ConfigParseOptions.defaults()), ConfigFactory.parseResourcesAnySyntax("application", ConfigParseOptions.defaults()), ConfigFactory.load("application"), ConfigFactory.defaultApplication(), ConfigFactory.load("application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults())) ) { assertEquals(1, c.getInt("a")) assertFalse("no b", c.hasPath("b")) } } } @Test def cachedDefaultConfig() { val load1 = ConfigFactory.load() val load2 = ConfigFactory.load() assertTrue("load() was cached", load1 eq load2) assertEquals(load1, load2) // the other loader has to have some reference.conf or else we just get // back the system properties singleton which is not per-class-loader val otherLoader = new TestClassLoader(this.getClass().getClassLoader(), Map("reference.conf" -> resourceFile("a_1.conf").toURI.toURL())) val load3 = ConfigFactory.load(otherLoader) val load4 = ConfigFactory.load(otherLoader) assertTrue("different config for different classloaders", load1 ne load3) assertTrue("load(loader) was cached", load3 eq load4) assertEquals(load3, load4) val load5 = ConfigFactory.load() val load6 = ConfigFactory.load() assertTrue("load() was cached again", load5 eq load6) assertEquals(load5, load5) assertEquals(load1, load5) val load7 = ConfigFactory.load(otherLoader) assertTrue("cache was dropped when switching loaders", load3 ne load7) assertEquals(load3, load7) } @Test def cachedReferenceConfig() { val load1 = ConfigFactory.defaultReference() val load2 = ConfigFactory.defaultReference() assertTrue("defaultReference() was cached", load1 eq load2) assertEquals(load1, load2) // the other loader has to have some reference.conf or else we just get // back the system properties singleton which is not per-class-loader val otherLoader = new TestClassLoader(this.getClass().getClassLoader(), Map("reference.conf" -> resourceFile("a_1.conf").toURI.toURL())) val load3 = ConfigFactory.defaultReference(otherLoader) val load4 = ConfigFactory.defaultReference(otherLoader) assertTrue("different config for different classloaders", load1 ne load3) assertTrue("defaultReference(loader) was cached", load3 eq load4) assertEquals(load3, load4) val load5 = ConfigFactory.defaultReference() val load6 = ConfigFactory.defaultReference() assertTrue("defaultReference() was cached again", load5 eq load6) assertEquals(load5, load5) assertEquals(load1, load5) val load7 = ConfigFactory.defaultReference(otherLoader) assertTrue("cache was dropped when switching loaders", load3 ne load7) assertEquals(load3, load7) } @Test def detectIncludeCycle() { val e = intercept[ConfigException.Parse] { ConfigFactory.load("cycle") } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("include statements nested")) } // We would ideally make this case NOT throw an exception but we need to do some work // to get there, see https://github.com/typesafehub/config/issues/160 @Test def detectIncludeFromList() { val e = intercept[ConfigException.Parse] { ConfigFactory.load("include-from-list.conf") } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("limitation")) } @Test def missingOverrideResourceFails() { assertEquals("config.file is not set", null, System.getProperty("config.file")) val old = System.getProperty("config.resource") try { System.setProperty("config.resource", "donotexists.conf") intercept[ConfigException.IO] { ConfigFactory.load() } } finally { // cleanup properties Option(old).map { v => System.setProperty("config.resource", v) v }.orElse { System.clearProperty("config.resource") None } assertEquals("config.resource restored", old, System.getProperty("config.resource")) ConfigImpl.reloadSystemPropertiesConfig() } } @Test def missingOverrideFileFails() { assertEquals("config.resource is not set", null, System.getProperty("config.resource")) val old = System.getProperty("config.file") try { System.setProperty("config.file", "donotexists.conf") intercept[ConfigException.IO] { ConfigFactory.load() } } finally { // cleanup properties Option(old).map { v => System.setProperty("config.file", v) v }.orElse { System.clearProperty("config.file") None } assertEquals("config.file restored", old, System.getProperty("config.file")) ConfigImpl.reloadSystemPropertiesConfig() } } @Test def exceptionSerializable() { // ArrayList is a serialization problem so we want to cover it in tests val comments = new java.util.ArrayList(List("comment 1", "comment 2").asJava) val e = new ConfigException.WrongType(SimpleConfigOrigin.newSimple("an origin").withComments(comments), "this is a message", new RuntimeException("this is a cause")) val eCopy = checkSerializableNoMeaningfulEquals(e) assertTrue("messages equal after deserialize", e.getMessage.equals(eCopy.getMessage)) assertTrue("cause messages equal after deserialize", e.getCause().getMessage.equals(eCopy.getCause().getMessage)) assertTrue("origins equal after deserialize", e.origin().equals(eCopy.origin())) } @Test def exceptionSerializableWithNullOrigin() { val e = new ConfigException.Missing("this is a message", new RuntimeException("this is a cause")) assertTrue("origin null before serialize", e.origin() == null) val eCopy = checkSerializableNoMeaningfulEquals(e) assertTrue("messages equal after deserialize", e.getMessage.equals(eCopy.getMessage)) assertTrue("cause messages equal after deserialize", e.getCause().getMessage.equals(eCopy.getCause().getMessage)) assertTrue("origin null after deserialize", e.origin() == null) } @Test def exceptionSerializableWithWrongType() { val e = intercept[ConfigException.WrongType] { ConfigValueFactory.fromAnyRef(Map("item" -> "uhoh, fail").asJava) match { case o: ConfigObject => o.toConfig.getStringList("item") } } val eCopy = checkSerializableNoMeaningfulEquals(e) assertTrue("messages equal after deserialize", e.getMessage.equals(eCopy.getMessage)) } @Test def invalidateCaches() { val conf0 = ConfigFactory.load() val sys0 = ConfigFactory.systemProperties() val conf1 = ConfigFactory.load() val sys1 = ConfigFactory.systemProperties() ConfigFactory.invalidateCaches() val conf2 = ConfigFactory.load() val sys2 = ConfigFactory.systemProperties() System.setProperty("invalidateCachesTest", "Hello!") ConfigFactory.invalidateCaches() val conf3 = ConfigFactory.load() val sys3 = ConfigFactory.systemProperties() val conf4 = ConfigFactory.load() val sys4 = ConfigFactory.systemProperties() System.clearProperty("invalidateCachesTest") assertTrue("stuff gets cached sys", sys0 eq sys1) assertTrue("stuff gets cached conf", conf0 eq conf1) assertTrue("test system property is not set sys", !sys0.hasPath("invalidateCachesTest")) assertTrue("test system property is not set conf", !conf0.hasPath("invalidateCachesTest")) assertTrue("invalidate caches works on unchanged system props sys", sys1 ne sys2) assertTrue("invalidate caches works on unchanged system props conf", conf1 ne conf2) assertTrue("invalidate caches works on changed system props sys", sys2 ne sys3) assertTrue("invalidate caches works on changed system props conf", conf2 ne conf3) assertTrue("invalidate caches doesn't change value if no system prop changes sys", sys1 == sys2) assertTrue("invalidate caches doesn't change value if no system prop changes conf", conf1 == conf2) assertTrue("test system property is set sys", sys3.hasPath("invalidateCachesTest")) assertTrue("test system property is set conf", conf3.hasPath("invalidateCachesTest")) assertTrue("invalidate caches DOES change value if system props changed sys", sys2 != sys3) assertTrue("invalidate caches DOES change value if system props changed conf", conf2 != conf3) assertTrue("stuff gets cached repeatedly sys", sys3 eq sys4) assertTrue("stuff gets cached repeatedly conf", conf3 eq conf4) } @Test def invalidateReferenceConfig(): Unit = { val orig = ConfigFactory.defaultReference() val cached = ConfigFactory.defaultReference() assertTrue("reference config was cached", orig eq cached) ConfigFactory.invalidateCaches() val changed = ConfigFactory.defaultReference() assertTrue("reference config was invalidated", orig ne changed) } @Test def invalidateFullConfig(): Unit = { val orig = ConfigFactory.load() val cached = ConfigFactory.load() assertTrue("full config was cached", orig eq cached) ConfigFactory.invalidateCaches() val changed = ConfigFactory.load() assertTrue("full config was invalidated", orig ne changed) } @Test def canUseSomeValuesWithoutResolving(): Unit = { val conf = ConfigFactory.parseString("a=42,b=${NOPE}") assertEquals(42, conf.getInt("a")) intercept[ConfigException.NotResolved] { conf.getInt("b") } } @Test def heuristicIncludeChecksClasspath(): Unit = { // from https://github.com/typesafehub/config/issues/188 withScratchDirectory("heuristicIncludeChecksClasspath") { dir => val f = new File(dir, "foo.conf") writeFile(f, """ include "onclasspath" """) val conf = ConfigFactory.parseFile(f) assertEquals(42, conf.getInt("onclasspath")) } } @Test def fileIncludeStatements(): Unit = { val file = resourceFile("file-include.conf") val conf = ConfigFactory.parseFile(file) assertEquals("got file-include.conf", 41, conf.getInt("base")) assertEquals("got subdir/foo.conf", 42, conf.getInt("foo")) assertEquals("got bar.conf", 43, conf.getInt("bar")) // these two do not work right now, because we do not // treat the filename as relative to the including file // if file() is specified, so `include file("bar-file.conf")` // fails. //assertEquals("got bar-file.conf", 44, conf.getInt("bar-file")) //assertEquals("got subdir/baz.conf", 45, conf.getInt("baz")) assertFalse("did not get bar-file.conf", conf.hasPath("bar-file")) assertFalse("did not get subdir/baz.conf", conf.hasPath("baz")) } @Test def hasPathOrNullWorks(): Unit = { val conf = ConfigFactory.parseString("x.a=null,x.b=42") assertFalse("hasPath says false for null", conf.hasPath("x.a")) assertTrue("hasPathOrNull says true for null", conf.hasPathOrNull("x.a")) assertTrue("hasPath says true for non-null", conf.hasPath("x.b")) assertTrue("hasPathOrNull says true for non-null", conf.hasPathOrNull("x.b")) assertFalse("hasPath says false for missing", conf.hasPath("x.c")) assertFalse("hasPathOrNull says false for missing", conf.hasPathOrNull("x.c")) // this is to be sure we handle a null along the path correctly assertFalse("hasPath says false for missing under null", conf.hasPath("x.a.y")) assertFalse("hasPathOrNull says false for missing under null", conf.hasPathOrNull("x.a.y")) // this is to be sure we handle missing along the path correctly assertFalse("hasPath says false for missing under missing", conf.hasPath("x.c.y")) assertFalse("hasPathOrNull says false for missing under missing", conf.hasPathOrNull("x.c.y")) } @Test def getIsNullWorks(): Unit = { val conf = ConfigFactory.parseString("x.a=null,x.b=42") assertTrue("getIsNull says true for null", conf.getIsNull("x.a")) assertFalse("getIsNull says false for non-null", conf.getIsNull("x.b")) intercept[ConfigException.Missing] { conf.getIsNull("x.c") } // missing underneath null intercept[ConfigException.Missing] { conf.getIsNull("x.a.y") } // missing underneath missing intercept[ConfigException.Missing] { conf.getIsNull("x.c.y") } } } class TestStrategy extends DefaultConfigLoadingStrategy { override def parseApplicationConfig(parseOptions: ConfigParseOptions): Config = { TestStrategy.increment() super.parseApplicationConfig(parseOptions) } } object TestStrategy { private var invocations = 0 def getIncovations() = invocations def increment() = invocations += 1 }config-1.3.1/config/src/test/scala/com/typesafe/config/impl/TestUtils.scala000066400000000000000000001117361277147274600266620ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import com.typesafe.config.ConfigOrigin import java.io.Reader import java.io.StringReader import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigSyntax import com.typesafe.config.ConfigFactory import java.io.File import java.io.ByteArrayOutputStream import java.io.ObjectOutputStream import java.io.ByteArrayInputStream import java.io.ObjectInputStream import java.io.NotSerializableException import scala.annotation.tailrec import java.net.URL import java.util.Locale import java.util.concurrent.Executors import java.util.concurrent.Callable import com.typesafe.config._ import scala.reflect.ClassTag import scala.reflect.classTag import scala.collection.JavaConverters._ import language.implicitConversions abstract trait TestUtils { protected def intercept[E <: Throwable: ClassTag](block: => Any): E = { val expectedClass = classTag[E].runtimeClass var thrown: Option[Throwable] = None val result = try { Some(block) } catch { case t: Throwable => thrown = Some(t) None } thrown match { case Some(t) if expectedClass.isAssignableFrom(t.getClass) => t.asInstanceOf[E] case Some(t) => throw new Exception(s"Expected exception ${expectedClass.getName} was not thrown, got $t", t) case None => throw new Exception(s"Expected exception ${expectedClass.getName} was not thrown, no exception was thrown and got result $result") } } protected def describeFailure[A](desc: String)(code: => A): A = { try { code } catch { case t: Throwable => println("Failure on: '%s'".format(desc)) throw t } } private class NotEqualToAnythingElse { override def equals(other: Any) = { other match { case x: NotEqualToAnythingElse => true case _ => false } } override def hashCode() = 971 } private object notEqualToAnything extends NotEqualToAnythingElse private def checkNotEqualToRandomOtherThing(a: Any) { assertFalse(a.equals(notEqualToAnything)) assertFalse(notEqualToAnything.equals(a)) } protected def checkNotEqualObjects(a: Any, b: Any) { assertFalse(a.equals(b)) assertFalse(b.equals(a)) // hashcode inequality isn't guaranteed, but // as long as it happens to work it might // detect a bug (if hashcodes are equal, // check if it's due to a bug or correct // before you remove this) assertFalse(a.hashCode() == b.hashCode()) checkNotEqualToRandomOtherThing(a) checkNotEqualToRandomOtherThing(b) } protected def checkEqualObjects(a: Any, b: Any) { assertTrue(a.equals(b)) assertTrue(b.equals(a)) assertTrue(a.hashCode() == b.hashCode()) checkNotEqualToRandomOtherThing(a) checkNotEqualToRandomOtherThing(b) } private val hexDigits = { val a = new Array[Char](16) var i = 0 for (c <- '0' to '9') { a(i) = c i += 1 } for (c <- 'A' to 'F') { a(i) = c i += 1 } a } private def encodeLegibleBinary(bytes: Array[Byte]): String = { val sb = new java.lang.StringBuilder() for (b <- bytes) { if ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') || b == '-' || b == ':' || b == '.' || b == '/' || b == ' ') { sb.append('_') sb.appendCodePoint(b.asInstanceOf[Char]) } else { sb.appendCodePoint(hexDigits((b & 0xF0) >> 4)) sb.appendCodePoint(hexDigits(b & 0x0F)) } } sb.toString } private def decodeLegibleBinary(s: String): Array[Byte] = { val a = new Array[Byte](s.length() / 2) var i = 0 var j = 0 while (i < s.length()) { val sub = s.substring(i, i + 2) i += 2 if (sub.charAt(0) == '_') { a(j) = charWrapper(sub.charAt(1)).byteValue } else { a(j) = Integer.parseInt(sub, 16).byteValue } j += 1 } a } private def copyViaSerialize(o: java.io.Serializable): AnyRef = { val byteStream = new ByteArrayOutputStream() val objectStream = new ObjectOutputStream(byteStream) objectStream.writeObject(o) objectStream.close() val inStream = new ByteArrayInputStream(byteStream.toByteArray()) val inObjectStream = new ObjectInputStream(inStream) val copy = inObjectStream.readObject() inObjectStream.close() copy } protected def checkSerializationCompat[T: ClassTag](expectedHex: String, o: T, changedOK: Boolean = false): Unit = { // be sure we can still deserialize the old one val inStream = new ByteArrayInputStream(decodeLegibleBinary(expectedHex)) var failure: Option[Exception] = None var inObjectStream: ObjectInputStream = null val deserialized = try { inObjectStream = new ObjectInputStream(inStream) // this can throw too inObjectStream.readObject() } catch { case e: Exception => failure = Some(e) null } finally { if (inObjectStream != null) inObjectStream.close() } val why = failure.map({ e => ": " + e.getClass.getSimpleName + ": " + e.getMessage }).getOrElse("") val byteStream = new ByteArrayOutputStream() val objectStream = new ObjectOutputStream(byteStream) objectStream.writeObject(o) objectStream.close() val hex = encodeLegibleBinary(byteStream.toByteArray()) def showCorrectResult(): Unit = { if (expectedHex != hex) { @tailrec def outputStringLiteral(s: String): Unit = { if (s.nonEmpty) { val (head, tail) = s.splitAt(80) val plus = if (tail.isEmpty) "" else " +" System.err.println("\"" + head + "\"" + plus) outputStringLiteral(tail) } } System.err.println("Correct result literal for " + o.getClass.getSimpleName + " serialization:") System.err.println("\"\" + ") // line up all the lines by using empty string on first line outputStringLiteral(hex) } } try { assertEquals("Can no longer deserialize the old format of " + o.getClass.getSimpleName + why, o, deserialized) assertFalse(failure.isDefined) // should have thrown if we had a failure if (!changedOK) assertEquals(o.getClass.getSimpleName + " serialization has changed (though we still deserialized the old serialization)", expectedHex, hex) } catch { case e: Throwable => showCorrectResult() throw e } } protected def checkNotSerializable(o: AnyRef): Unit = { val byteStream = new ByteArrayOutputStream() val objectStream = new ObjectOutputStream(byteStream) val e = intercept[NotSerializableException] { objectStream.writeObject(o) } objectStream.close() } protected def checkSerializable[T: Manifest](expectedHex: String, o: T): T = { val t = checkSerializable(o) checkSerializationCompat(expectedHex, o) t } protected def checkSerializableOldFormat[T: Manifest](expectedHex: String, o: T): T = { val t = checkSerializable(o) checkSerializationCompat(expectedHex, o, changedOK = true) t } protected def checkSerializableNoMeaningfulEquals[T: Manifest](o: T): T = { assertTrue(o.getClass.getSimpleName + " not an instance of Serializable", o.isInstanceOf[java.io.Serializable]) val a = o.asInstanceOf[java.io.Serializable] val b = try { copyViaSerialize(a) } catch { case nf: ClassNotFoundException => throw new AssertionError("failed to make a copy via serialization, " + "possibly caused by http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6446627", nf) case e: Exception => System.err.println(e.getStackTrace.toString); throw new AssertionError("failed to make a copy via serialization", e) } assertTrue("deserialized type " + b.getClass.getSimpleName + " doesn't match serialized type " + a.getClass.getSimpleName, classTag[T].runtimeClass.isAssignableFrom(b.getClass)) b.asInstanceOf[T] } protected def checkSerializable[T: Manifest](o: T): T = { checkEqualObjects(o, o) assertTrue(o.getClass.getSimpleName + " not an instance of Serializable", o.isInstanceOf[java.io.Serializable]) val b = checkSerializableNoMeaningfulEquals(o) checkEqualObjects(o, b) checkEqualOrigins(o, b) b } // origin() is not part of value equality but is serialized, so // we check it separately protected def checkEqualOrigins[T](a: T, b: T): Unit = { import scala.collection.JavaConverters._ (a, b) match { case (obj1: ConfigObject, obj2: ConfigObject) => assertEquals(obj1.origin(), obj2.origin()) for (e <- obj1.entrySet().asScala) { checkEqualOrigins(e.getValue(), obj2.get(e.getKey())) } case (list1: ConfigList, list2: ConfigList) => assertEquals(list1.origin(), list2.origin()) for ((v1, v2) <- list1.asScala zip list2.asScala) { checkEqualOrigins(v1, v2) } case (value1: ConfigValue, value2: ConfigValue) => assertEquals(value1.origin(), value2.origin()) case _ => } } def fakeOrigin() = { SimpleConfigOrigin.newSimple("fake origin") } def includer() = { ConfigImpl.defaultIncluder(); } case class ParseTest(liftBehaviorUnexpected: Boolean, whitespaceMatters: Boolean, test: String) object ParseTest { def apply(liftBehaviorUnexpected: Boolean, test: String): ParseTest = { ParseTest(liftBehaviorUnexpected, false, test); } } implicit def string2jsontest(test: String): ParseTest = ParseTest(false, test) // note: it's important to put {} or [] at the root if you // want to test "invalidity reasons" other than "wrong root" private val invalidJsonInvalidConf = List[ParseTest]( "{", "}", "[", "]", ",", ParseTest(true, "10"), // value not in array or object, lift-json now allows this ParseTest(true, "\"foo\""), // value not in array or object, lift-json allows it "\"", // single quote by itself ParseTest(true, "[,]"), // array with just a comma in it; lift is OK with this ParseTest(true, "[,,]"), // array with just two commas in it; lift is cool with this too ParseTest(true, "[1,2,,]"), // array with two trailing commas ParseTest(true, "[,1,2]"), // array with initial comma ParseTest(true, "{ , }"), // object with just a comma in it ParseTest(true, "{ , , }"), // object with just two commas in it "{ 1,2 }", // object with single values not key-value pair ParseTest(true, "{ , \"foo\" : 10 }"), // object starts with comma ParseTest(true, "{ \"foo\" : 10 ,, }"), // object has two trailing commas " \"a\" : 10 ,, ", // two trailing commas for braceless root object "{ \"foo\" : }", // no value in object "{ : 10 }", // no key in object ParseTest(true, " \"foo\" : "), // no value in object with no braces; lift-json thinks this is acceptable ParseTest(true, " : 10 "), // no key in object with no braces; lift-json is cool with this too " \"foo\" : 10 } ", // close brace but no open " \"foo\" : 10 [ ", // no-braces object with trailing gunk "{ \"foo\" }", // no value or colon "{ \"a\" : [ }", // [ is not a valid value "{ \"foo\" : 10, true }", // non-key after comma "{ foo \n bar : 10 }", // newline in the middle of the unquoted key "[ 1, \\", // ends with backslash // these two problems are ignored by the lift tokenizer "[:\"foo\", \"bar\"]", // colon in an array; lift doesn't throw (tokenizer erases it) "[\"foo\" : \"bar\"]", // colon in an array another way, lift ignores (tokenizer erases it) "[ \"hello ]", // unterminated string ParseTest(true, "{ \"foo\" , true }"), // comma instead of colon, lift is fine with this ParseTest(true, "{ \"foo\" : true \"bar\" : false }"), // missing comma between fields, lift fine with this "[ 10, }]", // array with } as an element "[ 10, {]", // array with { as an element "{}x", // trailing invalid token after the root object "[]x", // trailing invalid token after the root array ParseTest(true, "{}{}"), // trailing token after the root object - lift OK with it ParseTest(true, "{}true"), // trailing token after the root object; lift ignores the {} ParseTest(true, "[]{}"), // trailing valid token after the root array ParseTest(true, "[]true"), // trailing valid token after the root array, lift ignores the [] "[${]", // unclosed substitution "[$]", // '$' by itself "[$ ]", // '$' by itself with spaces after "[${}]", // empty substitution (no path) "[${?}]", // no path with ? substitution ParseTest(false, true, "[${ ?foo}]"), // space before ? not allowed """{ "a" : [1,2], "b" : y${a}z }""", // trying to interpolate an array in a string """{ "a" : { "c" : 2 }, "b" : y${a}z }""", // trying to interpolate an object in a string """{ "a" : ${a} }""", // simple cycle """[ { "a" : 2, "b" : ${${a}} } ]""", // nested substitution "[ = ]", // = is not a valid token in unquoted text "[ + ]", "[ # ]", "[ ` ]", "[ ^ ]", "[ ? ]", "[ ! ]", "[ @ ]", "[ * ]", "[ & ]", "[ \\ ]", "+=", "[ += ]", "+= 10", "10 +=", "[ 10e+3e ]", // "+" not allowed in unquoted strings, and not a valid number ParseTest(true, "[ \"foo\nbar\" ]"), // unescaped newline in quoted string, lift doesn't care "[ # comment ]", "${ #comment }", "[ // comment ]", "${ // comment }", "{ include \"bar\" : 10 }", // include with a value after it "{ include foo }", // include with unquoted string "{ include : { \"a\" : 1 } }", // include used as unquoted key "a=", // no value "a:", // no value with colon "a= ", // no value with whitespace after "a.b=", // no value with path "{ a= }", // no value inside braces "{ a: }") // no value with colon inside braces // We'll automatically try each of these with whitespace modifications // so no need to add every possible whitespace variation protected val validJson = List[ParseTest]("{}", "[]", """{ "foo" : "bar" }""", """["foo", "bar"]""", """{ "foo" : 42 }""", "{ \"foo\"\n : 42 }", // newline after key "{ \"foo\" : \n 42 }", // newline after colon """[10, 11]""", """[10,"foo"]""", """{ "foo" : "bar", "baz" : "boo" }""", """{ "foo" : { "bar" : "baz" }, "baz" : "boo" }""", """{ "foo" : { "bar" : "baz", "woo" : "w00t" }, "baz" : "boo" }""", """{ "foo" : [10,11,12], "baz" : "boo" }""", """[{},{},{},{}]""", """[[[[[[]]]]]]""", """[[1], [1,2], [1,2,3], []]""", // nested multiple-valued array """{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":42}}}}}}}}""", "[ \"#comment\" ]", // quoted # comment "[ \"//comment\" ]", // quoted // comment // this long one is mostly to test rendering """{ "foo" : { "bar" : "baz", "woo" : "w00t" }, "baz" : { "bar" : "baz", "woo" : [1,2,3,4], "w00t" : true, "a" : false, "b" : 3.14, "c" : null } }""", "{}", ParseTest(true, "[ 10e+3 ]")) // "+" in a number (lift doesn't handle) private val validConfInvalidJson = List[ParseTest]("", // empty document " ", // empty document single space "\n", // empty document single newline " \n \n \n\n\n", // complicated empty document "# foo", // just a comment "# bar\n", // just a comment with a newline "# foo\n//bar", // comment then another with no newline """{ "foo" = 42 }""", // equals rather than colon """{ "foo" = (42) }""", // value with round braces """{ foo { "bar" : 42 } }""", // omit the colon for object value """{ foo baz { "bar" : 42 } }""", // omit the colon with unquoted key with spaces """ "foo" : 42 """, // omit braces on root object """{ "foo" : bar }""", // no quotes on value """{ "foo" : null bar 42 baz true 3.14 "hi" }""", // bunch of values to concat into a string "{ foo : \"bar\" }", // no quotes on key "{ foo : bar }", // no quotes on key or value "{ foo.bar : bar }", // path expression in key "{ foo.\"hello world\".baz : bar }", // partly-quoted path expression in key "{ foo.bar \n : bar }", // newline after path expression in key "{ foo bar : bar }", // whitespace in the key "{ true : bar }", // key is a non-string token ParseTest(true, """{ "foo" : "bar", "foo" : "bar2" }"""), // dup keys - lift just returns both ParseTest(true, "[ 1, 2, 3, ]"), // single trailing comma (lift fails to throw) ParseTest(true, "[1,2,3 , ]"), // single trailing comma with whitespace ParseTest(true, "[1,2,3\n\n , \n]"), // single trailing comma with newlines ParseTest(true, "[1,]"), // single trailing comma with one-element array ParseTest(true, "{ \"foo\" : 10, }"), // extra trailing comma (lift fails to throw) ParseTest(true, "{ \"a\" : \"b\", }"), // single trailing comma in object "{ a : b, }", // single trailing comma in object (unquoted strings) "{ a : b \n , \n }", // single trailing comma in object with newlines "a : b, c : d,", // single trailing comma in object with no root braces "{ a : b\nc : d }", // skip comma if there's a newline "a : b\nc : d", // skip comma if there's a newline and no root braces "a : b\nc : d,", // skip one comma but still have one at the end "[ foo ]", // not a known token in JSON "[ t ]", // start of "true" but ends wrong in JSON "[ tx ]", "[ tr ]", "[ trx ]", "[ tru ]", "[ trux ]", "[ truex ]", "[ 10x ]", // number token with trailing junk "[ / ]", // unquoted string "slash" "{ include \"foo\" }", // valid include "{ include\n\"foo\" }", // include with just a newline separating from string "{ include\"foo\" }", // include with no whitespace after it "[ include ]", // include can be a string value in an array "{ foo : include }", // include can be a field value also "{ include \"foo\", \"a\" : \"b\" }", // valid include followed by comma and field "{ foo include : 42 }", // valid to have a key not starting with include "[ ${foo} ]", "[ ${?foo} ]", "[ ${\"foo\"} ]", "[ ${foo.bar} ]", "[ abc xyz ${foo.bar} qrs tuv ]", // value concatenation "[ 1, 2, 3, blah ]", "[ ${\"foo.bar\"} ]", "{} # comment", "{} // comment", """{ "foo" #comment : 10 }""", """{ "foo" // comment : 10 }""", """{ "foo" : #comment 10 }""", """{ "foo" : // comment 10 }""", """{ "foo" : 10 #comment }""", """{ "foo" : 10 // comment }""", """[ 10, # comment 11]""", """[ 10, // comment 11]""", """[ 10 # comment , 11]""", """[ 10 // comment , 11]""", """{ /a/b/c : 10 }""", // key has a slash in it ParseTest(false, true, "[${ foo.bar}]"), // substitution with leading spaces ParseTest(false, true, "[${foo.bar }]"), // substitution with trailing spaces ParseTest(false, true, "[${ \"foo.bar\"}]"), // substitution with leading spaces and quoted ParseTest(false, true, "[${\"foo.bar\" }]"), // substitution with trailing spaces and quoted """[ ${"foo""bar"} ]""", // multiple strings in substitution """[ ${foo "bar" baz} ]""", // multiple strings and whitespace in substitution "[${true}]", // substitution with unquoted true token "a = [], a += b", // += operator with previous init "{ a = [], a += 10 }", // += in braces object with previous init "a += b", // += operator without previous init "{ a += 10 }", // += in braces object without previous init "[ 10e3e3 ]", // two exponents. this should parse to a number plus string "e3" "[ 1-e3 ]", // malformed number should end up as a string instead "[ 1.0.0 ]", // two decimals, should end up as a string "[ 1.0. ]") // trailing decimal should end up as a string protected val invalidJson = validConfInvalidJson ++ invalidJsonInvalidConf; protected val invalidConf = invalidJsonInvalidConf; // .conf is a superset of JSON so validJson just goes in here protected val validConf = validConfInvalidJson ++ validJson; protected def addOffendingJsonToException[R](parserName: String, s: String)(body: => R) = { try { body } catch { case t: Throwable => val tokens = try { "tokens: " + tokenizeAsList(s) } catch { case e: Throwable => "tokenizer failed: " + e.getMessage(); } // don't use AssertionError because it seems to keep Eclipse // from showing the causing exception in JUnit view for some reason throw new Exception(parserName + " parser did wrong thing on '" + s + "', " + tokens, t) } } protected def whitespaceVariations(tests: Seq[ParseTest], validInLift: Boolean): Seq[ParseTest] = { val variations = List({ s: String => s }, // identity { s: String => " " + s }, { s: String => s + " " }, { s: String => " " + s + " " }, { s: String => s.replace(" ", "") }, // this would break with whitespace in a key or value { s: String => s.replace(":", " : ") }, // could break with : in a key or value { s: String => s.replace(",", " , ") } // could break with , in a key or value ) tests flatMap { t => if (t.whitespaceMatters) { Seq(t) } else { val withNonAscii = if (t.test.contains(" ")) Seq(ParseTest(validInLift, t.test.replace(" ", "\u2003"))) // 2003 = em space, to test non-ascii whitespace else Seq() withNonAscii ++ (for (v <- variations) yield ParseTest(t.liftBehaviorUnexpected, v(t.test))) } } } // it's important that these do NOT use the public API to create the // instances, because we may be testing that the public API returns the // right instance by comparing to these, so using public API here would // make the test compare public API to itself. protected def intValue(i: Int) = new ConfigInt(fakeOrigin(), i, null) protected def longValue(l: Long) = new ConfigLong(fakeOrigin(), l, null) protected def boolValue(b: Boolean) = new ConfigBoolean(fakeOrigin(), b) protected def nullValue() = new ConfigNull(fakeOrigin()) protected def stringValue(s: String) = new ConfigString.Quoted(fakeOrigin(), s) protected def doubleValue(d: Double) = new ConfigDouble(fakeOrigin(), d, null) protected def parseObject(s: String) = { parseConfig(s).root } protected def parseConfig(s: String) = { val options = ConfigParseOptions.defaults(). setOriginDescription("test string"). setSyntax(ConfigSyntax.CONF); ConfigFactory.parseString(s, options).asInstanceOf[SimpleConfig] } protected def subst(ref: String, optional: Boolean): ConfigReference = { val path = Path.newPath(ref) new ConfigReference(fakeOrigin(), new SubstitutionExpression(path, optional)) } protected def subst(ref: String): ConfigReference = { subst(ref, false) } protected def substInString(ref: String, optional: Boolean): ConfigConcatenation = { import scala.collection.JavaConverters._ val path = Path.newPath(ref) val pieces = List[AbstractConfigValue](stringValue("start<"), subst(ref, optional), stringValue(">end")) new ConfigConcatenation(fakeOrigin(), pieces.asJava) } protected def substInString(ref: String): ConfigConcatenation = { substInString(ref, false) } def tokenTrue = Tokens.newBoolean(fakeOrigin(), true) def tokenFalse = Tokens.newBoolean(fakeOrigin(), false) def tokenNull = Tokens.newNull(fakeOrigin()) def tokenUnquoted(s: String) = Tokens.newUnquotedText(fakeOrigin(), s) def tokenString(s: String) = Tokens.newString(fakeOrigin(), s, "\"" + s + "\"") def tokenDouble(d: Double) = Tokens.newDouble(fakeOrigin(), d, "" + d) def tokenInt(i: Int) = Tokens.newInt(fakeOrigin(), i, "" + i) def tokenLong(l: Long) = Tokens.newLong(fakeOrigin(), l, l.toString()) def tokenLine(line: Int) = Tokens.newLine(fakeOrigin.withLineNumber(line)) def tokenCommentDoubleSlash(text: String) = Tokens.newCommentDoubleSlash(fakeOrigin(), text) def tokenCommentHash(text: String) = Tokens.newCommentHash(fakeOrigin(), text) def tokenWhitespace(text: String) = Tokens.newIgnoredWhitespace(fakeOrigin(), text) private def tokenMaybeOptionalSubstitution(optional: Boolean, expression: Token*) = { val l = new java.util.ArrayList[Token] for (t <- expression) { l.add(t); } Tokens.newSubstitution(fakeOrigin(), optional, l); } def tokenSubstitution(expression: Token*) = { tokenMaybeOptionalSubstitution(false, expression: _*) } def tokenOptionalSubstitution(expression: Token*) = { tokenMaybeOptionalSubstitution(true, expression: _*) } // quoted string substitution (no interpretation of periods) def tokenKeySubstitution(s: String) = tokenSubstitution(tokenString(s)) def tokenize(origin: ConfigOrigin, input: Reader): java.util.Iterator[Token] = { Tokenizer.tokenize(origin, input, ConfigSyntax.CONF) } def tokenize(input: Reader): java.util.Iterator[Token] = { tokenize(SimpleConfigOrigin.newSimple("anonymous Reader"), input) } def tokenize(s: String): java.util.Iterator[Token] = { val reader = new StringReader(s) val result = tokenize(reader) // reader.close() // can't close until the iterator is traversed, so this tokenize() flavor is inherently broken result } def tokenizeAsList(s: String) = { import scala.collection.JavaConverters._ tokenize(s).asScala.toList } def tokenizeAsString(s: String) = { Tokenizer.render(tokenize(s)) } def configNodeSimpleValue(value: Token) = { new ConfigNodeSimpleValue(value) } def configNodeKey(path: String) = PathParser.parsePathNode(path) def configNodeSingleToken(value: Token) = { new ConfigNodeSingleToken(value: Token) } def configNodeObject(nodes: List[AbstractConfigNode]) = { new ConfigNodeObject(nodes.asJavaCollection) } def configNodeArray(nodes: List[AbstractConfigNode]) = { new ConfigNodeArray(nodes.asJavaCollection) } def configNodeConcatenation(nodes: List[AbstractConfigNode]) = { new ConfigNodeConcatenation(nodes.asJavaCollection) } def nodeColon = new ConfigNodeSingleToken(Tokens.COLON) def nodeSpace = new ConfigNodeSingleToken(tokenUnquoted(" ")) def nodeOpenBrace = new ConfigNodeSingleToken(Tokens.OPEN_CURLY) def nodeCloseBrace = new ConfigNodeSingleToken(Tokens.CLOSE_CURLY) def nodeOpenBracket = new ConfigNodeSingleToken(Tokens.OPEN_SQUARE) def nodeCloseBracket = new ConfigNodeSingleToken(Tokens.CLOSE_SQUARE) def nodeComma = new ConfigNodeSingleToken(Tokens.COMMA) def nodeLine(line: Integer) = new ConfigNodeSingleToken(tokenLine(line)) def nodeWhitespace(whitespace: String) = new ConfigNodeSingleToken(tokenWhitespace(whitespace)) def nodeKeyValuePair(key: ConfigNodePath, value: AbstractConfigNodeValue) = { val nodes = List(key, nodeSpace, nodeColon, nodeSpace, value) new ConfigNodeField(nodes.asJavaCollection) } def nodeInt(value: Integer) = new ConfigNodeSimpleValue(tokenInt(value)) def nodeString(value: String) = new ConfigNodeSimpleValue(tokenString(value)) def nodeLong(value: Long) = new ConfigNodeSimpleValue(tokenLong(value)) def nodeDouble(value: Double) = new ConfigNodeSimpleValue(tokenDouble(value)) def nodeTrue = new ConfigNodeSimpleValue(tokenTrue) def nodeFalse = new ConfigNodeSimpleValue(tokenFalse) def nodeCommentHash(text: String) = new ConfigNodeComment(tokenCommentHash(text)) def nodeCommentDoubleSlash(text: String) = new ConfigNodeComment(tokenCommentDoubleSlash(text)) def nodeUnquotedText(text: String) = new ConfigNodeSimpleValue(tokenUnquoted(text)) def nodeNull = new ConfigNodeSimpleValue(tokenNull) def nodeKeySubstitution(s: String) = new ConfigNodeSimpleValue(tokenKeySubstitution(s)) def nodeOptionalSubstitution(expression: Token*) = new ConfigNodeSimpleValue(tokenOptionalSubstitution(expression: _*)) def nodeSubstitution(expression: Token*) = new ConfigNodeSimpleValue(tokenSubstitution(expression: _*)) def isWindows: Boolean = sys.props.get("os.name").exists(_.toLowerCase(Locale.ROOT).contains("windows")) def userDrive: String = if (isWindows) sys.props.get("user.dir").fold("")(_.takeWhile(_ != File.separatorChar)) else "" // this is importantly NOT using Path.newPath, which relies on // the parser; in the test suite we are often testing the parser, // so we don't want to use the parser to build the expected result. def path(elements: String*) = new Path(elements: _*) val resourceDir = { val f = new File("config/src/test/resources") if (!f.exists()) { val here = new File(".").getAbsolutePath throw new Exception(s"Tests must be run from the root project directory containing ${f.getPath()}, however the current directory is $here") } f } protected def resourceFile(filename: String): File = new File(resourceDir, filename) protected def jsonQuotedResourceFile(filename: String): String = quoteJsonString(resourceFile(filename).toString) protected class TestClassLoader(parent: ClassLoader, val additions: Map[String, URL]) extends ClassLoader(parent) { override def findResources(name: String) = { import scala.collection.JavaConverters._ val other = super.findResources(name).asScala additions.get(name).map({ url => Iterator(url) ++ other }).getOrElse(other).asJavaEnumeration } override def findResource(name: String) = { additions.get(name).getOrElse(null) } } protected def withContextClassLoader[T](loader: ClassLoader)(body: => T): T = { val executor = Executors.newSingleThreadExecutor() val f = executor.submit(new Callable[T] { override def call(): T = { val t = Thread.currentThread() val old = t.getContextClassLoader() t.setContextClassLoader(loader) val result = try { body } finally { t.setContextClassLoader(old) } result } }) f.get } private def printIndented(indent: Int, s: String): Unit = { for (i <- 0 to indent) System.err.print(' ') System.err.println(s) } protected def showDiff(a: ConfigValue, b: ConfigValue, indent: Int = 0): Unit = { if (a != b) { if (a.valueType != b.valueType) { printIndented(indent, "- " + a.valueType) printIndented(indent, "+ " + b.valueType) } else if (a.valueType == ConfigValueType.OBJECT) { import scala.collection.JavaConverters._ printIndented(indent, "OBJECT") val aS = a.asInstanceOf[ConfigObject].asScala val bS = b.asInstanceOf[ConfigObject].asScala for (aKV <- aS) { val bVOption = bS.get(aKV._1) if (Some(aKV._2) != bVOption) { printIndented(indent + 1, aKV._1) if (bVOption.isDefined) { showDiff(aKV._2, bVOption.get, indent + 2) } else { printIndented(indent + 2, "- " + aKV._2) printIndented(indent + 2, "+ (missing)") } } } } else { printIndented(indent, "- " + a) printIndented(indent, "+ " + b) } } } protected def quoteJsonString(s: String): String = ConfigImplUtil.renderJsonString(s) sealed abstract class Problem(path: String, line: Int) { def check(p: ConfigException.ValidationProblem) { assertEquals("matching path", path, p.path()) assertEquals("matching line for " + path, line, p.origin().lineNumber()) } protected def assertMessage(p: ConfigException.ValidationProblem, re: String) { assertTrue("didn't get expected message for " + path + ": got '" + p.problem() + "'", p.problem().matches(re)) } } case class Missing(path: String, line: Int, expected: String) extends Problem(path, line) { override def check(p: ConfigException.ValidationProblem) { super.check(p) val re = "No setting.*" + path + ".*expecting.*" + expected + ".*" assertMessage(p, re) } } case class WrongType(path: String, line: Int, expected: String, got: String) extends Problem(path, line) { override def check(p: ConfigException.ValidationProblem) { super.check(p) val re = "Wrong value type.*" + path + ".*expecting.*" + expected + ".*got.*" + got + ".*" assertMessage(p, re) } } case class WrongElementType(path: String, line: Int, expected: String, got: String) extends Problem(path, line) { override def check(p: ConfigException.ValidationProblem) { super.check(p) val re = "List at.*" + path + ".*wrong value type.*expecting.*" + expected + ".*got.*element of.*" + got + ".*" assertMessage(p, re) } } protected def checkValidationException(e: ConfigException.ValidationFailed, expecteds: Seq[Problem]) { val problems = e.problems().asScala.toIndexedSeq.sortBy(_.path).sortBy(_.origin.lineNumber) //for (problem <- problems) // System.err.println(problem.origin().description() + ": " + problem.path() + ": " + problem.problem()) for ((problem, expected) <- problems zip expecteds) { expected.check(problem) } assertEquals("found expected validation problems, got '" + problems + "' and expected '" + expecteds + "'", expecteds.size, problems.size) } protected def writeFile(f: File, content: String): Unit = { val writer = new java.io.PrintWriter(f, "UTF-8") writer.append(content) writer.close() } private def deleteRecursive(f: File): Unit = { if (f.exists) { if (f.isDirectory) { val children = f.listFiles() if (children ne null) { for (c <- children) deleteRecursive(c) } } f.delete() } } protected def withScratchDirectory[T](testcase: String)(body: File => T): Unit = { val target = new File("config/target") if (!target.isDirectory) throw new RuntimeException(s"Expecting $target to exist") val suffix = java.lang.Integer.toHexString(java.util.concurrent.ThreadLocalRandom.current.nextInt) val scratch = new File(target, s"$testcase-$suffix") scratch.mkdirs() try { body(scratch) } finally { deleteRecursive(scratch) } } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/TokenTest.scala000066400000000000000000000047221277147274600266360ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ class TokenTest extends TestUtils { @Test def tokenEquality() { // syntax tokens checkEqualObjects(Tokens.START, Tokens.START) checkNotEqualObjects(Tokens.START, Tokens.OPEN_CURLY) // int checkEqualObjects(tokenInt(42), tokenInt(42)) checkNotEqualObjects(tokenInt(42), tokenInt(43)) // long checkEqualObjects(tokenLong(42), tokenLong(42)) checkNotEqualObjects(tokenLong(42), tokenLong(43)) // int and long mixed checkEqualObjects(tokenInt(42), tokenLong(42)) checkNotEqualObjects(tokenInt(42), tokenLong(43)) // boolean checkEqualObjects(tokenTrue, tokenTrue) checkNotEqualObjects(tokenTrue, tokenFalse) // double checkEqualObjects(tokenDouble(3.14), tokenDouble(3.14)) checkNotEqualObjects(tokenDouble(3.14), tokenDouble(4.14)) // string checkEqualObjects(tokenString("foo"), tokenString("foo")) checkNotEqualObjects(tokenString("foo"), tokenString("bar")) // unquoted checkEqualObjects(tokenUnquoted("foo"), tokenUnquoted("foo")) checkNotEqualObjects(tokenUnquoted("foo"), tokenUnquoted("bar")) // key subst checkEqualObjects(tokenKeySubstitution("foo"), tokenKeySubstitution("foo")) checkNotEqualObjects(tokenKeySubstitution("foo"), tokenKeySubstitution("bar")) // null checkEqualObjects(tokenNull, tokenNull) // newline checkEqualObjects(tokenLine(10), tokenLine(10)) checkNotEqualObjects(tokenLine(10), tokenLine(11)) // different types are not equal checkNotEqualObjects(tokenTrue, tokenInt(1)) checkNotEqualObjects(tokenString("true"), tokenTrue) } @Test def tokenToString() { // just be sure toString() doesn't throw, it's for debugging // so its exact output doesn't matter a lot tokenTrue.toString() tokenFalse.toString() tokenInt(42).toString() tokenLong(43).toString() tokenDouble(3.14).toString() tokenNull.toString() tokenUnquoted("foo").toString() tokenString("bar").toString() tokenKeySubstitution("a").toString() tokenLine(10).toString() Tokens.START.toString() Tokens.END.toString() Tokens.COLON.toString() } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/TokenizerTest.scala000066400000000000000000000326531277147274600275340ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit.Test import com.typesafe.config.ConfigException import language.implicitConversions class TokenizerTest extends TestUtils { // FIXME most of this file should be using this method private def tokenizerTest(expected: List[Token], s: String) { assertEquals(List(Tokens.START) ++ expected ++ List(Tokens.END), tokenizeAsList(s)) assertEquals(s, tokenizeAsString(s)) } @Test def tokenizeEmptyString() { val source = "" val expected = List() tokenizerTest(expected, source) } @Test def tokenizeNewlines() { val source = "\n\n" val expected = List(tokenLine(1), tokenLine(2)) tokenizerTest(expected, source) } @Test def tokenizeAllTypesNoSpaces() { // all token types with no spaces (not sure JSON spec wants this to work, // but spec is unclear to me when spaces are required, and banning them // is actually extra work). val source = """,:=}{][+="foo"""" + "\"\"\"bar\"\"\"" + """true3.14false42null${a.b}${?x.y}${"c.d"}""" + "\n" val expected = List(Tokens.COMMA, Tokens.COLON, Tokens.EQUALS, Tokens.CLOSE_CURLY, Tokens.OPEN_CURLY, Tokens.CLOSE_SQUARE, Tokens.OPEN_SQUARE, Tokens.PLUS_EQUALS, tokenString("foo"), tokenString("bar"), tokenTrue, tokenDouble(3.14), tokenFalse, tokenLong(42), tokenNull, tokenSubstitution(tokenUnquoted("a.b")), tokenOptionalSubstitution(tokenUnquoted("x.y")), tokenKeySubstitution("c.d"), tokenLine(1)) tokenizerTest(expected, source) } @Test def tokenizeAllTypesWithSingleSpaces() { val source = """ , : = } { ] [ += "foo" """ + "\"\"\"bar\"\"\"" + """ 42 true 3.14 false null ${a.b} ${?x.y} ${"c.d"} """ + "\n " val expected = List(tokenWhitespace(" "), Tokens.COMMA, tokenWhitespace(" "), Tokens.COLON, tokenWhitespace(" "), Tokens.EQUALS, tokenWhitespace(" "), Tokens.CLOSE_CURLY, tokenWhitespace(" "), Tokens.OPEN_CURLY, tokenWhitespace(" "), Tokens.CLOSE_SQUARE, tokenWhitespace(" "), Tokens.OPEN_SQUARE, tokenWhitespace(" "), Tokens.PLUS_EQUALS, tokenWhitespace(" "), tokenString("foo"), tokenUnquoted(" "), tokenString("bar"), tokenUnquoted(" "), tokenLong(42), tokenUnquoted(" "), tokenTrue, tokenUnquoted(" "), tokenDouble(3.14), tokenUnquoted(" "), tokenFalse, tokenUnquoted(" "), tokenNull, tokenUnquoted(" "), tokenSubstitution(tokenUnquoted("a.b")), tokenUnquoted(" "), tokenOptionalSubstitution(tokenUnquoted("x.y")), tokenUnquoted(" "), tokenKeySubstitution("c.d"), tokenWhitespace(" "), tokenLine(1), tokenWhitespace(" ")) tokenizerTest(expected, source) } @Test def tokenizeAllTypesWithMultipleSpaces() { val source = """ , : = } { ] [ += "foo" """ + "\"\"\"bar\"\"\"" + """ 42 true 3.14 false null ${a.b} ${?x.y} ${"c.d"} """ + "\n " val expected = List(tokenWhitespace(" "), Tokens.COMMA, tokenWhitespace(" "), Tokens.COLON, tokenWhitespace(" "), Tokens.EQUALS, tokenWhitespace(" "), Tokens.CLOSE_CURLY, tokenWhitespace(" "), Tokens.OPEN_CURLY, tokenWhitespace(" "), Tokens.CLOSE_SQUARE, tokenWhitespace(" "), Tokens.OPEN_SQUARE, tokenWhitespace(" "), Tokens.PLUS_EQUALS, tokenWhitespace(" "), tokenString("foo"), tokenUnquoted(" "), tokenString("bar"), tokenUnquoted(" "), tokenLong(42), tokenUnquoted(" "), tokenTrue, tokenUnquoted(" "), tokenDouble(3.14), tokenUnquoted(" "), tokenFalse, tokenUnquoted(" "), tokenNull, tokenUnquoted(" "), tokenSubstitution(tokenUnquoted("a.b")), tokenUnquoted(" "), tokenOptionalSubstitution(tokenUnquoted("x.y")), tokenUnquoted(" "), tokenKeySubstitution("c.d"), tokenWhitespace(" "), tokenLine(1), tokenWhitespace(" ")) tokenizerTest(expected, source) } @Test def tokenizeTrueAndUnquotedText() { val source = """truefoo""" val expected = List(tokenTrue, tokenUnquoted("foo")) tokenizerTest(expected, source) } @Test def tokenizeFalseAndUnquotedText() { val source = """falsefoo""" val expected = List(tokenFalse, tokenUnquoted("foo")) tokenizerTest(expected, source) } @Test def tokenizeNullAndUnquotedText() { val source = """nullfoo""" val expected = List(tokenNull, tokenUnquoted("foo")) tokenizerTest(expected, source) } @Test def tokenizeUnquotedTextContainingRoundBrace() { val source = """(footrue)""" val expected = List(tokenUnquoted("(footrue)")) tokenizerTest(expected, source) } @Test def tokenizeUnquotedTextContainingTrue() { val source = """footrue""" val expected = List(tokenUnquoted("footrue")) tokenizerTest(expected, source) } @Test def tokenizeUnquotedTextContainingSpaceTrue() { val source = """foo true""" val expected = List(tokenUnquoted("foo"), tokenUnquoted(" "), tokenTrue) tokenizerTest(expected, source) } @Test def tokenizeTrueAndSpaceAndUnquotedText() { val source = """true foo""" val expected = List(tokenTrue, tokenUnquoted(" "), tokenUnquoted("foo")) tokenizerTest(expected, source) } @Test def tokenizeUnquotedTextContainingSlash() { tokenizerTest(List(tokenUnquoted("a/b/c/")), "a/b/c/") tokenizerTest(List(tokenUnquoted("/")), "/") tokenizerTest(List(tokenUnquoted("/"), tokenUnquoted(" "), tokenUnquoted("/")), "/ /") tokenizerTest(List(tokenCommentDoubleSlash("")), "//") } @Test def tokenizeUnquotedTextKeepsSpaces() { val source = " foo \n" val expected = List(tokenWhitespace(" "), tokenUnquoted("foo"), tokenWhitespace(" "), tokenLine(1)) tokenizerTest(expected, source) } @Test def tokenizeUnquotedTextKeepsInternalSpaces() { val source = " foo bar baz \n" val expected = List(tokenWhitespace(" "), tokenUnquoted("foo"), tokenUnquoted(" "), tokenUnquoted("bar"), tokenUnquoted(" "), tokenUnquoted("baz"), tokenWhitespace(" "), tokenLine(1)) tokenizerTest(expected, source) } @Test def tokenizeMixedUnquotedQuoted() { val source = " foo\"bar\"baz \n" val expected = List(tokenWhitespace(" "), tokenUnquoted("foo"), tokenString("bar"), tokenUnquoted("baz"), tokenWhitespace(" "), tokenLine(1)) tokenizerTest(expected, source) } @Test def tokenizerUnescapeStrings(): Unit = { case class UnescapeTest(escaped: String, result: ConfigString) implicit def pair2unescapetest(pair: (String, String)): UnescapeTest = UnescapeTest(pair._1, new ConfigString.Quoted(fakeOrigin(), pair._2)) // getting the actual 6 chars we want in a string is a little pesky. // \u005C is backslash. Just prove we're doing it right here. assertEquals(6, "\\u0046".length) assertEquals('4', "\\u0046"(4)) assertEquals('6', "\\u0046"(5)) val tests = List[UnescapeTest]((""" "" """, ""), (" \"\\u0000\" ", Character.toString(0)), // nul byte (""" "\"\\\/\b\f\n\r\t" """, "\"\\/\b\f\n\r\t"), (" \"\\u0046\" ", "F"), (" \"\\u0046\\u0046\" ", "FF")) for (t <- tests) { describeFailure(t.toString) { val expected = List(tokenWhitespace(" "), Tokens.newValue(t.result, t.toString), tokenWhitespace(" ")) tokenizerTest(expected, t.escaped) } } } @Test def tokenizerReturnsProblemOnInvalidStrings(): Unit = { val invalidTests = List(""" "\" """, // nothing after a backslash """ "\q" """, // there is no \q escape sequence "\"\\u123\"", // too short "\"\\u12\"", // too short "\"\\u1\"", // too short "\"\\u\"", // too short "\"", // just a single quote """ "abcdefg""", // no end quote """\"\""", // file ends with a backslash "$", // file ends with a $ "${" // file ends with a ${ ) for (t <- invalidTests) { val tokenized = tokenizeAsList(t) val maybeProblem = tokenized.find(Tokens.isProblem(_)) assertTrue(s"expected failure for <$t> but got ${t}", maybeProblem.isDefined) } } @Test def tokenizerEmptyTripleQuoted(): Unit = { val source = "\"\"\"\"\"\"" val expected = List(tokenString("")) tokenizerTest(expected, source) } @Test def tokenizerTrivialTripleQuoted(): Unit = { val source = "\"\"\"bar\"\"\"" val expected = List(tokenString("bar")) tokenizerTest(expected, source) } @Test def tokenizerNoEscapesInTripleQuoted(): Unit = { val source = "\"\"\"\\n\"\"\"" val expected = List(tokenString("\\n")) tokenizerTest(expected, source) } @Test def tokenizerTrailingQuotesInTripleQuoted(): Unit = { val source = "\"\"\"\"\"\"\"\"\"" val expected = List(tokenString("\"\"\"")) tokenizerTest(expected, source) } @Test def tokenizerNewlineInTripleQuoted(): Unit = { val source = "\"\"\"foo\nbar\"\"\"" val expected = List(tokenString("foo\nbar")) tokenizerTest(expected, source) } @Test def tokenizerParseNumbers(): Unit = { abstract class NumberTest(val s: String, val result: Token) case class LongTest(override val s: String, override val result: Token) extends NumberTest(s, result) case class DoubleTest(override val s: String, override val result: Token) extends NumberTest(s, result) implicit def pair2inttest(pair: (String, Int)) = LongTest(pair._1, tokenLong(pair._2)) implicit def pair2longtest(pair: (String, Long)) = LongTest(pair._1, tokenLong(pair._2)) implicit def pair2doubletest(pair: (String, Double)) = DoubleTest(pair._1, tokenDouble(pair._2)) val tests = List[NumberTest](("1", 1), ("1.2", 1.2), ("1e6", 1e6), ("1e-6", 1e-6), ("1E-6", 1e-6), // capital E is allowed ("-1", -1), ("-1.2", -1.2)) for (t <- tests) { describeFailure(t.toString()) { val expected = List(t.result) tokenizerTest(expected, t.s) } } } @Test def commentsHandledInVariousContexts() { tokenizerTest(List(tokenString("//bar")), "\"//bar\"") tokenizerTest(List(tokenString("#bar")), "\"#bar\"") tokenizerTest(List(tokenUnquoted("bar"), tokenCommentDoubleSlash("comment")), "bar//comment") tokenizerTest(List(tokenUnquoted("bar"), tokenCommentHash("comment")), "bar#comment") tokenizerTest(List(tokenInt(10), tokenCommentDoubleSlash("comment")), "10//comment") tokenizerTest(List(tokenInt(10), tokenCommentHash("comment")), "10#comment") tokenizerTest(List(tokenDouble(3.14), tokenCommentDoubleSlash("comment")), "3.14//comment") tokenizerTest(List(tokenDouble(3.14), tokenCommentHash("comment")), "3.14#comment") // be sure we keep the newline tokenizerTest(List(tokenInt(10), tokenCommentDoubleSlash("comment"), tokenLine(1), tokenInt(12)), "10//comment\n12") tokenizerTest(List(tokenInt(10), tokenCommentHash("comment"), tokenLine(1), tokenInt(12)), "10#comment\n12") // be sure we handle multi-line comments tokenizerTest(List(tokenCommentDoubleSlash("comment"), tokenLine(1), tokenCommentDoubleSlash("comment2")), "//comment\n//comment2") tokenizerTest(List(tokenCommentHash("comment"), tokenLine(1), tokenCommentHash("comment2")), "#comment\n#comment2") tokenizerTest(List(tokenWhitespace(" "), tokenCommentDoubleSlash("comment\r"), tokenLine(1), tokenWhitespace(" "), tokenCommentDoubleSlash("comment2 "), tokenLine(2), tokenCommentDoubleSlash("comment3 "), tokenLine(3), tokenLine(4), tokenCommentDoubleSlash("comment4")), " //comment\r\n //comment2 \n//comment3 \n\n//comment4") tokenizerTest(List(tokenWhitespace(" "), tokenCommentHash("comment\r"), tokenLine(1), tokenWhitespace(" "), tokenCommentHash("comment2 "), tokenLine(2), tokenCommentHash("comment3 "), tokenLine(3), tokenLine(4), tokenCommentHash("comment4")), " #comment\r\n #comment2 \n#comment3 \n\n#comment4") } @Test def tokenizeReservedChars() { for (invalid <- "+`^?!@*&\\") { val tokenized = tokenizeAsList(invalid.toString) assertEquals(3, tokenized.size) assertEquals(Tokens.START, tokenized(0)) assertEquals(Tokens.END, tokenized(2)) val problem = tokenized(1) assertTrue("reserved char is a problem", Tokens.isProblem(problem)) if (invalid == '+') assertEquals("end of file", Tokens.getProblemWhat(problem)) else assertEquals("" + invalid, Tokens.getProblemWhat(problem)) } } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/ToyHttp.scala000066400000000000000000000112551277147274600263300ustar00rootroot00000000000000package com.typesafe.config.impl import java.net.ServerSocket import java.net.InetSocketAddress import scala.annotation.tailrec import scala.util.control.NonFatal import java.net.Socket import java.io.BufferedReader import java.io.IOException import java.io.EOFException import java.io.OutputStream import java.text.SimpleDateFormat import java.util.Locale import java.util.TimeZone import java.io.PrintWriter import java.io.OutputStreamWriter import java.nio.charset.StandardCharsets import java.util.Date // terrible http server that's good enough for our test suite final class ToyHttp(handler: ToyHttp.Request => ToyHttp.Response) { import ToyHttp.{ Request, Response } private final val serverSocket = new ServerSocket() serverSocket.bind(new InetSocketAddress("127.0.0.1", 0)) final val port = serverSocket.getLocalPort private final val thread = new Thread(new Runnable() { override def run() = mainLoop(); }) thread.setDaemon(true) thread.setName("ToyHttp") thread.start() def stop(): Unit = { try serverSocket.close() catch { case e: IOException => } try thread.interrupt() catch { case NonFatal(e) => } thread.join() } @tailrec private def mainLoop(): Unit = { val done = try { val socket = serverSocket.accept() try handleRequest(socket) catch { case _: EOFException => case e: IOException => System.err.println(s"error handling http request: ${e.getClass.getName}: ${e.getMessage} ${e.getStackTrace.mkString("\n")}") } false } catch { case e: java.net.SocketException => true } if (!done) mainLoop() } private def handleRequest(socket: Socket): Unit = { val in = socket.getInputStream val out = socket.getOutputStream try { // HTTP requests look like this: // GET /path/here HTTP/1.0 // SomeHeader: bar // OtherHeader: foo // \r\n val reader = new BufferedReader(new java.io.InputStreamReader(in)) val path = parsePath(reader) val header = parseHeader(reader) //System.err.println(s"request path '$path' headers $header") val response = handler(Request(path, header)) //System.err.println(s"response $response") sendResponse(out, response) } finally { in.close() out.close() } } private def parseHeader(reader: BufferedReader): Map[String, String] = { def readHeaders(sofar: Map[String, String]): Map[String, String] = { val line = reader.readLine() val colon = line.indexOf(':') if (colon > 0) { val name = line.substring(0, colon).toLowerCase() val value = line.substring(colon + 1).replaceAll("^[ \t]+", "") readHeaders(sofar + (name -> value)) } else { sofar } } readHeaders(Map.empty) } private def parsePath(reader: BufferedReader): String = { val methodPathProto = reader.readLine().split(" +") val method = methodPathProto(0) val path = methodPathProto(1) path } private def codeText(code: Int) = code match { case 200 => "OK" case 404 => "Not Found" case 500 => "Internal Server Error" case _ => throw new RuntimeException(s"add text for $code") } private def sendResponse(out: OutputStream, response: Response): Unit = { //val stuff = new java.io.ByteArrayOutputStream //val writer = new PrintWriter(new OutputStreamWriter(stuff, StandardCharsets.UTF_8)) val writer = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)) val dateFormat = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); writer.append(s"HTTP/1.1 ${response.code} ${codeText(response.code)}\r\n") writer.append(s"Date: ${dateFormat.format(new Date)}\r\n") writer.append(s"Content-Type: ${response.contentType}; charset=utf-8\r\n") val bytes = response.body.getBytes("UTF-8") writer.append(s"Content-Length: $bytes\r\n") writer.append("\r\n") writer.append(response.body) writer.flush() } } object ToyHttp { def apply(handler: Request => Response): ToyHttp = new ToyHttp(handler) final case class Request(path: String, headers: Map[String, String]) final case class Response(code: Int, contentType: String, body: String) } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/UnitParserTest.scala000066400000000000000000000154331277147274600276530ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ import com.typesafe.config._ import java.util.concurrent.TimeUnit class UnitParserTest extends TestUtils { @Test def parseDuration(): Unit = { val oneSecs = List("1s", "1 s", "1seconds", "1 seconds", " 1s ", " 1 s ", "1second", "1000", "1000ms", "1000 ms", "1000 milliseconds", " 1000 milliseconds ", "1000millisecond", "1000000us", "1000000 us", "1000000 microseconds", "1000000microsecond", "1000000000ns", "1000000000 ns", "1000000000 nanoseconds", "1000000000nanosecond", "0.01666666666666666666666m", "0.01666666666666666666666 minutes", "0.01666666666666666666666 minute", "0.00027777777777777777777h", "0.00027777777777777777777 hours", "0.00027777777777777777777hour", "1.1574074074074073e-05d", "1.1574074074074073e-05 days", "1.1574074074074073e-05day") val oneSecInNanos = TimeUnit.SECONDS.toNanos(1) for (s <- oneSecs) { val result = SimpleConfig.parseDuration(s, fakeOrigin(), "test") assertEquals(oneSecInNanos, result) } // bad units val e = intercept[ConfigException.BadValue] { SimpleConfig.parseDuration("100 dollars", fakeOrigin(), "test") } assertTrue(e.getMessage.contains("time unit")) // bad number val e2 = intercept[ConfigException.BadValue] { SimpleConfig.parseDuration("1 00 seconds", fakeOrigin(), "test") } assertTrue(e2.getMessage.contains("duration number")) } // https://github.com/typesafehub/config/issues/117 // this broke because "1d" is a valid double for parseDouble @Test def parseOneDayAsMilliseconds(): Unit = { val result = SimpleConfig.parseDuration("1d", fakeOrigin(), "test") val dayInNanos = TimeUnit.DAYS.toNanos(1) assertEquals("could parse 1d", dayInNanos, result) val conf = parseConfig("foo = 1d") assertEquals("could get 1d from conf as days", 1L, conf.getDuration("foo", TimeUnit.DAYS)) assertEquals("could get 1d from conf as nanos", dayInNanos, conf.getNanoseconds("foo")) assertEquals("could get 1d from conf as millis", TimeUnit.DAYS.toMillis(1), conf.getMilliseconds("foo")) } @Test def parseMemorySizeInBytes(): Unit = { def parseMem(s: String): Long = SimpleConfig.parseBytes(s, fakeOrigin(), "test") assertEquals(Long.MaxValue, parseMem(s"${Long.MaxValue} bytes")) assertEquals(Long.MinValue, parseMem(s"${Long.MinValue} bytes")) val oneMebis = List("1048576", "1048576b", "1048576bytes", "1048576byte", "1048576 b", "1048576 bytes", " 1048576 b ", " 1048576 bytes ", "1048576B", "1024k", "1024K", "1024Ki", "1024KiB", "1024 kibibytes", "1024 kibibyte", "1m", "1M", "1 M", "1Mi", "1MiB", "1 mebibytes", "1 mebibyte", "0.0009765625g", "0.0009765625G", "0.0009765625Gi", "0.0009765625GiB", "0.0009765625 gibibytes", "0.0009765625 gibibyte") for (s <- oneMebis) { val result = parseMem(s) assertEquals(1024 * 1024, result) } val oneMegas = List("1000000", "1000000b", "1000000bytes", "1000000byte", "1000000 b", "1000000 bytes", " 1000000 b ", " 1000000 bytes ", "1000000B", "1000kB", "1000 kilobytes", "1000 kilobyte", "1MB", "1 megabytes", "1 megabyte", ".001GB", ".001 gigabytes", ".001 gigabyte") for (s <- oneMegas) { val result = parseMem(s) assertEquals(1000 * 1000, result) } var result = 1024L * 1024 * 1024 for (unit <- Seq("tebi", "pebi", "exbi")) { val first = unit.substring(0, 1).toUpperCase() result = result * 1024 assertEquals(result, parseMem("1" + first)) assertEquals(result, parseMem("1" + first + "i")) assertEquals(result, parseMem("1" + first + "iB")) assertEquals(result, parseMem("1" + unit + "byte")) assertEquals(result, parseMem("1" + unit + "bytes")) } result = 1000L * 1000 * 1000 for (unit <- Seq("tera", "peta", "exa")) { val first = unit.substring(0, 1).toUpperCase() result = result * 1000 assertEquals(result, parseMem("1" + first + "B")) assertEquals(result, parseMem("1" + unit + "byte")) assertEquals(result, parseMem("1" + unit + "bytes")) } // bad units val e = intercept[ConfigException.BadValue] { SimpleConfig.parseBytes("100 dollars", fakeOrigin(), "test") } assertTrue(e.getMessage.contains("size-in-bytes unit")) // bad number val e2 = intercept[ConfigException.BadValue] { SimpleConfig.parseBytes("1 00 bytes", fakeOrigin(), "test") } assertTrue(e2.getMessage.contains("size-in-bytes number")) } // later on we'll want to check this with BigInteger version of getBytes @Test def parseHugeMemorySizes(): Unit = { def parseMem(s: String): Long = SimpleConfig.parseBytes(s, fakeOrigin(), "test") def assertOutOfRange(s: String) = { val fail = intercept[ConfigException.BadValue] { parseMem(s) } assertTrue("number was too big", fail.getMessage.contains("out of range")) } import java.math.BigInteger assertOutOfRange(s"${BigInteger.valueOf(Long.MaxValue).add(BigInteger.valueOf(1)).toString} bytes") assertOutOfRange(s"${BigInteger.valueOf(Long.MinValue).subtract(BigInteger.valueOf(1)).toString} bytes") var result = 1024L * 1024 * 1024 for (unit <- Seq("zebi", "yobi")) { val first = unit.substring(0, 1).toUpperCase() assertOutOfRange("1" + first) assertOutOfRange("1" + first + "i") assertOutOfRange("1" + first + "iB") assertOutOfRange("1" + unit + "byte") assertOutOfRange("1" + unit + "bytes") assertOutOfRange("1.1" + first) assertOutOfRange("-1" + first) } result = 1000L * 1000 * 1000 for (unit <- Seq("zetta", "yotta")) { val first = unit.substring(0, 1).toUpperCase() assertOutOfRange("1" + first + "B") assertOutOfRange("1" + unit + "byte") assertOutOfRange("1" + unit + "bytes") assertOutOfRange("1.1" + first + "B") assertOutOfRange("-1" + first + "B") } assertOutOfRange("1000 exabytes") assertOutOfRange("10000000 petabytes") } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/UtilTest.scala000066400000000000000000000064321277147274600264730ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ class UtilTest extends TestUtils { private lazy val supplementaryChars = { val sb = new java.lang.StringBuilder() val codepoints = Seq( 0x2070E, 0x20731, 0x20779, 0x20C53, 0x20C78, 0x20C96, 0x20CCF, 0x20CD5, 0x20D15, 0x20D7C) for (c <- codepoints) { sb.appendCodePoint(c) } assertTrue(sb.length() > codepoints.length) sb.toString() } @Test def unicodeTrimSupplementaryChars() { assertEquals("", ConfigImplUtil.unicodeTrim("")) assertEquals("a", ConfigImplUtil.unicodeTrim("a")) assertEquals("abc", ConfigImplUtil.unicodeTrim("abc")) assertEquals("", ConfigImplUtil.unicodeTrim(" \n \n \u00A0 ")) assertEquals(supplementaryChars, ConfigImplUtil.unicodeTrim(supplementaryChars)) val s = " \u00A0 \n " + supplementaryChars + " \n \u00A0 " val asciiTrimmed = s.trim() val unitrimmed = ConfigImplUtil.unicodeTrim(s) assertFalse(asciiTrimmed.equals(unitrimmed)) assertEquals(supplementaryChars, unitrimmed) } @Test def definitionOfWhitespace() { assertTrue(ConfigImplUtil.isWhitespace(' ')) assertTrue(ConfigImplUtil.isWhitespace('\n')) // these three are nonbreaking spaces assertTrue(ConfigImplUtil.isWhitespace('\u00A0')) assertTrue(ConfigImplUtil.isWhitespace('\u2007')) assertTrue(ConfigImplUtil.isWhitespace('\u202F')) // vertical tab, a weird one assertTrue(ConfigImplUtil.isWhitespace('\u000B')) // file separator, another weird one assertTrue(ConfigImplUtil.isWhitespace('\u001C')) } @Test def equalsThatHandlesNull() { assertTrue(ConfigImplUtil.equalsHandlingNull(null, null)) assertFalse(ConfigImplUtil.equalsHandlingNull(new Object(), null)) assertFalse(ConfigImplUtil.equalsHandlingNull(null, new Object())) assertTrue(ConfigImplUtil.equalsHandlingNull("", "")) } val lotsOfStrings = (invalidJson ++ validConf).map(_.test) private def roundtripJson(s: String) { val rendered = ConfigImplUtil.renderJsonString(s) val parsed = parseConfig("{ foo: " + rendered + "}").getString("foo") assertTrue("String round-tripped through maybe-unquoted escaping '" + s + "' " + s.length + " rendering '" + rendered + "' " + rendered.length + " parsed '" + parsed + "' " + parsed.length, s == parsed) } private def roundtripUnquoted(s: String) { val rendered = ConfigImplUtil.renderStringUnquotedIfPossible(s) val parsed = parseConfig("{ foo: " + rendered + "}").getString("foo") assertTrue("String round-tripped through maybe-unquoted escaping '" + s + "' " + s.length + " rendering '" + rendered + "' " + rendered.length + " parsed '" + parsed + "' " + parsed.length, s == parsed) } @Test def renderJsonString() { for (s <- lotsOfStrings) { roundtripJson(s) } } @Test def renderUnquotedIfPossible() { for (s <- lotsOfStrings) { roundtripUnquoted(s) } } } config-1.3.1/config/src/test/scala/com/typesafe/config/impl/ValidationTest.scala000066400000000000000000000112361277147274600276460ustar00rootroot00000000000000/** * Copyright (C) 2011 Typesafe Inc. */ package com.typesafe.config.impl import org.junit.Assert._ import org.junit._ import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigException import scala.collection.JavaConverters._ import scala.io.Source class ValidationTest extends TestUtils { @Test def validation() { val reference = ConfigFactory.parseFile(resourceFile("validate-reference.conf"), ConfigParseOptions.defaults()) val conf = ConfigFactory.parseFile(resourceFile("validate-invalid.conf"), ConfigParseOptions.defaults()) val e = intercept[ConfigException.ValidationFailed] { conf.checkValid(reference) } val expecteds = Seq( Missing("willBeMissing", 1, "number"), WrongType("int3", 7, "number", "object"), WrongType("float2", 9, "number", "boolean"), WrongType("float3", 10, "number", "list"), WrongType("bool1", 11, "boolean", "number"), WrongType("bool3", 13, "boolean", "object"), Missing("object1.a", 17, "string"), WrongType("object2", 18, "object", "list"), WrongType("object3", 19, "object", "number"), WrongElementType("array3", 22, "boolean", "object"), WrongElementType("array4", 23, "object", "number"), WrongType("array5", 24, "list", "number"), WrongType("a.b.c.d.e.f.g", 28, "boolean", "number"), Missing("a.b.c.d.e.f.j", 28, "boolean"), WrongType("a.b.c.d.e.f.i", 30, "boolean", "list")) checkValidationException(e, expecteds) } @Test def validationWithRoot() { val objectWithB = parseObject("""{ b : c }""") val reference = ConfigFactory.parseFile(resourceFile("validate-reference.conf"), ConfigParseOptions.defaults()).withFallback(objectWithB) val conf = ConfigFactory.parseFile(resourceFile("validate-invalid.conf"), ConfigParseOptions.defaults()) val e = intercept[ConfigException.ValidationFailed] { conf.checkValid(reference, "a", "b") } val expecteds = Seq( Missing("b", 1, "string"), WrongType("a.b.c.d.e.f.g", 28, "boolean", "number"), Missing("a.b.c.d.e.f.j", 28, "boolean"), WrongType("a.b.c.d.e.f.i", 30, "boolean", "list")) checkValidationException(e, expecteds) } @Test def validationCatchesUnresolved() { val reference = parseConfig("""{ a : 2 }""") val conf = parseConfig("""{ b : ${c}, c : 42 }""") val e = intercept[ConfigException.NotResolved] { conf.checkValid(reference) } assertTrue("expected different message, got: " + e.getMessage, e.getMessage.contains("resolve")) } @Test def validationCatchesListOverriddenWithNumber() { val reference = parseConfig("""{ a : [{},{},{}] }""") val conf = parseConfig("""{ a : 42 }""") val e = intercept[ConfigException.ValidationFailed] { conf.checkValid(reference) } val expecteds = Seq(WrongType("a", 1, "list", "number")) checkValidationException(e, expecteds) } @Test def validationCatchesListOverriddenWithDifferentList() { val reference = parseConfig("""{ a : [true,false,false] }""") val conf = parseConfig("""{ a : [42,43] }""") val e = intercept[ConfigException.ValidationFailed] { conf.checkValid(reference) } val expecteds = Seq(WrongElementType("a", 1, "boolean", "number")) checkValidationException(e, expecteds) } @Test def validationAllowsListOverriddenWithSameTypeList() { val reference = parseConfig("""{ a : [1,2,3] }""") val conf = parseConfig("""{ a : [4,5] }""") conf.checkValid(reference) } @Test def validationCatchesListOverriddenWithNoIndexesObject() { val reference = parseConfig("""{ a : [1,2,3] }""") val conf = parseConfig("""{ a : { notANumber: foo } }""") val e = intercept[ConfigException.ValidationFailed] { conf.checkValid(reference) } val expecteds = Seq(WrongType("a", 1, "list", "object")) checkValidationException(e, expecteds) } @Test def validationAllowsListOverriddenWithIndexedObject() { val reference = parseConfig("""{ a : [a,b,c] }""") val conf = parseConfig("""{ a : { "0" : x, "1" : y } }""") conf.checkValid(reference) assertEquals("got the sequence from overriding list with indexed object", Seq("x", "y"), conf.getStringList("a").asScala) } } config-1.3.1/config/src/test/scala/equiv03/000077500000000000000000000000001277147274600203745ustar00rootroot00000000000000config-1.3.1/config/src/test/scala/equiv03/SomethingInEquiv03.java000066400000000000000000000001761277147274600246440ustar00rootroot00000000000000package equiv03; /** This is to test loading resources relative to this class */ public final class SomethingInEquiv03 { } config-1.3.1/examples/000077500000000000000000000000001277147274600146205ustar00rootroot00000000000000config-1.3.1/examples/java/000077500000000000000000000000001277147274600155415ustar00rootroot00000000000000config-1.3.1/examples/java/complex-app/000077500000000000000000000000001277147274600177665ustar00rootroot00000000000000config-1.3.1/examples/java/complex-app/src/000077500000000000000000000000001277147274600205555ustar00rootroot00000000000000config-1.3.1/examples/java/complex-app/src/main/000077500000000000000000000000001277147274600215015ustar00rootroot00000000000000config-1.3.1/examples/java/complex-app/src/main/java/000077500000000000000000000000001277147274600224225ustar00rootroot00000000000000config-1.3.1/examples/java/complex-app/src/main/java/ComplexApp.java000066400000000000000000000062351277147274600253430ustar00rootroot00000000000000import com.typesafe.config.*; import simplelib.*; class ComplexApp { // Simple-lib is a library in this same examples/ directory. // This method demos usage of that library with the config // provided. private static void demoConfigInSimpleLib(Config config) { SimpleLibContext context = new SimpleLibContext(config); context.printSetting("simple-lib.foo"); context.printSetting("simple-lib.hello"); context.printSetting("simple-lib.whatever"); } public static void main(String[] args) { // This app is "complex" because we load multiple separate app // configs into a single JVM and we have a separately-configurable // context for simple lib. // system property overrides work, but the properties must be set // before the config lib is used (config lib will not notice changes // once it loads the properties) System.setProperty("simple-lib.whatever", "This value comes from a system property"); // ///////// // "config1" is just an example of using a file other than // application.conf Config config1 = ConfigFactory.load("complex1"); // use the config ourselves System.out.println("config1, complex-app.something=" + config1.getString("complex-app.something")); // use the config for a library demoConfigInSimpleLib(config1); // //////// // "config2" shows how to configure a library with a custom settings // subtree Config config2 = ConfigFactory.load("complex2"); // use the config ourselves System.out.println("config2, complex-app.something=" + config2.getString("complex-app.something")); // pull out complex-app.simple-lib-context and move it to // the toplevel, creating a new config suitable for our // SimpleLibContext. // The defaultOverrides() have to be put back on top of the stack so // they still override any simple-lib settings. // We fall back to config2 again to be sure we get simple-lib's // reference.conf plus any other settings we've set. You could // also just fall back to ConfigFactory.referenceConfig() if // you don't want complex2.conf settings outside of // complex-app.simple-lib-context to be used. Config simpleLibConfig2 = ConfigFactory.defaultOverrides() .withFallback(config2.getConfig("complex-app.simple-lib-context")) .withFallback(config2); demoConfigInSimpleLib(simpleLibConfig2); // //////// // Here's an illustration that simple-lib will get upset if we pass it // a bad config. In this case, we'll fail to merge the reference // config in to complex-app.simple-lib-context, so simple-lib will // point out that some settings are missing. try { demoConfigInSimpleLib(config2.getConfig("complex-app.simple-lib-context")); } catch (ConfigException.ValidationFailed e) { System.out.println("when we passed a bad config to simple-lib, it said: " + e.getMessage()); } } } config-1.3.1/examples/java/complex-app/src/main/resources/000077500000000000000000000000001277147274600235135ustar00rootroot00000000000000config-1.3.1/examples/java/complex-app/src/main/resources/complex1.conf000066400000000000000000000005051277147274600261120ustar00rootroot00000000000000# these are our own config values defined by the app complex-app { something="This value comes from complex-app's complex1.conf" } # Here we override some values used by a library simple-lib.foo="This value comes from complex-app's complex1.conf" simple-lib.whatever = "This value comes from complex-app's complex1.conf"config-1.3.1/examples/java/complex-app/src/main/resources/complex2.conf000066400000000000000000000010171277147274600261120ustar00rootroot00000000000000complex-app { something="This value comes from complex-app's complex2.conf" # here we want a simple-lib-context unique to our app # which can be custom-configured. In code, we have to # pull out this subtree and pass it to simple-lib. simple-lib-context = { simple-lib { foo="This value comes from complex-app's complex2.conf in its custom simple-lib-context" whatever = "This value comes from complex-app's complex2.conf in its custom simple-lib-context" } } } config-1.3.1/examples/java/simple-app/000077500000000000000000000000001277147274600176105ustar00rootroot00000000000000config-1.3.1/examples/java/simple-app/src/000077500000000000000000000000001277147274600203775ustar00rootroot00000000000000config-1.3.1/examples/java/simple-app/src/main/000077500000000000000000000000001277147274600213235ustar00rootroot00000000000000config-1.3.1/examples/java/simple-app/src/main/java/000077500000000000000000000000001277147274600222445ustar00rootroot00000000000000config-1.3.1/examples/java/simple-app/src/main/java/SimpleApp.java000066400000000000000000000023601277147274600250020ustar00rootroot00000000000000import com.typesafe.config.*; import simplelib.*; class SimpleApp { public static void main(String[] args) { // example of how system properties override; note this // must be set before the config lib is used System.setProperty("simple-lib.whatever", "This value comes from a system property"); // Load our own config values from the default location, // application.conf Config conf = ConfigFactory.load(); System.out.println("The answer is: " + conf.getString("simple-app.answer")); // In this simple app, we're allowing SimpleLibContext() to // use the default config in application.conf ; this is exactly // the same as passing in ConfigFactory.load() here, so we could // also write "new SimpleLibContext(conf)" and it would be the same. // (simple-lib is a library in this same examples/ directory). // The point is that SimpleLibContext defaults to ConfigFactory.load() // but also allows us to pass in our own Config. SimpleLibContext context = new SimpleLibContext(); context.printSetting("simple-lib.foo"); context.printSetting("simple-lib.hello"); context.printSetting("simple-lib.whatever"); } } config-1.3.1/examples/java/simple-app/src/main/resources/000077500000000000000000000000001277147274600233355ustar00rootroot00000000000000config-1.3.1/examples/java/simple-app/src/main/resources/application.conf000066400000000000000000000004251277147274600265100ustar00rootroot00000000000000# these are our own config values defined by the app simple-app { answer=42 } # Here we override some values used by a library simple-lib.foo="This value comes from simple-app's application.conf" simple-lib.whatever = "This value comes from simple-app's application.conf" config-1.3.1/examples/java/simple-lib/000077500000000000000000000000001277147274600175765ustar00rootroot00000000000000config-1.3.1/examples/java/simple-lib/src/000077500000000000000000000000001277147274600203655ustar00rootroot00000000000000config-1.3.1/examples/java/simple-lib/src/main/000077500000000000000000000000001277147274600213115ustar00rootroot00000000000000config-1.3.1/examples/java/simple-lib/src/main/java/000077500000000000000000000000001277147274600222325ustar00rootroot00000000000000config-1.3.1/examples/java/simple-lib/src/main/java/simplelib/000077500000000000000000000000001277147274600242125ustar00rootroot00000000000000config-1.3.1/examples/java/simple-lib/src/main/java/simplelib/SimpleLibContext.java000066400000000000000000000024271277147274600303070ustar00rootroot00000000000000package simplelib; import com.typesafe.config.*; // Whenever you write a library, allow people to supply a Config but // also default to ConfigFactory.load if they don't supply one. // Libraries generally have some kind of Context or other object // where it's convenient to place the configuration. public class SimpleLibContext { private Config config; // we have a constructor allowing the app to provide a custom Config public SimpleLibContext(Config config) { this.config = config; // This verifies that the Config is sane and has our // reference config. Importantly, we specify the "simple-lib" // path so we only validate settings that belong to this // library. Otherwise, we might throw mistaken errors about // settings we know nothing about. config.checkValid(ConfigFactory.defaultReference(), "simple-lib"); } // This uses the standard default Config, if none is provided, // which simplifies apps willing to use the defaults public SimpleLibContext() { this(ConfigFactory.load()); } // this is the amazing functionality provided by simple-lib public void printSetting(String path) { System.out.println("The setting '" + path + "' is: " + config.getString(path)); } } config-1.3.1/examples/java/simple-lib/src/main/resources/000077500000000000000000000000001277147274600233235ustar00rootroot00000000000000config-1.3.1/examples/java/simple-lib/src/main/resources/reference.conf000066400000000000000000000003201277147274600261230ustar00rootroot00000000000000simple-lib { foo = "This value comes from simple-lib's reference.conf" hello = "This value comes from simple-lib's reference.conf" whatever = "This value comes from simple-lib's reference.conf" } config-1.3.1/examples/scala/000077500000000000000000000000001277147274600157035ustar00rootroot00000000000000config-1.3.1/examples/scala/complex-app/000077500000000000000000000000001277147274600201305ustar00rootroot00000000000000config-1.3.1/examples/scala/complex-app/src/000077500000000000000000000000001277147274600207175ustar00rootroot00000000000000config-1.3.1/examples/scala/complex-app/src/main/000077500000000000000000000000001277147274600216435ustar00rootroot00000000000000config-1.3.1/examples/scala/complex-app/src/main/resources/000077500000000000000000000000001277147274600236555ustar00rootroot00000000000000config-1.3.1/examples/scala/complex-app/src/main/resources/complex1.conf000066400000000000000000000005051277147274600262540ustar00rootroot00000000000000# these are our own config values defined by the app complex-app { something="This value comes from complex-app's complex1.conf" } # Here we override some values used by a library simple-lib.foo="This value comes from complex-app's complex1.conf" simple-lib.whatever = "This value comes from complex-app's complex1.conf"config-1.3.1/examples/scala/complex-app/src/main/resources/complex2.conf000066400000000000000000000010171277147274600262540ustar00rootroot00000000000000complex-app { something="This value comes from complex-app's complex2.conf" # here we want a simple-lib-context unique to our app # which can be custom-configured. In code, we have to # pull out this subtree and pass it to simple-lib. simple-lib-context = { simple-lib { foo="This value comes from complex-app's complex2.conf in its custom simple-lib-context" whatever = "This value comes from complex-app's complex2.conf in its custom simple-lib-context" } } } config-1.3.1/examples/scala/complex-app/src/main/scala/000077500000000000000000000000001277147274600227265ustar00rootroot00000000000000config-1.3.1/examples/scala/complex-app/src/main/scala/ComplexApp.scala000066400000000000000000000054251277147274600260110ustar00rootroot00000000000000import com.typesafe.config._ import simplelib._ object ComplexApp extends App { // This app is "complex" because we load multiple separate app // configs into a single JVM and we have a separately-configurable // context for simple lib. // using a custom Config with the simple-lib library // (simple-lib is a library in this same examples/ directory) def demoConfigInSimpleLib(config: Config) { val context = new SimpleLibContext(config) context.printSetting("simple-lib.foo") context.printSetting("simple-lib.hello") context.printSetting("simple-lib.whatever") } // system property overrides work, but the properties must be set // before the config lib is used (config lib will not notice changes // once it loads the properties) System.setProperty("simple-lib.whatever", "This value comes from a system property") /////////// // "config1" is just an example of using a file other than application.conf val config1 = ConfigFactory.load("complex1") // use the config ourselves println("config1, complex-app.something=" + config1.getString("complex-app.something")) // use the config for a library demoConfigInSimpleLib(config1) ////////// // "config2" shows how to configure a library with a custom settings subtree val config2 = ConfigFactory.load("complex2"); // use the config ourselves println("config2, complex-app.something=" + config2.getString("complex-app.something")) // pull out complex-app.simple-lib-context and move it to // the toplevel, creating a new config suitable for our SimpleLibContext. // The defaultOverrides() have to be put back on top of the stack so // they still override any simple-lib settings. // We fall back to config2 again to be sure we get simple-lib's // reference.conf plus any other settings we've set. You could // also just fall back to ConfigFactory.referenceConfig() if // you don't want complex2.conf settings outside of // complex-app.simple-lib-context to be used. val simpleLibConfig2 = ConfigFactory.defaultOverrides() .withFallback(config2.getConfig("complex-app.simple-lib-context")) .withFallback(config2) demoConfigInSimpleLib(simpleLibConfig2) ////////// // Here's an illustration that simple-lib will get upset if we pass it // a bad config. In this case, we'll fail to merge the reference // config in to complex-app.simple-lib-context, so simple-lib will // point out that some settings are missing. try { demoConfigInSimpleLib(config2.getConfig("complex-app.simple-lib-context")) } catch { case e: ConfigException.ValidationFailed => println("when we passed a bad config to simple-lib, it said: " + e.getMessage) } } config-1.3.1/examples/scala/simple-app/000077500000000000000000000000001277147274600177525ustar00rootroot00000000000000config-1.3.1/examples/scala/simple-app/src/000077500000000000000000000000001277147274600205415ustar00rootroot00000000000000config-1.3.1/examples/scala/simple-app/src/main/000077500000000000000000000000001277147274600214655ustar00rootroot00000000000000config-1.3.1/examples/scala/simple-app/src/main/resources/000077500000000000000000000000001277147274600234775ustar00rootroot00000000000000config-1.3.1/examples/scala/simple-app/src/main/resources/application.conf000066400000000000000000000004251277147274600266520ustar00rootroot00000000000000# these are our own config values defined by the app simple-app { answer=42 } # Here we override some values used by a library simple-lib.foo="This value comes from simple-app's application.conf" simple-lib.whatever = "This value comes from simple-app's application.conf" config-1.3.1/examples/scala/simple-app/src/main/scala/000077500000000000000000000000001277147274600225505ustar00rootroot00000000000000config-1.3.1/examples/scala/simple-app/src/main/scala/SimpleApp.scala000066400000000000000000000021271277147274600254510ustar00rootroot00000000000000import com.typesafe.config._ import simplelib._ object SimpleApp extends App { // example of how system properties override; note this // must be set before the config lib is used System.setProperty("simple-lib.whatever", "This value comes from a system property") // Load our own config values from the default location, application.conf val conf = ConfigFactory.load() println("The answer is: " + conf.getString("simple-app.answer")) // In this simple app, we're allowing SimpleLibContext() to // use the default config in application.conf ; this is exactly // the same as passing in ConfigFactory.load() here, so we could // also write "new SimpleLibContext(conf)" and it would be the same. // (simple-lib is a library in this same examples/ directory). // The point is that SimpleLibContext defaults to ConfigFactory.load() // but also allows us to pass in our own Config. val context = new SimpleLibContext() context.printSetting("simple-lib.foo") context.printSetting("simple-lib.hello") context.printSetting("simple-lib.whatever") } config-1.3.1/examples/scala/simple-lib/000077500000000000000000000000001277147274600177405ustar00rootroot00000000000000config-1.3.1/examples/scala/simple-lib/src/000077500000000000000000000000001277147274600205275ustar00rootroot00000000000000config-1.3.1/examples/scala/simple-lib/src/main/000077500000000000000000000000001277147274600214535ustar00rootroot00000000000000config-1.3.1/examples/scala/simple-lib/src/main/resources/000077500000000000000000000000001277147274600234655ustar00rootroot00000000000000config-1.3.1/examples/scala/simple-lib/src/main/resources/reference.conf000066400000000000000000000003201277147274600262650ustar00rootroot00000000000000simple-lib { foo = "This value comes from simple-lib's reference.conf" hello = "This value comes from simple-lib's reference.conf" whatever = "This value comes from simple-lib's reference.conf" } config-1.3.1/examples/scala/simple-lib/src/main/scala/000077500000000000000000000000001277147274600225365ustar00rootroot00000000000000config-1.3.1/examples/scala/simple-lib/src/main/scala/simplelib/000077500000000000000000000000001277147274600245165ustar00rootroot00000000000000config-1.3.1/examples/scala/simple-lib/src/main/scala/simplelib/SimpleLib.scala000066400000000000000000000045351277147274600274120ustar00rootroot00000000000000package simplelib import com.typesafe.config._ // Whenever you write a library, allow people to supply a Config but // also default to ConfigFactory.load if they don't supply one. // Libraries generally have some kind of Context or other object // where it's convenient to place the configuration. // we have a constructor allowing the app to provide a custom Config class SimpleLibContext(config: Config) { // This verifies that the Config is sane and has our // reference config. Importantly, we specify the "simple-lib" // path so we only validate settings that belong to this // library. Otherwise, we might throw mistaken errors about // settings we know nothing about. config.checkValid(ConfigFactory.defaultReference(), "simple-lib") // This uses the standard default Config, if none is provided, // which simplifies apps willing to use the defaults def this() { this(ConfigFactory.load()) } // this is the amazing functionality provided by simple-lib def printSetting(path: String) { println("The setting '" + path + "' is: " + config.getString(path)) } } // Here is an OPTIONAL alternative way to access settings, which // has the advantage of validating fields on startup and avoiding // typos. This is redundant with the SimpleLibContext above, // in fact we'll show a settings-based context below. class SimpleLibSettings(config: Config) { // checkValid(), just as in the plain SimpleLibContext config.checkValid(ConfigFactory.defaultReference(), "simple-lib") // note that these fields are NOT lazy, because if we're going to // get any exceptions, we want to get them on startup. val foo = config.getString("simple-lib.foo") val hello = config.getString("simple-lib.hello") val whatever = config.getString("simple-lib.whatever") } // This is a different way to do SimpleLibContext, using the // SimpleLibSettings class to encapsulate and validate your // settings on startup class SimpleLibContext2(config: Config) { val settings = new SimpleLibSettings(config) def this() { this(ConfigFactory.load()) } // this is the amazing functionality provided by simple-lib with a Settings class def printSettings() { println("foo=" + settings.foo) println("hello=" + settings.hello) println("whatever=" + settings.whatever) } } config-1.3.1/project/000077500000000000000000000000001277147274600144505ustar00rootroot00000000000000config-1.3.1/project/Build.scala000066400000000000000000000122171277147274600165170ustar00rootroot00000000000000import sbt._ import Keys._ import com.etsy.sbt.checkstyle.CheckstylePlugin.autoImport._ import com.typesafe.sbt.osgi.SbtOsgi import com.typesafe.sbt.osgi.SbtOsgi.autoImport._ import com.typesafe.sbt.JavaVersionCheckPlugin.autoImport._ object ConfigBuild extends Build { val unpublished = Seq( // no artifacts in this project publishArtifact := false, // make-pom has a more specific publishArtifact setting already // so needs specific override publishArtifact in makePom := false, // no docs to publish publishArtifact in packageDoc := false, // can't seem to get rid of ivy files except by no-op'ing the entire publish task publish := {}, publishLocal := {} ) object sonatype extends PublishToSonatype { def projectUrl = "https://github.com/typesafehub/config" def developerId = "havocp" def developerName = "Havoc Pennington" def developerUrl = "http://ometer.com/" def scmUrl = "git://github.com/typesafehub/config.git" } override val settings = super.settings ++ Seq(isSnapshot <<= isSnapshot or version(_ endsWith "-SNAPSHOT")) lazy val commonSettings: Seq[Setting[_]] = unpublished ++ Seq(javaVersionPrefix in javaVersionCheck := None) lazy val rootSettings: Seq[Setting[_]] = commonSettings ++ Seq(aggregate in doc := false, doc := (doc in (configLib, Compile)).value, aggregate in packageDoc := false, packageDoc := (packageDoc in (configLib, Compile)).value, aggregate in checkstyle := false, checkstyle := (checkstyle in (configLib, Compile)).value) lazy val root = Project(id = "root", base = file("."), settings = rootSettings) aggregate(testLib, configLib, simpleLibScala, simpleAppScala, complexAppScala, simpleLibJava, simpleAppJava, complexAppJava) lazy val configLib = Project(id = "config", base = file("config"), settings = sonatype.settings ++ osgiSettings ++ Seq( OsgiKeys.exportPackage := Seq("com.typesafe.config", "com.typesafe.config.impl"), publish := sys.error("use publishSigned instead of plain publish"), publishLocal := sys.error("use publishLocalSigned instead of plain publishLocal") )).enablePlugins(SbtOsgi) dependsOn testLib % "test->test" def project(id: String, base: File) = Project(id, base, settings = commonSettings) lazy val testLib = project("config-test-lib", file("test-lib")) lazy val simpleLibScala = project("config-simple-lib-scala", file("examples/scala/simple-lib")) dependsOn configLib lazy val simpleAppScala = project("config-simple-app-scala", file("examples/scala/simple-app")) dependsOn simpleLibScala lazy val complexAppScala = project("config-complex-app-scala", file("examples/scala/complex-app")) dependsOn simpleLibScala lazy val simpleLibJava = project("config-simple-lib-java", file("examples/java/simple-lib")) dependsOn configLib lazy val simpleAppJava = project("config-simple-app-java", file("examples/java/simple-app")) dependsOn simpleLibJava lazy val complexAppJava = project("config-complex-app-java", file("examples/java/complex-app")) dependsOn simpleLibJava } // from https://raw.github.com/paulp/scala-improving/master/project/PublishToSonatype.scala abstract class PublishToSonatype { val ossSnapshots = "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/" val ossStaging = "Sonatype OSS Staging" at "https://oss.sonatype.org/service/local/staging/deploy/maven2/" def projectUrl: String def developerId: String def developerName: String def developerUrl: String def licenseName = "Apache License, Version 2.0" def licenseUrl = "http://www.apache.org/licenses/LICENSE-2.0" def licenseDistribution = "repo" def scmUrl: String def scmConnection = "scm:git:" + scmUrl def generatePomExtra: xml.NodeSeq = { { projectUrl } { licenseName } { licenseUrl } { licenseDistribution } { scmUrl } { scmConnection } { developerId } { developerName } { developerUrl } } def settings: Seq[Setting[_]] = Seq( publishMavenStyle := true, publishTo <<= isSnapshot { (snapshot) => Some(if (snapshot) ossSnapshots else ossStaging) }, publishArtifact in Test := false, pomIncludeRepository := (_ => false), pomExtra := generatePomExtra ) } config-1.3.1/project/build.properties000066400000000000000000000000241277147274600176610ustar00rootroot00000000000000sbt.version=0.13.11 config-1.3.1/project/linksource.scala000066400000000000000000000036371277147274600176440ustar00rootroot00000000000000import sbt._ import Keys._ import plugins.JvmPlugin object LinkSourcePlugin extends AutoPlugin { object autoImport { lazy val javadocSourceBaseUrl = settingKey[Option[String]]("base URL (no trailing slash) for source code") } import autoImport._ override def trigger = allRequirements override def requires = JvmPlugin override lazy val projectSettings = Seq( javadocSourceBaseUrl := None, javacOptions in (Compile, doc) := { val old = (javacOptions in doc).value if (old.contains("-linksource")) old else "-linksource" +: old }, (doc in Compile) := { val result = (doc in Compile).value val dir = (target in doc in Compile).value javadocSourceBaseUrl.value.foreach { url => rewriteSourceLinks(dir, url, streams.value.log) } result } ) private def rewriteSourceLinks(dir: File, sourceBaseUrl: String, log: Logger): Unit = { // Convert to // "https://github.com/typesafehub/config/blob/v1.2.1/config/src/main/java/com/typesafe/config/Config.java#L165" // in all .html files found underneath dir val origRegex = "href=\".*src-html/([^\"]+)\"".r def listFiles(d: File): Seq[File] = IO.listFiles(d).toSeq.flatMap { f => if (f.isDirectory) listFiles(f) else Seq(f) } val htmlFiles = listFiles(dir).filter(_.getName.endsWith(".html")) for (f <- htmlFiles) { val content = IO.read(f) val changed = origRegex.replaceAllIn(content, { m: scala.util.matching.Regex.Match => val oldFileLine = m.group(1) val fileLine = oldFileLine.replace("line.", "L").replace(".html", ".java") s""" href="$sourceBaseUrl/$fileLine" target="_blank" """ }) if (content != changed) { log.info(s"Replacing source links in $f") IO.write(f, changed) } } } } config-1.3.1/project/plugins.sbt000066400000000000000000000007141277147274600166450ustar00rootroot00000000000000addSbtPlugin("de.johoop" % "findbugs4sbt" % "1.4.0") addSbtPlugin("de.johoop" % "jacoco4sbt" % "2.1.6") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") addSbtPlugin("com.typesafe.sbt" % "sbt-osgi" % "0.8.0") addSbtPlugin("com.typesafe.sbt" % "sbt-scalariform" % "1.2.1") addSbtPlugin("com.etsy" % "sbt-checkstyle-plugin" % "3.0.0") addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.5") addSbtPlugin("com.typesafe.sbt" % "sbt-javaversioncheck" % "0.1.0") config-1.3.1/test-lib/000077500000000000000000000000001277147274600145255ustar00rootroot00000000000000config-1.3.1/test-lib/src/000077500000000000000000000000001277147274600153145ustar00rootroot00000000000000config-1.3.1/test-lib/src/test/000077500000000000000000000000001277147274600162735ustar00rootroot00000000000000config-1.3.1/test-lib/src/test/resources/000077500000000000000000000000001277147274600203055ustar00rootroot00000000000000config-1.3.1/test-lib/src/test/resources/test-lib.conf000066400000000000000000000003461277147274600227020ustar00rootroot00000000000000# don't copy anything about how this is done; look at the # examples/ directory and the docs for things that should be # copied. this is weird test suite stuff. test-lib { description = This is to test classpath searches. } config-1.3.1/test-lib/src/test/resources/test01.conf000066400000000000000000000007511277147274600222770ustar00rootroot00000000000000# don't copy anything about how this is done; look at the # examples/ directory and the docs for things that should be # copied. this is weird test suite stuff. ## Here we are testing that this file test01.conf is merged ## properly with another test01.conf on the classpath test-lib { fromTestLib = true } ints { ## this would override the other test01.conf if ## we merged this file first; the test suite ## is supposed to check this key's value. fortyTwo = 900 }