    001/**
    002 *   Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
    003 */
    004package com.typesafe.config.impl;
    005
    006import java.io.File;
    007import java.lang.ref.WeakReference;
    008import java.net.URL;
    009import java.time.Duration;
    010import java.util.ArrayList;
    011import java.util.Collections;
    012import java.util.HashMap;
    013import java.util.Iterator;
    014import java.util.List;
    015import java.util.Map;
    016import java.util.Properties;
    017import java.util.concurrent.Callable;
    018
    019import com.typesafe.config.Config;
    020import com.typesafe.config.ConfigException;
    021import com.typesafe.config.ConfigIncluder;
    022import com.typesafe.config.ConfigMemorySize;
    023import com.typesafe.config.ConfigObject;
    024import com.typesafe.config.ConfigOrigin;
    025import com.typesafe.config.ConfigParseOptions;
    026import com.typesafe.config.ConfigParseable;
    027import com.typesafe.config.ConfigValue;
    028import com.typesafe.config.impl.SimpleIncluder.NameSource;
    029
    030/**
    031 * Internal implementation detail, not ABI stable, do not touch.
    032 * For use only by the {@link com.typesafe.config} package.
    033 */
    034public class ConfigImpl {
    035
    036    private static class LoaderCache {
    037        private Config currentSystemProperties;
    038        private WeakReference<ClassLoader> currentLoader;
    039        private Map<String, Config> cache;
    040
    041        LoaderCache() {
    042            this.currentSystemProperties = null;
    043            this.currentLoader = new WeakReference<ClassLoader>(null);
    044            this.cache = new HashMap<String, Config>();
    045        }
    046
    047        // for now, caching as long as the loader remains the same,
    048        // drop entire cache if it changes.
    049        synchronized Config getOrElseUpdate(ClassLoader loader, String key, Callable<Config> updater) {
    050            if (loader != currentLoader.get()) {
    051                // reset the cache if we start using a different loader
    052                cache.clear();
    053                currentLoader = new WeakReference<ClassLoader>(loader);
    054            }
    055
    056            Config systemProperties = systemPropertiesAsConfig();
    057            if (systemProperties != currentSystemProperties) {
    058                cache.clear();
    059                currentSystemProperties = systemProperties;
    060            }
    061
    062            Config config = cache.get(key);
    063            if (config == null) {
    064                try {
    065                    config = updater.call();
    066                } catch (RuntimeException e) {
    067                    throw e; // this will include ConfigException
    068                } catch (Exception e) {
    069                    throw new ConfigException.Generic(e.getMessage(), e);
    070                }
    071                if (config == null)
    072                    throw new ConfigException.BugOrBroken("null config from cache updater");
    073                cache.put(key, config);
    074            }
    075
    076            return config;
    077        }
    078    }
    079
    080    private static class LoaderCacheHolder {
    081        static final LoaderCache cache = new LoaderCache();
    082    }
    083
    084    public static Config computeCachedConfig(ClassLoader loader, String key,
    085            Callable<Config> updater) {
    086        LoaderCache cache;
    087        try {
    088            cache = LoaderCacheHolder.cache;
    089        } catch (ExceptionInInitializerError e) {
    090            throw ConfigImplUtil.extractInitializerError(e);
    091        }
    092        return cache.getOrElseUpdate(loader, key, updater);
    093    }
    094
    095
    096    static class FileNameSource implements SimpleIncluder.NameSource {
    097        @Override
    098        public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) {
    099            return Parseable.newFile(new File(name), parseOptions);
    100        }
    101    };
    102
    103    static class ClasspathNameSource implements SimpleIncluder.NameSource {
    104        @Override
    105        public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) {
    106            return Parseable.newResources(name, parseOptions);
    107        }
    108    };
    109
    110    static class ClasspathNameSourceWithClass implements SimpleIncluder.NameSource {
    111        final private Class<?> klass;
    112
    113        public ClasspathNameSourceWithClass(Class<?> klass) {
    114            this.klass = klass;
    115        }
    116
    117        @Override
    118        public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) {
    119            return Parseable.newResources(klass, name, parseOptions);
    120        }
    121    };
    122
    123    public static ConfigObject parseResourcesAnySyntax(Class<?> klass, String resourceBasename,
    124            ConfigParseOptions baseOptions) {
    125        NameSource source = new ClasspathNameSourceWithClass(klass);
    126        return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions);
    127    }
    128
    129    public static ConfigObject parseResourcesAnySyntax(String resourceBasename,
    130            ConfigParseOptions baseOptions) {
    131        NameSource source = new ClasspathNameSource();
    132        return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions);
    133    }
    134
    135    public static ConfigObject parseFileAnySyntax(File basename, ConfigParseOptions baseOptions) {
    136        NameSource source = new FileNameSource();
    137        return SimpleIncluder.fromBasename(source, basename.getPath(), baseOptions);
    138    }
    139
    140    static AbstractConfigObject emptyObject(String originDescription) {
    141        ConfigOrigin origin = originDescription != null ? SimpleConfigOrigin
    142                .newSimple(originDescription) : null;
    143        return emptyObject(origin);
    144    }
    145
    146    public static Config emptyConfig(String originDescription) {
    147        return emptyObject(originDescription).toConfig();
    148    }
    149
    150    static AbstractConfigObject empty(ConfigOrigin origin) {
    151        return emptyObject(origin);
    152    }
    153
    154    // default origin for values created with fromAnyRef and no origin specified
    155    final private static ConfigOrigin defaultValueOrigin = SimpleConfigOrigin
    156            .newSimple("hardcoded value");
    157    final private static ConfigBoolean defaultTrueValue = new ConfigBoolean(
    158            defaultValueOrigin, true);
    159    final private static ConfigBoolean defaultFalseValue = new ConfigBoolean(
    160            defaultValueOrigin, false);
    161    final private static ConfigNull defaultNullValue = new ConfigNull(
    162            defaultValueOrigin);
    163    final private static SimpleConfigList defaultEmptyList = new SimpleConfigList(
    164            defaultValueOrigin, Collections.<AbstractConfigValue> emptyList());
    165    final private static SimpleConfigObject defaultEmptyObject = SimpleConfigObject
    166            .empty(defaultValueOrigin);
    167
    168    private static SimpleConfigList emptyList(ConfigOrigin origin) {
    169        if (origin == null || origin == defaultValueOrigin)
    170            return defaultEmptyList;
    171        else
    172            return new SimpleConfigList(origin,
    173                    Collections.<AbstractConfigValue> emptyList());
    174    }
    175
    176    private static AbstractConfigObject emptyObject(ConfigOrigin origin) {
    177        // we want null origin to go to SimpleConfigObject.empty() to get the
    178        // origin "empty config" rather than "hardcoded value"
    179        if (origin == defaultValueOrigin)
    180            return defaultEmptyObject;
    181        else
    182            return SimpleConfigObject.empty(origin);
    183    }
    184
    185    private static ConfigOrigin valueOrigin(String originDescription) {
    186        if (originDescription == null)
    187            return defaultValueOrigin;
    188        else
    189            return SimpleConfigOrigin.newSimple(originDescription);
    190    }
    191
    192    public static ConfigValue fromAnyRef(Object object, String originDescription) {
    193        ConfigOrigin origin = valueOrigin(originDescription);
    194        return fromAnyRef(object, origin, FromMapMode.KEYS_ARE_KEYS);
    195    }
    196
    197    public static ConfigObject fromPathMap(
    198            Map<String, ? extends Object> pathMap, String originDescription) {
    199        ConfigOrigin origin = valueOrigin(originDescription);
    200        return (ConfigObject) fromAnyRef(pathMap, origin,
    201                FromMapMode.KEYS_ARE_PATHS);
    202    }
    203
    204    static AbstractConfigValue fromAnyRef(Object object, ConfigOrigin origin,
    205            FromMapMode mapMode) {
    206        if (origin == null)
    207            throw new ConfigException.BugOrBroken(
    208                    "origin not supposed to be null");
    209
    210        if (object == null) {
    211            if (origin != defaultValueOrigin)
    212                return new ConfigNull(origin);
    213            else
    214                return defaultNullValue;
    215        } else if(object instanceof AbstractConfigValue) {
    216            return (AbstractConfigValue) object;
    217        } else if (object instanceof Boolean) {
    218            if (origin != defaultValueOrigin) {
    219                return new ConfigBoolean(origin, (Boolean) object);
    220            } else if ((Boolean) object) {
    221                return defaultTrueValue;
    222            } else {
    223                return defaultFalseValue;
    224            }
    225        } else if (object instanceof String) {
    226            return new ConfigString.Quoted(origin, (String) object);
    227        } else if (object instanceof Number) {
    228            // here we always keep the same type that was passed to us,
    229            // rather than figuring out if a Long would fit in an Int
    230            // or a Double has no fractional part. i.e. deliberately
    231            // not using ConfigNumber.newNumber() when we have a
    232            // Double, Integer, or Long.
    233            if (object instanceof Double) {
    234                return new ConfigDouble(origin, (Double) object, null);
    235            } else if (object instanceof Integer) {
    236                return new ConfigInt(origin, (Integer) object, null);
    237            } else if (object instanceof Long) {
    238                return new ConfigLong(origin, (Long) object, null);
    239            } else {
    240                return ConfigNumber.newNumber(origin,
    241                        ((Number) object).doubleValue(), null);
    242            }
    243        } else if (object instanceof Duration) {
    244            return new ConfigLong(origin, ((Duration) object).toMillis(), null);
    245        } else if (object instanceof Map) {
    246            if (((Map<?, ?>) object).isEmpty())
    247                return emptyObject(origin);
    248
    249            if (mapMode == FromMapMode.KEYS_ARE_KEYS) {
    250                Map<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
    251                for (Map.Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
    252                    Object key = entry.getKey();
    253                    if (!(key instanceof String))
    254                        throw new ConfigException.BugOrBroken(
    255                                "bug in method caller: not valid to create ConfigObject from map with non-String key: "
    256                                        + key);
    257                    AbstractConfigValue value = fromAnyRef(entry.getValue(),
    258                            origin, mapMode);
    259                    values.put((String) key, value);
    260                }
    261
    262                return new SimpleConfigObject(origin, values);
    263            } else {
    264                return PropertiesParser.fromPathMap(origin, (Map<?, ?>) object);
    265            }
    266        } else if (object instanceof Iterable) {
    267            Iterator<?> i = ((Iterable<?>) object).iterator();
    268            if (!i.hasNext())
    269                return emptyList(origin);
    270
    271            List<AbstractConfigValue> values = new ArrayList<AbstractConfigValue>();
    272            while (i.hasNext()) {
    273                AbstractConfigValue v = fromAnyRef(i.next(), origin, mapMode);
    274                values.add(v);
    275            }
    276
    277            return new SimpleConfigList(origin, values);
    278        } else if (object instanceof ConfigMemorySize) {
    279            return new ConfigLong(origin, ((ConfigMemorySize) object).toBytes(), null);
    280        } else {
    281            throw new ConfigException.BugOrBroken(
    282                    "bug in method caller: not valid to create ConfigValue from: "
    283                            + object);
    284        }
    285    }
    286
    287    private static class DefaultIncluderHolder {
    288        static final ConfigIncluder defaultIncluder = new SimpleIncluder(null);
    289    }
    290
    291    static ConfigIncluder defaultIncluder() {
    292        try {
    293            return DefaultIncluderHolder.defaultIncluder;
    294        } catch (ExceptionInInitializerError e) {
    295            throw ConfigImplUtil.extractInitializerError(e);
    296        }
    297    }
    298
    299    private static Properties getSystemProperties() {
    300        // Avoid ConcurrentModificationException due to parallel setting of system properties by copying properties
    301        final Properties systemProperties = System.getProperties();
    302        final Properties systemPropertiesCopy = new Properties();
    303        synchronized (systemProperties) {
    304            systemPropertiesCopy.putAll(systemProperties);
    305        }
    306        return systemPropertiesCopy;
    307    }
    308
    309    private static AbstractConfigObject loadSystemProperties() {
    310        return (AbstractConfigObject) Parseable.newProperties(getSystemProperties(),
    311                ConfigParseOptions.defaults().setOriginDescription("system properties")).parse();
    312    }
    313
    314    private static class SystemPropertiesHolder {
    315        // this isn't final due to the reloadSystemPropertiesConfig() hack below
    316        static volatile AbstractConfigObject systemProperties = loadSystemProperties();
    317    }
    318
    319    static AbstractConfigObject systemPropertiesAsConfigObject() {
    320        try {
    321            return SystemPropertiesHolder.systemProperties;
    322        } catch (ExceptionInInitializerError e) {
    323            throw ConfigImplUtil.extractInitializerError(e);
    324        }
    325    }
    326
    327    public static Config systemPropertiesAsConfig() {
    328        return systemPropertiesAsConfigObject().toConfig();
    329    }
    330
    331    public static void reloadSystemPropertiesConfig() {
    332        // ConfigFactory.invalidateCaches() relies on this having the side
    333        // effect that it drops all caches
    334        SystemPropertiesHolder.systemProperties = loadSystemProperties();
    335    }
    336
    337    private static AbstractConfigObject loadEnvVariables() {
    338        return PropertiesParser.fromStringMap(newSimpleOrigin("env variables"), System.getenv());
    339    }
    340
    341    private static class EnvVariablesHolder {
    342        static volatile AbstractConfigObject envVariables = loadEnvVariables();
    343    }
    344
    345    static AbstractConfigObject envVariablesAsConfigObject() {
    346        try {
    347            return EnvVariablesHolder.envVariables;
    348        } catch (ExceptionInInitializerError e) {
    349            throw ConfigImplUtil.extractInitializerError(e);
    350        }
    351    }
    352
    353    public static Config envVariablesAsConfig() {
    354        return envVariablesAsConfigObject().toConfig();
    355    }
    356
    357    public static void reloadEnvVariablesConfig() {
    358        // ConfigFactory.invalidateCaches() relies on this having the side
    359        // effect that it drops all caches
    360        EnvVariablesHolder.envVariables = loadEnvVariables();
    361    }
    362
    363    public static Config defaultReference(final ClassLoader loader) {
    364        return computeCachedConfig(loader, "defaultReference", new Callable<Config>() {
    365            @Override
    366            public Config call() {
    367                Config unresolvedResources = Parseable
    368                        .newResources("reference.conf",
    369                                ConfigParseOptions.defaults().setClassLoader(loader))
    370                        .parse().toConfig();
    371                return systemPropertiesAsConfig().withFallback(unresolvedResources).resolve();
    372            }
    373        });
    374    }
    375
    376    private static class DebugHolder {
    377        private static String LOADS = "loads";
    378        private static String SUBSTITUTIONS = "substitutions";
    379
    380        private static Map<String, Boolean> loadDiagnostics() {
    381            Map<String, Boolean> result = new HashMap<String, Boolean>();
    382            result.put(LOADS, false);
    383            result.put(SUBSTITUTIONS, false);
    384
    385            // People do -Dconfig.trace=foo,bar to enable tracing of different things
    386            String s = System.getProperty("config.trace");
    387            if (s == null) {
    388                return result;
    389            } else {
    390                String[] keys = s.split(",");
    391                for (String k : keys) {
    392                    if (k.equals(LOADS)) {
    393                        result.put(LOADS, true);
    394                    } else if (k.equals(SUBSTITUTIONS)) {
    395                        result.put(SUBSTITUTIONS, true);
    396                    } else {
    397                        System.err.println("config.trace property contains unknown trace topic '"
    398                                + k + "'");
    399                    }
    400                }
    401                return result;
    402            }
    403        }
    404
    405        private static final Map<String, Boolean> diagnostics = loadDiagnostics();
    406
    407        private static final boolean traceLoadsEnabled = diagnostics.get(LOADS);
    408        private static final boolean traceSubstitutionsEnabled = diagnostics.get(SUBSTITUTIONS);
    409
    410        static boolean traceLoadsEnabled() {
    411            return traceLoadsEnabled;
    412        }
    413
    414        static boolean traceSubstitutionsEnabled() {
    415            return traceSubstitutionsEnabled;
    416        }
    417    }
    418
    419    public static boolean traceLoadsEnabled() {
    420        try {
    421            return DebugHolder.traceLoadsEnabled();
    422        } catch (ExceptionInInitializerError e) {
    423            throw ConfigImplUtil.extractInitializerError(e);
    424        }
    425    }
    426
    427    public static boolean traceSubstitutionsEnabled() {
    428        try {
    429            return DebugHolder.traceSubstitutionsEnabled();
    430        } catch (ExceptionInInitializerError e) {
    431            throw ConfigImplUtil.extractInitializerError(e);
    432        }
    433    }
    434
    435    public static void trace(String message) {
    436        System.err.println(message);
    437    }
    438
    439    public static void trace(int indentLevel, String message) {
    440        while (indentLevel > 0) {
    441            System.err.print("  ");
    442            indentLevel -= 1;
    443        }
    444        System.err.println(message);
    445    }
    446
    447    // the basic idea here is to add the "what" and have a canonical
    448    // toplevel error message. the "original" exception may however have extra
    449    // detail about what happened. call this if you have a better "what" than
    450    // further down on the stack.
    451    static ConfigException.NotResolved improveNotResolved(Path what,
    452            ConfigException.NotResolved original) {
    453        String newMessage = what.render()
    454                + " has not been resolved, you need to call Config#resolve(),"
    455                + " see API docs for Config#resolve()";
    456        if (newMessage.equals(original.getMessage()))
    457            return original;
    458        else
    459            return new ConfigException.NotResolved(newMessage, original);
    460    }
    461
    462    public static ConfigOrigin newSimpleOrigin(String description) {
    463        if (description == null) {
    464            return defaultValueOrigin;
    465        } else {
    466            return SimpleConfigOrigin.newSimple(description);
    467        }
    468    }
    469
    470    public static ConfigOrigin newFileOrigin(String filename) {
    471        return SimpleConfigOrigin.newFile(filename);
    472    }
    473
    474    public static ConfigOrigin newURLOrigin(URL url) {
    475        return SimpleConfigOrigin.newURL(url);
    476    }
    477}