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