mirror of
https://github.com/lightbend/config.git
synced 2025-01-15 14:50:23 +08:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
3d44d90d4d
23
HOCON.md
23
HOCON.md
@ -30,7 +30,7 @@
|
||||
- [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: missing files and required files](#include-semantics-missing-files-and-required-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)
|
||||
@ -47,6 +47,7 @@
|
||||
- [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)
|
||||
- [Note on Windows and case sensitivity of environment variables](#note-on-windows-and-case-sensitivity-of-environment-variables)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
@ -1295,7 +1296,27 @@ must be lowercase. Exactly these strings are supported:
|
||||
- `m`, `minute`, `minutes`
|
||||
- `h`, `hour`, `hours`
|
||||
- `d`, `day`, `days`
|
||||
|
||||
### Period Format
|
||||
|
||||
Similar to the `getDuration()` method, there is a `getPeriod()` method
|
||||
available for getting time units as a `java.time.Period`.
|
||||
|
||||
This can use the general "units format" described above; bare
|
||||
numbers are taken to be in days, while strings are
|
||||
parsed as a number plus an optional unit string.
|
||||
|
||||
The supported unit strings for period are case sensitive and
|
||||
must be lowercase. Exactly these strings are supported:
|
||||
|
||||
- `d`, `day`, `days`
|
||||
- `w`, `week`, `weeks`
|
||||
- `m`, `mo`, `month`, `months` (note that if you are using `getTemporal()`
|
||||
which may return either a `java.time.Duration` or a `java.time.Period`
|
||||
you will want to use `mo` rather than `m` to prevent your unit being
|
||||
parsed as minutes)
|
||||
- `y`, `year`, `years`
|
||||
|
||||
### Size in bytes format
|
||||
|
||||
Implementations may wish to support a `getBytes()` returning a
|
||||
|
14
NEWS.md
14
NEWS.md
@ -1,3 +1,17 @@
|
||||
# 1.3.2: October 6, 2017
|
||||
|
||||
- environment variables are now able to be resolved to lists in
|
||||
the same fashion as system properties.
|
||||
- added `getPeriod()` which returns time units as
|
||||
`java.time.Period`. Currently supported periods are days, weeks,
|
||||
months and years. [More information here](HOCON.md#period-format).
|
||||
- `ConfigResolveOptions` now has `appendResolver(...)` which allows
|
||||
having custom behavior when unresolved substitutions are encountered
|
||||
during resolution.
|
||||
- Config Beans now support `Set` collection.
|
||||
- a few other small bugfixes. All of the fixed issues can be found
|
||||
in the [milestone page](https://github.com/typesafehub/config/milestone/1?closed=1).
|
||||
|
||||
# 1.3.1: September 24, 2016
|
||||
|
||||
- added `include required("foo")` syntax to specify includes that
|
||||
|
25
README.md
25
README.md
@ -81,11 +81,13 @@ to merge it in.
|
||||
- [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)
|
||||
- [Kotlin wrappers for the Java library](#kotlin-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)
|
||||
- [JavaScript port](#javascript-port)
|
||||
- [Linting tool](#linting-tool)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
@ -196,7 +198,7 @@ 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)
|
||||
[checkValid() method](http://typesafehub.github.io/config/latest/api/com/typesafe/config/Config.html#checkValid-com.typesafe.config.Config-java.lang.String...-)
|
||||
- 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)
|
||||
@ -251,7 +253,8 @@ 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:
|
||||
system properties can be used to force a different config source
|
||||
(e.g. from command line `-Dconfig.file=path/to/config-file`):
|
||||
|
||||
- `config.resource` specifies a resource name - not a
|
||||
basename, i.e. `application.conf` not `application`
|
||||
@ -837,16 +840,23 @@ format.
|
||||
* 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
|
||||
* config-annotation https://github.com/zhongl/config-annotation
|
||||
* PureConfig https://github.com/pureconfig/pureconfig
|
||||
* Simple Scala Config https://github.com/ElderResearch/ssc
|
||||
* konfig https://github.com/vpon/konfig
|
||||
* ScalaConfig https://github.com/andr83/scalaconfig
|
||||
* static-config https://github.com/Krever/static-config
|
||||
* validated-config https://github.com/carlpulley/validated-config
|
||||
* Cedi Config https://github.com/ccadllc/cedi-config
|
||||
* Cfg https://github.com/carueda/cfg
|
||||
|
||||
#### Clojure wrappers for the Java library
|
||||
|
||||
* beamly-core.config https://github.com/beamly/beamly-core.config
|
||||
|
||||
|
||||
#### Kotlin wrappers for the Java library
|
||||
* config4k https://github.com/config4k/config4k
|
||||
|
||||
#### Scala port
|
||||
|
||||
* SHocon https://github.com/unicredit/shocon (work with both Scala and Scala.Js)
|
||||
@ -867,7 +877,10 @@ format.
|
||||
|
||||
* https://github.com/puppetlabs/cpp-hocon
|
||||
|
||||
#### JavaScript port
|
||||
|
||||
* https://github.com/yellowblood/hocon-js (missing features, under development)
|
||||
|
||||
#### Linting tool
|
||||
|
||||
* A web based linting tool http://www.hoconlint.com/
|
||||
|
||||
|
65
build.sbt
65
build.sbt
@ -13,6 +13,58 @@ scalacOptions in GlobalScope in Test := Seq("-unchecked", "-deprecation", "-feat
|
||||
|
||||
scalaVersion in ThisBuild := "2.10.4"
|
||||
|
||||
val sonatype = new 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"
|
||||
}
|
||||
|
||||
lazy val commonSettings: Seq[Setting[_]] = Def.settings(
|
||||
unpublished,
|
||||
javaVersionPrefix in javaVersionCheck := None
|
||||
)
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.settings(
|
||||
commonSettings,
|
||||
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
|
||||
)
|
||||
.aggregate(
|
||||
testLib, configLib,
|
||||
simpleLibScala, simpleAppScala, complexAppScala,
|
||||
simpleLibJava, simpleAppJava, complexAppJava
|
||||
)
|
||||
|
||||
lazy val configLib = Project("config", file("config"))
|
||||
.settings(
|
||||
sonatype.settings,
|
||||
osgiSettings,
|
||||
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 proj(id: String, base: File) = Project(id, base) settings commonSettings
|
||||
|
||||
lazy val testLib = proj("config-test-lib", file("test-lib"))
|
||||
|
||||
lazy val simpleLibScala = proj("config-simple-lib-scala", file("examples/scala/simple-lib")) dependsOn configLib
|
||||
lazy val simpleAppScala = proj("config-simple-app-scala", file("examples/scala/simple-app")) dependsOn simpleLibScala
|
||||
lazy val complexAppScala = proj("config-complex-app-scala", file("examples/scala/complex-app")) dependsOn simpleLibScala
|
||||
|
||||
lazy val simpleLibJava = proj("config-simple-lib-java", file("examples/java/simple-lib")) dependsOn configLib
|
||||
lazy val simpleAppJava = proj("config-simple-app-java", file("examples/java/simple-app")) dependsOn simpleLibJava
|
||||
lazy val complexAppJava = proj("config-complex-app-java", file("examples/java/complex-app")) dependsOn simpleLibJava
|
||||
|
||||
useGpg := true
|
||||
|
||||
aggregate in PgpKeys.publishSigned := false
|
||||
@ -20,3 +72,16 @@ PgpKeys.publishSigned := (PgpKeys.publishSigned in configLib).value
|
||||
|
||||
aggregate in PgpKeys.publishLocalSigned := false
|
||||
PgpKeys.publishLocalSigned := (PgpKeys.publishLocalSigned in configLib).value
|
||||
|
||||
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 := {}
|
||||
)
|
||||
|
@ -14,9 +14,13 @@ ScalariformKeys.preferences in Compile := formatPrefs
|
||||
ScalariformKeys.preferences in Test := formatPrefs
|
||||
|
||||
fork in test := true
|
||||
fork in Test := true
|
||||
fork in run := true
|
||||
fork in run in Test := true
|
||||
|
||||
//env vars for tests
|
||||
envVars in Test ++= Map("testList.0" -> "0", "testList.1" -> "1")
|
||||
|
||||
autoScalaLibrary := false
|
||||
crossPaths := false
|
||||
|
||||
@ -58,7 +62,7 @@ checkstyle in Compile := {
|
||||
}
|
||||
|
||||
// add checkstyle as a dependency of doc
|
||||
doc in Compile <<= (doc in Compile).dependsOn(checkstyle in Compile)
|
||||
doc in Compile := ((doc in Compile).dependsOn(checkstyle in Compile)).value
|
||||
|
||||
findbugsSettings
|
||||
findbugsReportType := Some(ReportType.Html)
|
||||
|
@ -4,6 +4,8 @@
|
||||
package com.typesafe.config;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Period;
|
||||
import java.time.temporal.TemporalAmount;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@ -793,6 +795,44 @@ public interface Config extends ConfigMergeable {
|
||||
*/
|
||||
Duration getDuration(String path);
|
||||
|
||||
/**
|
||||
* Gets a value as a java.time.Period. If the value is
|
||||
* already a number, then it's taken as days; if it's
|
||||
* a string, it's parsed understanding units suffixes like
|
||||
* "10d" or "5w" as documented in the <a
|
||||
* href="https://github.com/typesafehub/config/blob/master/HOCON.md">the
|
||||
* spec</a>. This method never returns null.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param path
|
||||
* path expression
|
||||
* @return the period 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
|
||||
*/
|
||||
Period getPeriod(String path);
|
||||
|
||||
/**
|
||||
* Gets a value as a java.time.temporal.TemporalAmount.
|
||||
* This method will first try get get the value as a java.time.Duration, and if unsuccessful,
|
||||
* then as a java.time.Period.
|
||||
* This means that values like "5m" will be parsed as 5 minutes rather than 5 months
|
||||
* @param path path expression
|
||||
* @return the temporal 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 TemporalAmount
|
||||
*/
|
||||
TemporalAmount getTemporal(String path);
|
||||
|
||||
/**
|
||||
* Gets a list value (with any element type) as a {@link ConfigList}, which
|
||||
* implements {@code java.util.List<ConfigValue>}. Throws if the path is
|
||||
|
@ -496,6 +496,7 @@ public final class ConfigFactory {
|
||||
// We rely on this having the side effect that it drops
|
||||
// all caches
|
||||
ConfigImpl.reloadSystemPropertiesConfig();
|
||||
ConfigImpl.reloadEnvVariablesConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,10 +29,13 @@ package com.typesafe.config;
|
||||
public final class ConfigResolveOptions {
|
||||
private final boolean useSystemEnvironment;
|
||||
private final boolean allowUnresolved;
|
||||
private final ConfigResolver resolver;
|
||||
|
||||
private ConfigResolveOptions(boolean useSystemEnvironment, boolean allowUnresolved) {
|
||||
private ConfigResolveOptions(boolean useSystemEnvironment, boolean allowUnresolved,
|
||||
ConfigResolver resolver) {
|
||||
this.useSystemEnvironment = useSystemEnvironment;
|
||||
this.allowUnresolved = allowUnresolved;
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,7 +45,7 @@ public final class ConfigResolveOptions {
|
||||
* @return the default resolve options
|
||||
*/
|
||||
public static ConfigResolveOptions defaults() {
|
||||
return new ConfigResolveOptions(true, false);
|
||||
return new ConfigResolveOptions(true, false, NULL_RESOLVER);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,7 +67,7 @@ public final class ConfigResolveOptions {
|
||||
* @return options with requested setting for use of environment variables
|
||||
*/
|
||||
public ConfigResolveOptions setUseSystemEnvironment(boolean value) {
|
||||
return new ConfigResolveOptions(value, allowUnresolved);
|
||||
return new ConfigResolveOptions(value, allowUnresolved, resolver);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,7 +94,55 @@ public final class ConfigResolveOptions {
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public ConfigResolveOptions setAllowUnresolved(boolean value) {
|
||||
return new ConfigResolveOptions(useSystemEnvironment, value);
|
||||
return new ConfigResolveOptions(useSystemEnvironment, value, resolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns options where the given resolver used as a fallback if a
|
||||
* reference cannot be otherwise resolved. This resolver will only be called
|
||||
* after resolution has failed to substitute with a value from within the
|
||||
* config itself and with any other resolvers that have been appended before
|
||||
* this one. Multiple resolvers can be added using,
|
||||
*
|
||||
* <pre>
|
||||
* ConfigResolveOptions options = ConfigResolveOptions.defaults()
|
||||
* .appendResolver(primary)
|
||||
* .appendResolver(secondary)
|
||||
* .appendResolver(tertiary);
|
||||
* </pre>
|
||||
*
|
||||
* With this config unresolved references will first be resolved with the
|
||||
* primary resolver, if that fails then the secondary, and finally if that
|
||||
* also fails the tertiary.
|
||||
*
|
||||
* If all fallbacks fail to return a substitution "allow unresolved"
|
||||
* determines whether resolution fails or continues.
|
||||
*`
|
||||
* @param value the resolver to fall back to
|
||||
* @return options that use the given resolver as a fallback
|
||||
* @since 1.3.2
|
||||
*/
|
||||
public ConfigResolveOptions appendResolver(ConfigResolver value) {
|
||||
if (value == null) {
|
||||
throw new ConfigException.BugOrBroken("null resolver passed to appendResolver");
|
||||
} else if (value == this.resolver) {
|
||||
return this;
|
||||
} else {
|
||||
return new ConfigResolveOptions(useSystemEnvironment, allowUnresolved,
|
||||
this.resolver.withFallback(value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resolver to use as a fallback if a substitution cannot be
|
||||
* otherwise resolved. Never returns null. This method is mostly used by the
|
||||
* config lib internally, not by applications.
|
||||
*
|
||||
* @return the non-null fallback resolver
|
||||
* @since 1.3.2
|
||||
*/
|
||||
public ConfigResolver getResolver() {
|
||||
return this.resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -104,4 +155,22 @@ public final class ConfigResolveOptions {
|
||||
public boolean getAllowUnresolved() {
|
||||
return allowUnresolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton resolver that never resolves paths.
|
||||
*/
|
||||
private static final ConfigResolver NULL_RESOLVER = new ConfigResolver() {
|
||||
|
||||
@Override
|
||||
public ConfigValue lookup(String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigResolver withFallback(ConfigResolver fallback) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
38
config/src/main/java/com/typesafe/config/ConfigResolver.java
Normal file
38
config/src/main/java/com/typesafe/config/ConfigResolver.java
Normal file
@ -0,0 +1,38 @@
|
||||
package com.typesafe.config;
|
||||
|
||||
/**
|
||||
* Implement this interface and provide an instance to
|
||||
* {@link ConfigResolveOptions#appendResolver ConfigResolveOptions.appendResolver()}
|
||||
* to provide custom behavior when unresolved substitutions are encountered
|
||||
* during resolution.
|
||||
* @since 1.3.2
|
||||
*/
|
||||
public interface ConfigResolver {
|
||||
|
||||
/**
|
||||
* Returns the value to substitute for the given unresolved path. To get the
|
||||
* components of the path use {@link ConfigUtil#splitPath(String)}. If a
|
||||
* non-null value is returned that value will be substituted, otherwise
|
||||
* resolution will continue to consider the substitution as still
|
||||
* unresolved.
|
||||
*
|
||||
* @param path the unresolved path
|
||||
* @return the value to use as a substitution or null
|
||||
*/
|
||||
public ConfigValue lookup(String path);
|
||||
|
||||
/**
|
||||
* Returns a new resolver that falls back to the given resolver if this
|
||||
* one doesn't provide a substitution itself.
|
||||
*
|
||||
* 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 resolver
|
||||
*/
|
||||
public ConfigResolver withFallback(ConfigResolver fallback);
|
||||
|
||||
}
|
@ -11,9 +11,11 @@ import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.time.Duration;
|
||||
import java.util.Set;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigObject;
|
||||
@ -160,6 +162,8 @@ public class ConfigBeanImpl {
|
||||
return config.getAnyRef(configPropName);
|
||||
} else if (parameterClass == List.class) {
|
||||
return getListValue(beanClass, parameterType, parameterClass, config, configPropName);
|
||||
} else if (parameterClass == Set.class) {
|
||||
return getSetValue(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();
|
||||
@ -186,6 +190,10 @@ public class ConfigBeanImpl {
|
||||
}
|
||||
}
|
||||
|
||||
private static Object getSetValue(Class<?> beanClass, Type parameterType, Class<?> parameterClass, Config config, String configPropName) {
|
||||
return new HashSet((List) getListValue(beanClass, parameterType, parameterClass, config, configPropName));
|
||||
}
|
||||
|
||||
private static Object getListValue(Class<?> beanClass, Type parameterType, Class<?> parameterClass, Config config, String configPropName) {
|
||||
Type elementType = ((ParameterizedType)parameterType).getActualTypeArguments()[0];
|
||||
|
||||
@ -277,7 +285,7 @@ public class ConfigBeanImpl {
|
||||
|
||||
private static boolean isOptionalProperty(Class beanClass, PropertyDescriptor beanProp) {
|
||||
Field field = getField(beanClass, beanProp.getName());
|
||||
return (field.getAnnotationsByType(Optional.class).length > 0);
|
||||
return field != null && (field.getAnnotationsByType(Optional.class).length > 0);
|
||||
}
|
||||
|
||||
private static Field getField(Class beanClass, String fieldName) {
|
||||
|
@ -335,20 +335,11 @@ public class ConfigImpl {
|
||||
}
|
||||
|
||||
private static AbstractConfigObject loadEnvVariables() {
|
||||
Map<String, String> env = System.getenv();
|
||||
Map<String, AbstractConfigValue> m = new HashMap<String, AbstractConfigValue>();
|
||||
for (Map.Entry<String, String> 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 */);
|
||||
return PropertiesParser.fromStringMap(newSimpleOrigin("env variables"), System.getenv());
|
||||
}
|
||||
|
||||
private static class EnvVariablesHolder {
|
||||
static final AbstractConfigObject envVariables = loadEnvVariables();
|
||||
static volatile AbstractConfigObject envVariables = loadEnvVariables();
|
||||
}
|
||||
|
||||
static AbstractConfigObject envVariablesAsConfigObject() {
|
||||
@ -363,6 +354,12 @@ public class ConfigImpl {
|
||||
return envVariablesAsConfigObject().toConfig();
|
||||
}
|
||||
|
||||
public static void reloadEnvVariablesConfig() {
|
||||
// ConfigFactory.invalidateCaches() relies on this having the side
|
||||
// effect that it drops all caches
|
||||
EnvVariablesHolder.envVariables = loadEnvVariables();
|
||||
}
|
||||
|
||||
public static Config defaultReference(final ClassLoader loader) {
|
||||
return computeCachedConfig(loader, "defaultReference", new Callable<Config>() {
|
||||
@Override
|
||||
|
@ -6,6 +6,8 @@ import java.util.Collections;
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigOrigin;
|
||||
import com.typesafe.config.ConfigRenderOptions;
|
||||
import com.typesafe.config.ConfigResolveOptions;
|
||||
import com.typesafe.config.ConfigValue;
|
||||
import com.typesafe.config.ConfigValueType;
|
||||
|
||||
/**
|
||||
@ -88,7 +90,8 @@ final class ConfigReference extends AbstractConfigValue implements Unmergeable {
|
||||
v = result.value;
|
||||
newContext = result.context;
|
||||
} else {
|
||||
v = null;
|
||||
ConfigValue fallback = context.options().getResolver().lookup(expr.path().render());
|
||||
v = (AbstractConfigValue) fallback;
|
||||
}
|
||||
} catch (NotPossibleToResolve e) {
|
||||
if (ConfigImpl.traceSubstitutionsEnabled())
|
||||
|
@ -56,15 +56,28 @@ final class PropertiesParser {
|
||||
|
||||
static AbstractConfigObject fromProperties(ConfigOrigin origin,
|
||||
Properties props) {
|
||||
return fromEntrySet(origin, props.entrySet());
|
||||
}
|
||||
|
||||
private static <K, V> AbstractConfigObject fromEntrySet(ConfigOrigin origin, Set<Map.Entry<K, V>> entries) {
|
||||
final Map<Path, Object> pathMap = getPathMap(entries);
|
||||
return fromPathMap(origin, pathMap, true /* from properties */);
|
||||
}
|
||||
|
||||
private static <K, V> Map<Path, Object> getPathMap(Set<Map.Entry<K, V>> entries) {
|
||||
Map<Path, Object> pathMap = new HashMap<Path, Object>();
|
||||
for (Map.Entry<Object, Object> entry : props.entrySet()) {
|
||||
for (Map.Entry<K, V> entry : entries) {
|
||||
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 */);
|
||||
return pathMap;
|
||||
}
|
||||
|
||||
static AbstractConfigObject fromStringMap(ConfigOrigin origin, Map<String, String> stringMap) {
|
||||
return fromEntrySet(origin, stringMap.entrySet());
|
||||
}
|
||||
|
||||
static AbstractConfigObject fromPathMap(ConfigOrigin origin,
|
||||
|
@ -3,8 +3,10 @@
|
||||
*/
|
||||
package com.typesafe.config.impl;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.Externalizable;
|
||||
@ -465,19 +467,23 @@ class SerializedConfigValue extends AbstractConfigValue implements Externalizabl
|
||||
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 */);
|
||||
}
|
||||
|
||||
DataInput input = fieldIn(in);
|
||||
if (code == SerializedField.ROOT_VALUE) {
|
||||
this.value = readValue(input, null /* baseOrigin */);
|
||||
} else if (code == SerializedField.ROOT_WAS_CONFIG) {
|
||||
in.readInt(); // discard length
|
||||
this.wasConfig = in.readBoolean();
|
||||
} else {
|
||||
// ignore unknown field
|
||||
skipField(in);
|
||||
this.wasConfig = input.readBoolean();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DataInput fieldIn(ObjectInput in) throws IOException {
|
||||
byte[] bytes = new byte[in.readInt()];
|
||||
in.readFully(bytes);
|
||||
return new DataInputStream(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
private static ConfigException shouldNotBeUsed() {
|
||||
return new ConfigException.BugOrBroken(SerializedConfigValue.class.getName()
|
||||
+ " should not exist outside of serialization");
|
||||
|
@ -7,7 +7,11 @@ import java.io.ObjectStreamException;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.Duration;
|
||||
import java.time.Period;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.TemporalAmount;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -322,6 +326,21 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
return Duration.ofNanos(nanos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Period getPeriod(String path){
|
||||
ConfigValue v = find(path, ConfigValueType.STRING);
|
||||
return parsePeriod((String) v.unwrapped(), v.origin(), path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemporalAmount getTemporal(String path){
|
||||
try{
|
||||
return getDuration(path);
|
||||
} catch (ConfigException.BadValue e){
|
||||
return getPeriod(path);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> List<T> getHomogeneousUnwrappedList(String path,
|
||||
ConfigValueType expected) {
|
||||
@ -583,6 +602,90 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
return s.substring(i + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a period string. If no units are specified in the string, it is
|
||||
* assumed to be in days. The returned period is in days.
|
||||
* The purpose of this function is to implement the period-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 days
|
||||
* @throws ConfigException
|
||||
* if string is invalid
|
||||
*/
|
||||
public static Period parsePeriod(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()));
|
||||
ChronoUnit units;
|
||||
|
||||
// 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 period value '" + input
|
||||
+ "'");
|
||||
|
||||
if (unitString.length() > 2 && !unitString.endsWith("s"))
|
||||
unitString = unitString + "s";
|
||||
|
||||
// note that this is deliberately case-sensitive
|
||||
if (unitString.equals("") || unitString.equals("d") || unitString.equals("days")) {
|
||||
units = ChronoUnit.DAYS;
|
||||
|
||||
} else if (unitString.equals("w") || unitString.equals("weeks")) {
|
||||
units = ChronoUnit.WEEKS;
|
||||
|
||||
} else if (unitString.equals("m") || unitString.equals("mo") || unitString.equals("months")) {
|
||||
units = ChronoUnit.MONTHS;
|
||||
|
||||
} else if (unitString.equals("y") || unitString.equals("years")) {
|
||||
units = ChronoUnit.YEARS;
|
||||
|
||||
} else {
|
||||
throw new ConfigException.BadValue(originForException,
|
||||
pathForException, "Could not parse time unit '"
|
||||
+ originalUnitString
|
||||
+ "' (try d, w, mo, y)");
|
||||
}
|
||||
|
||||
try {
|
||||
return periodOf(Integer.parseInt(numberString), units);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ConfigException.BadValue(originForException,
|
||||
pathForException, "Could not parse duration number '"
|
||||
+ numberString + "'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static Period periodOf(int n, ChronoUnit unit){
|
||||
if(unit.isTimeBased()){
|
||||
throw new DateTimeException(unit + " cannot be converted to a java.time.Period");
|
||||
}
|
||||
|
||||
switch (unit){
|
||||
case DAYS:
|
||||
return Period.ofDays(n);
|
||||
case WEEKS:
|
||||
return Period.ofWeeks(n);
|
||||
case MONTHS:
|
||||
return Period.ofMonths(n);
|
||||
case YEARS:
|
||||
return Period.ofYears(n);
|
||||
default:
|
||||
throw new DateTimeException(unit + " cannot be converted to a java.time.Period");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -0,0 +1,24 @@
|
||||
package beanconfig;
|
||||
|
||||
public class DifferentFieldNameFromAccessorsConfig {
|
||||
|
||||
private String customStringField;
|
||||
private Long number;
|
||||
|
||||
|
||||
public String getStringField() {
|
||||
return customStringField;
|
||||
}
|
||||
|
||||
public void setStringField(String stringField) {
|
||||
this.customStringField = stringField;
|
||||
}
|
||||
|
||||
public Long getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(Long number) {
|
||||
this.number = number;
|
||||
}
|
||||
}
|
139
config/src/test/java/beanconfig/SetsConfig.java
Normal file
139
config/src/test/java/beanconfig/SetsConfig.java
Normal file
@ -0,0 +1,139 @@
|
||||
package beanconfig;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigMemorySize;
|
||||
import com.typesafe.config.ConfigObject;
|
||||
import com.typesafe.config.ConfigValue;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Set;
|
||||
|
||||
public class SetsConfig {
|
||||
|
||||
Set<Integer> empty;
|
||||
Set<Integer> ofInt;
|
||||
Set<String> ofString;
|
||||
Set<Double> ofDouble;
|
||||
Set<Long> ofLong;
|
||||
Set<Object> ofNull;
|
||||
Set<Boolean> ofBoolean;
|
||||
Set<Object> ofObject;
|
||||
Set<Config> ofConfig;
|
||||
Set<ConfigObject> ofConfigObject;
|
||||
Set<ConfigValue> ofConfigValue;
|
||||
Set<Duration> ofDuration;
|
||||
Set<ConfigMemorySize> ofMemorySize;
|
||||
Set<StringsConfig> ofStringBean;
|
||||
|
||||
public Set<Integer> getEmpty() {
|
||||
return empty;
|
||||
}
|
||||
|
||||
public void setEmpty(Set<Integer> empty) {
|
||||
this.empty = empty;
|
||||
}
|
||||
|
||||
public Set<Integer> getOfInt() {
|
||||
return ofInt;
|
||||
}
|
||||
|
||||
public void setOfInt(Set<Integer> ofInt) {
|
||||
this.ofInt = ofInt;
|
||||
}
|
||||
|
||||
public Set<String> getOfString() {
|
||||
return ofString;
|
||||
}
|
||||
|
||||
public void setOfString(Set<String> ofString) {
|
||||
this.ofString = ofString;
|
||||
}
|
||||
|
||||
public Set<Double> getOfDouble() {
|
||||
return ofDouble;
|
||||
}
|
||||
|
||||
public void setOfDouble(Set<Double> ofDouble) {
|
||||
this.ofDouble = ofDouble;
|
||||
}
|
||||
|
||||
public Set<Object> getOfNull() {
|
||||
return ofNull;
|
||||
}
|
||||
|
||||
public void setOfNull(Set<Object> ofNull) {
|
||||
this.ofNull = ofNull;
|
||||
}
|
||||
|
||||
public Set<Boolean> getOfBoolean() {
|
||||
return ofBoolean;
|
||||
}
|
||||
|
||||
public void setOfBoolean(Set<Boolean> ofBoolean) {
|
||||
this.ofBoolean = ofBoolean;
|
||||
}
|
||||
|
||||
public Set<Object> getOfObject() {
|
||||
return ofObject;
|
||||
}
|
||||
|
||||
public void setOfObject(Set<Object> ofObject) {
|
||||
this.ofObject = ofObject;
|
||||
}
|
||||
|
||||
public Set<Long> getOfLong() {
|
||||
return ofLong;
|
||||
}
|
||||
|
||||
public void setOfLong(Set<Long> ofLong) {
|
||||
this.ofLong = ofLong;
|
||||
}
|
||||
|
||||
public Set<Config> getOfConfig() {
|
||||
return ofConfig;
|
||||
}
|
||||
|
||||
public void setOfConfig(Set<Config> ofConfig) {
|
||||
this.ofConfig = ofConfig;
|
||||
}
|
||||
|
||||
public Set<ConfigObject> getOfConfigObject() {
|
||||
return ofConfigObject;
|
||||
}
|
||||
|
||||
public void setOfConfigObject(Set<ConfigObject> ofConfigObject) {
|
||||
this.ofConfigObject = ofConfigObject;
|
||||
}
|
||||
|
||||
public Set<ConfigValue> getOfConfigValue() {
|
||||
return ofConfigValue;
|
||||
}
|
||||
|
||||
public void setOfConfigValue(Set<ConfigValue> ofConfigValue) {
|
||||
this.ofConfigValue = ofConfigValue;
|
||||
}
|
||||
|
||||
public Set<Duration> getOfDuration() {
|
||||
return ofDuration;
|
||||
}
|
||||
|
||||
public void setOfDuration(Set<Duration> ofDuration) {
|
||||
this.ofDuration = ofDuration;
|
||||
}
|
||||
|
||||
public Set<ConfigMemorySize> getOfMemorySize() {
|
||||
return ofMemorySize;
|
||||
}
|
||||
|
||||
public void setOfMemorySize(Set<ConfigMemorySize> ofMemorySize) {
|
||||
this.ofMemorySize = ofMemorySize;
|
||||
}
|
||||
|
||||
public Set<StringsConfig> getOfStringBean() {
|
||||
return ofStringBean;
|
||||
}
|
||||
|
||||
public void setOfStringBean(Set<StringsConfig> ofStringBean) {
|
||||
this.ofStringBean = ofStringBean;
|
||||
}
|
||||
}
|
@ -101,5 +101,31 @@
|
||||
"valueObject": {
|
||||
"mandatoryValue": "notNull"
|
||||
}
|
||||
},
|
||||
"sets" : {
|
||||
"empty" : [],
|
||||
"ofInt" : [1, 2, 3, 2, 3],
|
||||
"ofString" : [ ${strings.a}, ${strings.b}, ${strings.c} ],
|
||||
"of-double" : [3.14, 4.14, 4.14, 5.14],
|
||||
"of-long" : { "1" : 32, "2" : 42, "3" : 52 }, // object-to-list conversion
|
||||
"ofNull" : [null, null, null],
|
||||
"ofBoolean" : [true, false, 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,14 @@
|
||||
"minusLargeNanos" : -4878955355435272204ns
|
||||
},
|
||||
|
||||
"periods" : {
|
||||
"day" : 1d,
|
||||
"dayAsNumber": 2,
|
||||
"week": 3 weeks,
|
||||
"month": 5 mo,
|
||||
"year": 8y
|
||||
},
|
||||
|
||||
"memsizes" : {
|
||||
"meg" : 1M,
|
||||
"megsList" : [1M, 1024K, 1048576],
|
||||
|
@ -132,6 +132,39 @@ class ConfigBeanFactoryTest extends TestUtils {
|
||||
assertEquals(List(stringsConfigOne, stringsConfigTwo).asJava, beanConfig.getOfStringBean)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testCreateSet() {
|
||||
val beanConfig: SetsConfig = ConfigBeanFactory.create(loadConfig().getConfig("sets"), classOf[SetsConfig])
|
||||
assertNotNull(beanConfig)
|
||||
assertEquals(Set().asJava, beanConfig.getEmpty)
|
||||
assertEquals(Set(1, 2, 3).asJava, beanConfig.getOfInt)
|
||||
assertEquals(Set(32L, 42L, 52L).asJava, beanConfig.getOfLong)
|
||||
assertEquals(Set("a", "b", "c").asJava, beanConfig.getOfString)
|
||||
assertEquals(3, beanConfig.getOfObject.size)
|
||||
assertEquals(3, beanConfig.getOfDouble.size)
|
||||
assertEquals(3, beanConfig.getOfConfig.size)
|
||||
assertTrue(beanConfig.getOfConfig.iterator().next().isInstanceOf[Config])
|
||||
assertEquals(3, beanConfig.getOfConfigObject.size)
|
||||
assertTrue(beanConfig.getOfConfigObject.iterator().next().isInstanceOf[ConfigObject])
|
||||
assertEquals(Set(intValue(1), intValue(2), stringValue("a")),
|
||||
beanConfig.getOfConfigValue.asScala)
|
||||
assertEquals(Set(Duration.ofMillis(1), Duration.ofHours(2), Duration.ofDays(3)),
|
||||
beanConfig.getOfDuration.asScala)
|
||||
assertEquals(Set(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(Set(stringsConfigOne, stringsConfigTwo).asJava, beanConfig.getOfStringBean)
|
||||
}
|
||||
|
||||
@Test
|
||||
def testCreateDuration() {
|
||||
val beanConfig: DurationsConfig = ConfigBeanFactory.create(loadConfig().getConfig("durations"), classOf[DurationsConfig])
|
||||
@ -237,6 +270,14 @@ class ConfigBeanFactoryTest extends TestUtils {
|
||||
assertTrue("error about the right property", e.getMessage.contains("'map'"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def testDifferentFieldNameFromAccessors(): Unit = {
|
||||
val e = intercept[ConfigException.ValidationFailed] {
|
||||
ConfigBeanFactory.create(ConfigFactory.empty(), classOf[DifferentFieldNameFromAccessorsConfig])
|
||||
}
|
||||
assertTrue("only one missing value error", e.getMessage.contains("No setting"))
|
||||
}
|
||||
|
||||
private def loadConfig(): Config = {
|
||||
val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/beanconfig01.conf")
|
||||
try {
|
||||
|
@ -292,7 +292,7 @@ class ConfigDocumentTest extends TestUtils {
|
||||
@Test
|
||||
def configDocumentFileParse {
|
||||
val configDocument = ConfigDocumentFactory.parseFile(resourceFile("/test03.conf"))
|
||||
val fileReader = new BufferedReader(new FileReader("config/src/test/resources/test03.conf"))
|
||||
val fileReader = new BufferedReader(new FileReader("src/test/resources/test03.conf"))
|
||||
var line = fileReader.readLine()
|
||||
val sb = new StringBuilder()
|
||||
while (line != null) {
|
||||
|
@ -10,6 +10,7 @@ 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 ConfigSubstitutionTest extends TestUtils {
|
||||
|
||||
@ -723,6 +724,35 @@ class ConfigSubstitutionTest extends TestUtils {
|
||||
checkNotSerializable(substComplexObject)
|
||||
}
|
||||
|
||||
@Test
|
||||
def resolveListFromSystemProps() {
|
||||
val props = parseObject(
|
||||
"""
|
||||
|"a": ${testList}
|
||||
""".stripMargin)
|
||||
|
||||
System.setProperty("testList.0", "0")
|
||||
System.setProperty("testList.1", "1")
|
||||
ConfigImpl.reloadSystemPropertiesConfig()
|
||||
|
||||
val resolved = resolve(ConfigFactory.systemProperties().withFallback(props).root.asInstanceOf[AbstractConfigObject])
|
||||
|
||||
assertEquals(List("0", "1"), resolved.getList("a").unwrapped().asScala)
|
||||
}
|
||||
|
||||
@Test
|
||||
def resolveListFromEnvVars() {
|
||||
val props = parseObject(
|
||||
"""
|
||||
|"a": ${testList}
|
||||
""".stripMargin)
|
||||
|
||||
//"testList.0" and "testList.1" are defined as envVars in build.sbt
|
||||
val resolved = resolve(props)
|
||||
|
||||
assertEquals(List("0", "1"), resolved.getList("a").unwrapped().asScala)
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -3,13 +3,16 @@
|
||||
*/
|
||||
package com.typesafe.config.impl
|
||||
|
||||
import java.time.temporal.{ ChronoUnit, TemporalUnit }
|
||||
|
||||
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 }
|
||||
import java.util.concurrent.TimeUnit.{ DAYS, HOURS, MICROSECONDS, MILLISECONDS, MINUTES, NANOSECONDS, SECONDS }
|
||||
|
||||
class ConfigTest extends TestUtils {
|
||||
|
||||
@ -811,6 +814,13 @@ class ConfigTest extends TestUtils {
|
||||
assertDurationAsTimeUnit(HOURS)
|
||||
assertDurationAsTimeUnit(DAYS)
|
||||
|
||||
// periods
|
||||
assertEquals(1, conf.getPeriod("periods.day").get(ChronoUnit.DAYS))
|
||||
assertEquals(2, conf.getPeriod("periods.dayAsNumber").getDays)
|
||||
assertEquals(3 * 7, conf.getTemporal("periods.week").get(ChronoUnit.DAYS))
|
||||
assertEquals(5, conf.getTemporal("periods.month").get(ChronoUnit.MONTHS))
|
||||
assertEquals(8, conf.getTemporal("periods.year").get(ChronoUnit.YEARS))
|
||||
|
||||
// should get size in bytes
|
||||
assertEquals(1024 * 1024L, conf.getBytes("memsizes.meg"))
|
||||
assertEquals(1024 * 1024L, conf.getBytes("memsizes.megAsNumber"))
|
||||
@ -1233,4 +1243,70 @@ class ConfigTest extends TestUtils {
|
||||
val resolved = unresolved.resolveWith(source)
|
||||
assertEquals(43, resolved.getInt("foo"))
|
||||
}
|
||||
|
||||
/**
|
||||
* A resolver that replaces paths that start with a particular prefix with
|
||||
* strings where that prefix has been replaced with another prefix.
|
||||
*/
|
||||
class DummyResolver(prefix: String, newPrefix: String, fallback: ConfigResolver) extends ConfigResolver {
|
||||
|
||||
override def lookup(path: String): ConfigValue = {
|
||||
if (path.startsWith(prefix))
|
||||
ConfigValueFactory.fromAnyRef(newPrefix + path.substring(prefix.length))
|
||||
else if (fallback != null)
|
||||
fallback.lookup(path)
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
override def withFallback(f: ConfigResolver): ConfigResolver = {
|
||||
if (fallback == null)
|
||||
new DummyResolver(prefix, newPrefix, f)
|
||||
else
|
||||
new DummyResolver(prefix, newPrefix, fallback.withFallback(f))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def runFallbackTest(expected: String, source: String,
|
||||
allowUnresolved: Boolean, resolvers: ConfigResolver*) = {
|
||||
val unresolved = ConfigFactory.parseString(source)
|
||||
var options = ConfigResolveOptions.defaults().setAllowUnresolved(allowUnresolved)
|
||||
for (resolver <- resolvers)
|
||||
options = options.appendResolver(resolver)
|
||||
val obj = unresolved.resolve(options).root()
|
||||
assertEquals(expected, obj.render(ConfigRenderOptions.concise().setJson(false)))
|
||||
}
|
||||
|
||||
@Test
|
||||
def resolveFallback(): Unit = {
|
||||
runFallbackTest(
|
||||
"x=a,y=b",
|
||||
"x=${a},y=${b}", false,
|
||||
new DummyResolver("", "", null))
|
||||
runFallbackTest(
|
||||
"x=\"a.b.c\",y=\"a.b.d\"",
|
||||
"x=${a.b.c},y=${a.b.d}", false,
|
||||
new DummyResolver("", "", null))
|
||||
runFallbackTest(
|
||||
"x=${a.b.c},y=${a.b.d}",
|
||||
"x=${a.b.c},y=${a.b.d}", true,
|
||||
new DummyResolver("x.", "", null))
|
||||
runFallbackTest(
|
||||
"x=${a.b.c},y=\"e.f\"",
|
||||
"x=${a.b.c},y=${d.e.f}", true,
|
||||
new DummyResolver("d.", "", null))
|
||||
runFallbackTest(
|
||||
"w=\"Y.c.d\",x=${a},y=\"X.b\",z=\"Y.c\"",
|
||||
"x=${a},y=${a.b},z=${a.b.c},w=${a.b.c.d}", true,
|
||||
new DummyResolver("a.b.", "Y.", null),
|
||||
new DummyResolver("a.", "X.", null))
|
||||
|
||||
runFallbackTest("x=${a.b.c}", "x=${a.b.c}", true, new DummyResolver("x.", "", null))
|
||||
val e = intercept[ConfigException.UnresolvedSubstitution] {
|
||||
runFallbackTest("x=${a.b.c}", "x=${a.b.c}", false, new DummyResolver("x.", "", null))
|
||||
}
|
||||
assertTrue(e.getMessage.contains("${a.b.c}"))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -279,6 +279,23 @@ class ConfigValueTest extends TestUtils {
|
||||
assertTrue(b.root.toConfig eq b)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reproduces the issue <a href=https://github.com/typesafehub/config/issues/461>#461</a>.
|
||||
* <p>
|
||||
* We use a custom de-/serializer that encodes String objects in a JDK-incompatible way. Encoding used here
|
||||
* is rather simplistic: a long indicating the length in bytes (JDK uses a variable length integer) followed
|
||||
* by the string's bytes. Running this test with the original SerializedConfigValue.readExternal()
|
||||
* implementation results in an EOFException thrown during deserialization.
|
||||
*/
|
||||
@Test
|
||||
def configConfigCustomSerializable() {
|
||||
val aMap = configMap("a" -> 1, "b" -> 2, "c" -> 3)
|
||||
val expected = new SimpleConfigObject(fakeOrigin(), aMap).toConfig
|
||||
val actual = checkSerializableWithCustomSerializer(expected)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
def configListEquality() {
|
||||
val aScalaSeq = Seq(1, 2, 3) map { intValue(_): AbstractConfigValue }
|
||||
|
@ -7,8 +7,7 @@ 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.util.{ Collections, TimeZone, TreeSet }
|
||||
import java.io.File
|
||||
import scala.collection.mutable
|
||||
import equiv03.SomethingInEquiv03
|
||||
@ -17,6 +16,15 @@ import java.net.URL
|
||||
import java.time.Duration
|
||||
|
||||
class PublicApiTest extends TestUtils {
|
||||
|
||||
@Before
|
||||
def before(): Unit = {
|
||||
// TimeZone.getDefault internally invokes System.setProperty("user.timezone", <default time zone>) and it may
|
||||
// cause flaky tests depending on tests order and jvm options. This method is invoked
|
||||
// eg. by URLConnection.getContentType (it reads headers and gets default time zone).
|
||||
TimeZone.getDefault
|
||||
}
|
||||
|
||||
@Test
|
||||
def basicLoadAndGet() {
|
||||
val conf = ConfigFactory.load("test01")
|
||||
@ -1016,8 +1024,8 @@ class PublicApiTest extends TestUtils {
|
||||
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)
|
||||
assertEquals("invalidate caches doesn't change value if no system prop changes sys", sys1, sys2)
|
||||
assertEquals("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"))
|
||||
|
@ -16,6 +16,8 @@ import java.io.ObjectOutputStream
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.NotSerializableException
|
||||
import java.io.OutputStream
|
||||
import java.io.InputStream
|
||||
import scala.annotation.tailrec
|
||||
import java.net.URL
|
||||
import java.util.Locale
|
||||
@ -721,7 +723,7 @@ abstract trait TestUtils {
|
||||
def path(elements: String*) = new Path(elements: _*)
|
||||
|
||||
val resourceDir = {
|
||||
val f = new File("config/src/test/resources")
|
||||
val f = new File("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")
|
||||
@ -871,7 +873,7 @@ abstract trait TestUtils {
|
||||
}
|
||||
|
||||
protected def withScratchDirectory[T](testcase: String)(body: File => T): Unit = {
|
||||
val target = new File("config/target")
|
||||
val target = new File("target")
|
||||
if (!target.isDirectory)
|
||||
throw new RuntimeException(s"Expecting $target to exist")
|
||||
val suffix = java.lang.Integer.toHexString(java.util.concurrent.ThreadLocalRandom.current.nextInt)
|
||||
@ -883,4 +885,32 @@ abstract trait TestUtils {
|
||||
deleteRecursive(scratch)
|
||||
}
|
||||
}
|
||||
|
||||
protected def checkSerializableWithCustomSerializer[T: Manifest](o: T): T = {
|
||||
val byteStream = new ByteArrayOutputStream()
|
||||
val objectStream = new CustomObjectOutputStream(byteStream)
|
||||
objectStream.writeObject(o)
|
||||
objectStream.close()
|
||||
val inStream = new ByteArrayInputStream(byteStream.toByteArray)
|
||||
val inObjectStream = new CustomObjectInputStream(inStream)
|
||||
val copy = inObjectStream.readObject()
|
||||
inObjectStream.close()
|
||||
copy.asInstanceOf[T]
|
||||
}
|
||||
|
||||
class CustomObjectOutputStream(out: OutputStream) extends ObjectOutputStream(out) {
|
||||
override def writeUTF(str: String): Unit = {
|
||||
val bytes = str.getBytes
|
||||
writeLong(bytes.length)
|
||||
write(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
class CustomObjectInputStream(in: InputStream) extends ObjectInputStream(in) {
|
||||
override def readUTF(): String = {
|
||||
val bytes = new Array[Byte](readLong().toByte)
|
||||
read(bytes)
|
||||
new String(bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,9 @@
|
||||
*/
|
||||
package com.typesafe.config.impl
|
||||
|
||||
import java.time.{ LocalDate, Period }
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
import org.junit.Assert._
|
||||
import org.junit._
|
||||
import com.typesafe.config._
|
||||
@ -40,6 +43,33 @@ class UnitParserTest extends TestUtils {
|
||||
assertTrue(e2.getMessage.contains("duration number"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def parsePeriod() = {
|
||||
val oneYears = List(
|
||||
"1y", "1 y", "1year", "1 years", " 1y ", " 1 y ",
|
||||
"365", "365d", "365 d", "365 days", " 365 days ", "365day",
|
||||
"12m", "12mo", "12 m", " 12 mo ", "12 months", "12month")
|
||||
val epochDate = LocalDate.ofEpochDay(0)
|
||||
val oneYear = ChronoUnit.DAYS.between(epochDate, epochDate.plus(Period.ofYears(1)))
|
||||
for (y <- oneYears) {
|
||||
val period = SimpleConfig.parsePeriod(y, fakeOrigin(), "test")
|
||||
val dayCount = ChronoUnit.DAYS.between(epochDate, epochDate.plus(period))
|
||||
assertEquals(oneYear, dayCount)
|
||||
}
|
||||
|
||||
// bad units
|
||||
val e = intercept[ConfigException.BadValue] {
|
||||
SimpleConfig.parsePeriod("100 dollars", fakeOrigin(), "test")
|
||||
}
|
||||
assertTrue(s"${e.getMessage} was not the expected error message", e.getMessage.contains("time unit"))
|
||||
|
||||
// bad number
|
||||
val e2 = intercept[ConfigException.BadValue] {
|
||||
SimpleConfig.parsePeriod("1 00 seconds", fakeOrigin(), "test")
|
||||
}
|
||||
assertTrue(s"${e2.getMessage} was not the expected error message", e2.getMessage.contains("time unit 'seconds'"))
|
||||
}
|
||||
|
||||
// https://github.com/typesafehub/config/issues/117
|
||||
// this broke because "1d" is a valid double for parseDouble
|
||||
@Test
|
||||
|
@ -1,119 +0,0 @@
|
||||
import 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 = {
|
||||
<url>{ projectUrl }</url>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>{ licenseName }</name>
|
||||
<url>{ licenseUrl }</url>
|
||||
<distribution>{ licenseDistribution }</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
<scm>
|
||||
<url>{ scmUrl }</url>
|
||||
<connection>{ scmConnection }</connection>
|
||||
</scm>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>{ developerId }</id>
|
||||
<name>{ developerName }</name>
|
||||
<url>{ developerUrl }</url>
|
||||
</developer>
|
||||
</developers>
|
||||
}
|
||||
|
||||
def settings: Seq[Setting[_]] = Seq(
|
||||
publishMavenStyle := true,
|
||||
publishTo <<= isSnapshot { (snapshot) => Some(if (snapshot) ossSnapshots else ossStaging) },
|
||||
publishArtifact in Test := false,
|
||||
pomIncludeRepository := (_ => false),
|
||||
pomExtra := generatePomExtra
|
||||
)
|
||||
}
|
48
project/PublishToSonatype.scala
Normal file
48
project/PublishToSonatype.scala
Normal file
@ -0,0 +1,48 @@
|
||||
import sbt._, Keys._
|
||||
|
||||
// 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 = {
|
||||
<url>{ projectUrl }</url>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>{ licenseName }</name>
|
||||
<url>{ licenseUrl }</url>
|
||||
<distribution>{ licenseDistribution }</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
<scm>
|
||||
<url>{ scmUrl }</url>
|
||||
<connection>{ scmConnection }</connection>
|
||||
</scm>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>{ developerId }</id>
|
||||
<name>{ developerName }</name>
|
||||
<url>{ developerUrl }</url>
|
||||
</developer>
|
||||
</developers>
|
||||
}
|
||||
|
||||
def settings: Seq[Setting[_]] = Seq(
|
||||
publishMavenStyle := true,
|
||||
publishTo := Some(if (isSnapshot.value) ossSnapshots else ossStaging),
|
||||
publishArtifact in Test := false,
|
||||
pomIncludeRepository := (_ => false),
|
||||
pomExtra := generatePomExtra
|
||||
)
|
||||
}
|
@ -1 +1 @@
|
||||
sbt.version=0.13.11
|
||||
sbt.version=0.13.16
|
||||
|
Loading…
Reference in New Issue
Block a user