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 Map<String, String> env = System.getenv(); 339 Map<String, AbstractConfigValue> m = new HashMap<String, AbstractConfigValue>(); 340 for (Map.Entry<String, String> entry : env.entrySet()) { 341 String key = entry.getKey(); 342 m.put(key, 343 new ConfigString.Quoted(SimpleConfigOrigin.newSimple("env var " + key), entry 344 .getValue())); 345 } 346 return new SimpleConfigObject(SimpleConfigOrigin.newSimple("env variables"), 347 m, ResolveStatus.RESOLVED, false /* ignoresFallbacks */); 348 } 349 350 private static class EnvVariablesHolder { 351 static final AbstractConfigObject envVariables = loadEnvVariables(); 352 } 353 354 static AbstractConfigObject envVariablesAsConfigObject() { 355 try { 356 return EnvVariablesHolder.envVariables; 357 } catch (ExceptionInInitializerError e) { 358 throw ConfigImplUtil.extractInitializerError(e); 359 } 360 } 361 362 public static Config envVariablesAsConfig() { 363 return envVariablesAsConfigObject().toConfig(); 364 } 365 366 public static Config defaultReference(final ClassLoader loader) { 367 return computeCachedConfig(loader, "defaultReference", new Callable<Config>() { 368 @Override 369 public Config call() { 370 Config unresolvedResources = Parseable 371 .newResources("reference.conf", 372 ConfigParseOptions.defaults().setClassLoader(loader)) 373 .parse().toConfig(); 374 return systemPropertiesAsConfig().withFallback(unresolvedResources).resolve(); 375 } 376 }); 377 } 378 379 private static class DebugHolder { 380 private static String LOADS = "loads"; 381 private static String SUBSTITUTIONS = "substitutions"; 382 383 private static Map<String, Boolean> loadDiagnostics() { 384 Map<String, Boolean> result = new HashMap<String, Boolean>(); 385 result.put(LOADS, false); 386 result.put(SUBSTITUTIONS, false); 387 388 // People do -Dconfig.trace=foo,bar to enable tracing of different things 389 String s = System.getProperty("config.trace"); 390 if (s == null) { 391 return result; 392 } else { 393 String[] keys = s.split(","); 394 for (String k : keys) { 395 if (k.equals(LOADS)) { 396 result.put(LOADS, true); 397 } else if (k.equals(SUBSTITUTIONS)) { 398 result.put(SUBSTITUTIONS, true); 399 } else { 400 System.err.println("config.trace property contains unknown trace topic '" 401 + k + "'"); 402 } 403 } 404 return result; 405 } 406 } 407 408 private static final Map<String, Boolean> diagnostics = loadDiagnostics(); 409 410 private static final boolean traceLoadsEnabled = diagnostics.get(LOADS); 411 private static final boolean traceSubstitutionsEnabled = diagnostics.get(SUBSTITUTIONS); 412 413 static boolean traceLoadsEnabled() { 414 return traceLoadsEnabled; 415 } 416 417 static boolean traceSubstitutionsEnabled() { 418 return traceSubstitutionsEnabled; 419 } 420 } 421 422 public static boolean traceLoadsEnabled() { 423 try { 424 return DebugHolder.traceLoadsEnabled(); 425 } catch (ExceptionInInitializerError e) { 426 throw ConfigImplUtil.extractInitializerError(e); 427 } 428 } 429 430 public static boolean traceSubstitutionsEnabled() { 431 try { 432 return DebugHolder.traceSubstitutionsEnabled(); 433 } catch (ExceptionInInitializerError e) { 434 throw ConfigImplUtil.extractInitializerError(e); 435 } 436 } 437 438 public static void trace(String message) { 439 System.err.println(message); 440 } 441 442 public static void trace(int indentLevel, String message) { 443 while (indentLevel > 0) { 444 System.err.print(" "); 445 indentLevel -= 1; 446 } 447 System.err.println(message); 448 } 449 450 // the basic idea here is to add the "what" and have a canonical 451 // toplevel error message. the "original" exception may however have extra 452 // detail about what happened. call this if you have a better "what" than 453 // further down on the stack. 454 static ConfigException.NotResolved improveNotResolved(Path what, 455 ConfigException.NotResolved original) { 456 String newMessage = what.render() 457 + " has not been resolved, you need to call Config#resolve()," 458 + " see API docs for Config#resolve()"; 459 if (newMessage.equals(original.getMessage())) 460 return original; 461 else 462 return new ConfigException.NotResolved(newMessage, original); 463 } 464 465 public static ConfigOrigin newSimpleOrigin(String description) { 466 if (description == null) { 467 return defaultValueOrigin; 468 } else { 469 return SimpleConfigOrigin.newSimple(description); 470 } 471 } 472 473 public static ConfigOrigin newFileOrigin(String filename) { 474 return SimpleConfigOrigin.newFile(filename); 475 } 476 477 public static ConfigOrigin newURLOrigin(URL url) { 478 return SimpleConfigOrigin.newURL(url); 479 } 480}