/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package org.deephacks.confit.internal.core.property.typesafe.impl;
import java.io.File;
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 org.deephacks.confit.internal.core.property.typesafe.impl.SimpleIncluder.NameSource;
import org.deephacks.confit.internal.core.property.typesafe.Config;
import org.deephacks.confit.internal.core.property.typesafe.ConfigException;
import org.deephacks.confit.internal.core.property.typesafe.ConfigIncluder;
import org.deephacks.confit.internal.core.property.typesafe.ConfigObject;
import org.deephacks.confit.internal.core.property.typesafe.ConfigOrigin;
import org.deephacks.confit.internal.core.property.typesafe.ConfigParseOptions;
import org.deephacks.confit.internal.core.property.typesafe.ConfigParseable;
import org.deephacks.confit.internal.core.property.typesafe.ConfigValue;
/** This is public but is only supposed to be used by the "typesafe" package */
public class ConfigImpl {
private static class LoaderCache {
private Config currentSystemProperties;
private ClassLoader currentLoader;
private Map<String, Config> cache;
LoaderCache() {
this.currentSystemProperties = null;
this.currentLoader = null;
this.cache = new HashMap<String, Config>();
}
// 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<Config> updater) {
if (loader != currentLoader) {
// reset the cache if we start using a different loader
cache.clear();
currentLoader = 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 typesafe from cache updater");
cache.put(key, config);
}
return config;
}
}
private static class LoaderCacheHolder {
static final LoaderCache cache = new LoaderCache();
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config computeCachedConfig(ClassLoader loader, String key,
Callable<Config> 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);
}
};
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigObject parseResourcesAnySyntax(Class<?> klass, String resourceBasename,
ConfigParseOptions baseOptions) {
NameSource source = new ClasspathNameSourceWithClass(klass);
return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigObject parseResourcesAnySyntax(String resourceBasename,
ConfigParseOptions baseOptions) {
NameSource source = new ClasspathNameSource();
return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
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);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
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.<AbstractConfigValue> 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.<AbstractConfigValue> emptyList());
}
private static AbstractConfigObject emptyObject(ConfigOrigin origin) {
// we want null origin to go to SimpleConfigObject.empty() to lookup the
// origin "empty typesafe" 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);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigValue fromAnyRef(Object object, String originDescription) {
ConfigOrigin origin = valueOrigin(originDescription);
return fromAnyRef(object, origin, FromMapMode.KEYS_ARE_KEYS);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigObject fromPathMap(
Map<String, ? extends Object> 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 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(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 Map) {
if (((Map<?, ?>) object).isEmpty())
return emptyObject(origin);
if (mapMode == FromMapMode.KEYS_ARE_KEYS) {
Map<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
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<AbstractConfigValue> values = new ArrayList<AbstractConfigValue>();
while (i.hasNext()) {
AbstractConfigValue v = fromAnyRef(i.next(), origin, mapMode);
values.add(v);
}
return new SimpleConfigList(origin, values);
} 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);
}
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config systemPropertiesAsConfig() {
return systemPropertiesAsConfigObject().toConfig();
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static void reloadSystemPropertiesConfig() {
// ConfigFactory.invalidateCaches() relies on this having the side
// effect that it drops list caches
SystemPropertiesHolder.systemProperties = loadSystemProperties();
}
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(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);
}
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config envVariablesAsConfig() {
return envVariablesAsConfigObject().toConfig();
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config defaultReference(final ClassLoader loader) {
return computeCachedConfig(loader, "defaultReference", new Callable<Config>() {
@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 Map<String, Boolean> loadDiagnostics() {
Map<String, Boolean> result = new HashMap<String, Boolean>();
result.put(LOADS, false);
// People do -Dconfig.trace=foo,bar to enable tracing of different things
String s = System.getProperty("typesafe.trace");
if (s == null) {
return result;
} else {
String[] keys = s.split(",");
for (String k : keys) {
if (k.equals(LOADS)) {
result.put(LOADS, true);
} else {
System.err.println("typesafe.trace property contains unknown trace topic '"
+ k + "'");
}
}
return result;
}
}
private static final Map<String, Boolean> diagnostics = loadDiagnostics();
private static final boolean traceLoadsEnabled = diagnostics.get(LOADS);
static boolean traceLoadsEnabled() {
return traceLoadsEnabled;
}
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static boolean traceLoadsEnabled() {
try {
return DebugHolder.traceLoadsEnabled();
} catch (ExceptionInInitializerError e) {
throw ConfigImplUtil.extractInitializerError(e);
}
}
public static void trace(String message) {
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);
}
}