From 2286fd765665a7ad955e980680268f00dd5c176d Mon Sep 17 00:00:00 2001 From: CmdrKittens <52665651+CmdrKittens@users.noreply.github.com> Date: Thu, 9 Apr 2020 00:18:17 -0400 Subject: [PATCH] New config --- pom.xml | 7 + .../drtshock/playervaults/PlayerVaults.java | 70 +- .../playervaults/commands/SignCommand.java | 2 +- .../playervaults/config/ConfigManager.java | 47 + .../drtshock/playervaults/config/Loader.java | 161 +++ .../config/annotation/Comment.java | 31 + .../config/annotation/ConfigName.java | 31 + .../config/annotation/WipeOnReload.java | 28 + .../playervaults/config/file/Config.java | 209 +++ .../lib/com/typesafe/config/Config.java | 1139 ++++++++++++++++ .../typesafe/config/ConfigBeanFactory.java | 49 + .../com/typesafe/config/ConfigException.java | 447 +++++++ .../com/typesafe/config/ConfigFactory.java | 1066 +++++++++++++++ .../typesafe/config/ConfigIncludeContext.java | 56 + .../com/typesafe/config/ConfigIncluder.java | 50 + .../config/ConfigIncluderClasspath.java | 25 + .../typesafe/config/ConfigIncluderFile.java | 27 + .../typesafe/config/ConfigIncluderURL.java | 27 + .../lib/com/typesafe/config/ConfigList.java | 48 + .../config/ConfigLoadingStrategy.java | 20 + .../com/typesafe/config/ConfigMemorySize.java | 63 + .../com/typesafe/config/ConfigMergeable.java | 72 + .../lib/com/typesafe/config/ConfigObject.java | 135 ++ .../lib/com/typesafe/config/ConfigOrigin.java | 118 ++ .../typesafe/config/ConfigOriginFactory.java | 68 + .../typesafe/config/ConfigParseOptions.java | 228 ++++ .../com/typesafe/config/ConfigParseable.java | 45 + .../typesafe/config/ConfigRenderOptions.java | 182 +++ .../typesafe/config/ConfigResolveOptions.java | 176 +++ .../com/typesafe/config/ConfigResolver.java | 38 + .../lib/com/typesafe/config/ConfigSyntax.java | 36 + .../lib/com/typesafe/config/ConfigUtil.java | 83 ++ .../lib/com/typesafe/config/ConfigValue.java | 122 ++ .../typesafe/config/ConfigValueFactory.java | 153 +++ .../com/typesafe/config/ConfigValueType.java | 12 + .../config/DefaultConfigLoadingStrategy.java | 62 + .../lib/com/typesafe/config/Optional.java | 14 + .../config/impl/AbstractConfigNode.java | 29 + .../config/impl/AbstractConfigNodeValue.java | 11 + .../config/impl/AbstractConfigObject.java | 221 ++++ .../config/impl/AbstractConfigValue.java | 411 ++++++ .../typesafe/config/impl/ConfigBeanImpl.java | 305 +++++ .../typesafe/config/impl/ConfigBoolean.java | 47 + .../config/impl/ConfigConcatenation.java | 293 +++++ .../config/impl/ConfigDelayedMerge.java | 342 +++++ .../config/impl/ConfigDelayedMergeObject.java | 326 +++++ .../config/impl/ConfigDocumentParser.java | 716 ++++++++++ .../typesafe/config/impl/ConfigDouble.java | 61 + .../com/typesafe/config/impl/ConfigImpl.java | 477 +++++++ .../typesafe/config/impl/ConfigImplUtil.java | 236 ++++ .../config/impl/ConfigIncludeKind.java | 5 + .../com/typesafe/config/impl/ConfigInt.java | 61 + .../com/typesafe/config/impl/ConfigLong.java | 61 + .../typesafe/config/impl/ConfigNodeArray.java | 14 + .../config/impl/ConfigNodeComment.java | 17 + .../config/impl/ConfigNodeComplexValue.java | 52 + .../config/impl/ConfigNodeConcatenation.java | 14 + .../typesafe/config/impl/ConfigNodeField.java | 78 ++ .../config/impl/ConfigNodeInclude.java | 46 + .../config/impl/ConfigNodeObject.java | 281 ++++ .../typesafe/config/impl/ConfigNodePath.java | 52 + .../typesafe/config/impl/ConfigNodeRoot.java | 67 + .../config/impl/ConfigNodeSimpleValue.java | 39 + .../config/impl/ConfigNodeSingleToken.java | 21 + .../com/typesafe/config/impl/ConfigNull.java | 58 + .../typesafe/config/impl/ConfigNumber.java | 110 ++ .../typesafe/config/impl/ConfigParser.java | 426 ++++++ .../typesafe/config/impl/ConfigReference.java | 161 +++ .../typesafe/config/impl/ConfigString.java | 90 ++ .../com/typesafe/config/impl/Container.java | 27 + .../config/impl/DefaultTransformer.java | 128 ++ .../com/typesafe/config/impl/FromMapMode.java | 8 + .../typesafe/config/impl/FullIncluder.java | 14 + .../lib/com/typesafe/config/impl/MemoKey.java | 44 + .../typesafe/config/impl/MergeableValue.java | 9 + .../com/typesafe/config/impl/OriginType.java | 9 + .../com/typesafe/config/impl/Parseable.java | 891 +++++++++++++ .../lib/com/typesafe/config/impl/Path.java | 232 ++++ .../com/typesafe/config/impl/PathBuilder.java | 60 + .../com/typesafe/config/impl/PathParser.java | 281 ++++ .../config/impl/PropertiesParser.java | 210 +++ .../config/impl/ReplaceableMergeStack.java | 14 + .../typesafe/config/impl/ResolveContext.java | 237 ++++ .../typesafe/config/impl/ResolveMemos.java | 35 + .../typesafe/config/impl/ResolveResult.java | 43 + .../typesafe/config/impl/ResolveSource.java | 349 +++++ .../typesafe/config/impl/ResolveStatus.java | 26 + .../config/impl/SerializedConfigValue.java | 534 ++++++++ .../typesafe/config/impl/SimpleConfig.java | 1168 +++++++++++++++++ .../config/impl/SimpleConfigDocument.java | 66 + .../config/impl/SimpleConfigList.java | 464 +++++++ .../config/impl/SimpleConfigObject.java | 671 ++++++++++ .../config/impl/SimpleConfigOrigin.java | 575 ++++++++ .../config/impl/SimpleIncludeContext.java | 51 + .../typesafe/config/impl/SimpleIncluder.java | 302 +++++ .../config/impl/SubstitutionExpression.java | 49 + .../lib/com/typesafe/config/impl/Token.java | 87 ++ .../com/typesafe/config/impl/TokenType.java | 24 + .../com/typesafe/config/impl/Tokenizer.java | 695 ++++++++++ .../lib/com/typesafe/config/impl/Tokens.java | 521 ++++++++ .../com/typesafe/config/impl/Unmergeable.java | 16 + .../lib/com/typesafe/config/impl/package.html | 25 + .../lib/com/typesafe/config/package.html | 58 + .../config/parser/ConfigDocument.java | 82 ++ .../config/parser/ConfigDocumentFactory.java | 93 ++ .../typesafe/config/parser/ConfigNode.java | 35 + .../com/typesafe/config/parser/package.html | 33 + .../playervaults/listeners/SignListener.java | 8 +- .../vaultmanagement/EconomyOperations.java | 17 +- src/main/resources/config.yml | 56 - src/main/resources/credit/apache2-license.txt | 202 +++ src/main/resources/credit/credit.txt | 5 + src/main/resources/credit/gplv3-license.txt | 674 ++++++++++ 113 files changed, 19303 insertions(+), 100 deletions(-) create mode 100644 src/main/java/com/drtshock/playervaults/config/ConfigManager.java create mode 100644 src/main/java/com/drtshock/playervaults/config/Loader.java create mode 100644 src/main/java/com/drtshock/playervaults/config/annotation/Comment.java create mode 100644 src/main/java/com/drtshock/playervaults/config/annotation/ConfigName.java create mode 100644 src/main/java/com/drtshock/playervaults/config/annotation/WipeOnReload.java create mode 100644 src/main/java/com/drtshock/playervaults/config/file/Config.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/Config.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigBeanFactory.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigException.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigFactory.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncludeContext.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluder.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluderClasspath.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluderFile.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluderURL.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigList.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigLoadingStrategy.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigMemorySize.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigMergeable.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigObject.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigOrigin.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigOriginFactory.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigParseOptions.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigParseable.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigRenderOptions.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigResolveOptions.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigResolver.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigSyntax.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigUtil.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigValue.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigValueFactory.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigValueType.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/DefaultConfigLoadingStrategy.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/Optional.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigNode.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigNodeValue.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigObject.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigValue.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigBeanImpl.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigBoolean.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigConcatenation.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDelayedMerge.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDelayedMergeObject.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDocumentParser.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDouble.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigImpl.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigImplUtil.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigIncludeKind.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigInt.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigLong.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeArray.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeComment.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeComplexValue.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeConcatenation.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeField.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeInclude.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeObject.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodePath.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeRoot.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeSimpleValue.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeSingleToken.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNull.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNumber.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigParser.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigReference.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigString.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Container.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/DefaultTransformer.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/FromMapMode.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/FullIncluder.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/MemoKey.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/MergeableValue.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/OriginType.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Parseable.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Path.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/PathBuilder.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/PathParser.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/PropertiesParser.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ReplaceableMergeStack.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveContext.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveMemos.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveResult.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveSource.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveStatus.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SerializedConfigValue.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfig.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigDocument.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigList.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigObject.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigOrigin.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleIncludeContext.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleIncluder.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SubstitutionExpression.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Token.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/TokenType.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Tokenizer.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Tokens.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Unmergeable.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/package.html create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/package.html create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/ConfigDocument.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/ConfigDocumentFactory.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/ConfigNode.java create mode 100644 src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/package.html delete mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/credit/apache2-license.txt create mode 100644 src/main/resources/credit/credit.txt create mode 100644 src/main/resources/credit/gplv3-license.txt diff --git a/pom.xml b/pom.xml index 47d6192..bceecbb 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,7 @@ *.yml lang/*.yml + credit/*.txt @@ -62,5 +63,11 @@ 1.7 provided + + org.checkerframework + checker-qual + 3.3.0 + provided + diff --git a/src/main/java/com/drtshock/playervaults/PlayerVaults.java b/src/main/java/com/drtshock/playervaults/PlayerVaults.java index f9ca46d..56b75a1 100644 --- a/src/main/java/com/drtshock/playervaults/PlayerVaults.java +++ b/src/main/java/com/drtshock/playervaults/PlayerVaults.java @@ -23,6 +23,8 @@ import com.drtshock.playervaults.commands.DeleteCommand; import com.drtshock.playervaults.commands.SignCommand; import com.drtshock.playervaults.commands.SignSetInfo; import com.drtshock.playervaults.commands.VaultCommand; +import com.drtshock.playervaults.config.Loader; +import com.drtshock.playervaults.config.file.Config; import com.drtshock.playervaults.listeners.Listeners; import com.drtshock.playervaults.listeners.SignListener; import com.drtshock.playervaults.listeners.VaultPreloadListener; @@ -53,6 +55,7 @@ import org.bukkit.scheduler.BukkitRunnable; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -83,6 +86,7 @@ public class PlayerVaults extends JavaPlugin { private String _versionString; private int maxVaultAmountPermTest; private Metrics metrics; + private Config config = new Config(); public static PlayerVaults getInstance() { return instance; @@ -105,7 +109,7 @@ public class PlayerVaults extends JavaPlugin { public void onEnable() { instance = this; loadConfig(); - DEBUG = getConfig().getBoolean("debug", false); + DEBUG = getConf().isDebug(); debug("config", System.currentTimeMillis()); uuidData = new File(this.getDataFolder(), "uuidvaults"); vaultData = new File(this.getDataFolder(), "base64vaults"); @@ -123,8 +127,8 @@ public class PlayerVaults extends JavaPlugin { getServer().getPluginManager().registerEvents(new VaultPreloadListener(), this); getServer().getPluginManager().registerEvents(new SignListener(this), this); debug("registering listeners", System.currentTimeMillis()); - this.backupsEnabled = this.getConfig().getBoolean("backups.enabled", true); - this.maxVaultAmountPermTest = this.getConfig().getInt("max-vault-amount-perm-to-test", 99); + this.backupsEnabled = this.getConf().getStorage().getFlatFile().isBackups(); + this.maxVaultAmountPermTest = this.getConf().getMaxVaultAmountPermTest(); loadSigns(); debug("loaded signs", System.currentTimeMillis()); debug("check update", System.currentTimeMillis()); @@ -136,8 +140,8 @@ public class PlayerVaults extends JavaPlugin { useVault = setupEconomy(); debug("setup economy", System.currentTimeMillis()); - if (getConfig().getBoolean("cleanup.enable", false)) { - getServer().getScheduler().runTaskAsynchronously(this, new Cleanup(getConfig().getInt("cleanup.lastEdit", 30))); + if (getConf().getPurge().isEnabled()) { + getServer().getScheduler().runTaskAsynchronously(this, new Cleanup(getConf().getPurge().getDaysSinceLastEdit())); } new BukkitRunnable() { @@ -200,14 +204,14 @@ public class PlayerVaults extends JavaPlugin { } } - this.metricsSimplePie("signs", () -> getConfig().getBoolean("signs-enabled") ? "enabled" : "disabled"); - this.metricsSimplePie("cleanup", () -> getConfig().getBoolean("cleanup.enable") ? "enabled" : "disabled"); - this.metricsSimplePie("language", () -> getConfig().getString("language", "english")); + this.metricsSimplePie("signs", () -> getConf().isSigns() ? "enabled" : "disabled"); + this.metricsSimplePie("cleanup", () -> getConf().getPurge().isEnabled() ? "enabled" : "disabled"); + this.metricsSimplePie("language", () -> getConf().getLanguage()); this.metricsDrillPie("block_items", () -> { Map> map = new HashMap<>(); Map entry = new HashMap<>(); - if (getConfig().getBoolean("blockitems")) { + if (getConf().getItemBlocking().isEnabled()) { for (Material material : blockedMats) { entry.put(material.toString(), 1); } @@ -215,7 +219,7 @@ public class PlayerVaults extends JavaPlugin { if (entry.isEmpty()) { entry.put("none", 1); } - map.put(getConfig().getBoolean("blockitems") ? "enabled" : "disabled", entry); + map.put(getConf().getItemBlocking().isEnabled() ? "enabled" : "disabled", entry); return map; }); @@ -265,7 +269,7 @@ public class PlayerVaults extends JavaPlugin { } } - if (getConfig().getBoolean("cleanup.enable", false)) { + if (getConf().getPurge().isEnabled()) { saveSignsFile(); } } @@ -297,12 +301,27 @@ public class PlayerVaults extends JavaPlugin { } private void loadConfig() { - saveDefaultConfig(); + File configYaml = new File(this.getDataFolder(), "config.yml"); + if (configYaml.exists()) { + this.config.setFromConfig(this.getConfig()); + try { + Files.move(configYaml.toPath(), this.getDataFolder().toPath().resolve("old_unused_config.yml")); + } catch (Exception e) { + this.getLogger().log(Level.SEVERE, "Failed to move config for backup", e); + configYaml.deleteOnExit(); + } + } + + try { + Loader.loadAndSave("config", this.config); + } catch (IOException | IllegalAccessException e) { + this.getLogger().log(Level.SEVERE, "Could not load config.", e); + } // Clear just in case this is a reload. blockedMats.clear(); - if (getConfig().getBoolean("blockitems", false) && getConfig().contains("blocked-items")) { - for (String s : getConfig().getStringList("blocked-items")) { + if (getConf().getItemBlocking().isEnabled()) { + for (String s : getConf().getItemBlocking().getList()) { Material mat = Material.matchMaterial(s); if (mat != null) { blockedMats.add(mat); @@ -312,6 +331,10 @@ public class PlayerVaults extends JavaPlugin { } } + public Config getConf() { + return this.config; + } + private void loadSigns() { File signs = new File(getDataFolder(), "signs.yml"); if (!signs.exists()) { @@ -328,7 +351,7 @@ public class PlayerVaults extends JavaPlugin { } private void reloadSigns() { - if (!getConfig().getBoolean("signs-enabled")) { + if (!getConf().isSigns()) { return; } if (!signsFile.exists()) loadSigns(); @@ -358,7 +381,7 @@ public class PlayerVaults extends JavaPlugin { } private void saveSignsFile() { - if (!getConfig().getBoolean("signs-enabled")) { + if (!getConf().isSigns()) { return; } @@ -372,24 +395,13 @@ public class PlayerVaults extends JavaPlugin { } } - /** - * Set an object in the config.yml - * - * @param path The path in the config. - * @param object What to be saved. - * @param conf Where to save the object. - */ - public void setInConfig(String path, T object, YamlConfiguration conf) { - conf.set(path, object); - } - public void loadLang() { File folder = new File(getDataFolder(), "lang"); if (!folder.exists()) { folder.mkdir(); } - String definedLanguage = getConfig().getString("language", "english"); + String definedLanguage = getConf().getLanguage(); // Save as default just incase. File english = null; @@ -444,7 +456,7 @@ public class PlayerVaults extends JavaPlugin { } public boolean isEconomyEnabled() { - return this.getConfig().getBoolean("economy.enabled", false) && this.useVault; + return this.getConf().getEconomy().isEnabled() && this.useVault; } public File getVaultData() { diff --git a/src/main/java/com/drtshock/playervaults/commands/SignCommand.java b/src/main/java/com/drtshock/playervaults/commands/SignCommand.java index b90f793..b3fea75 100644 --- a/src/main/java/com/drtshock/playervaults/commands/SignCommand.java +++ b/src/main/java/com/drtshock/playervaults/commands/SignCommand.java @@ -30,7 +30,7 @@ public class SignCommand implements CommandExecutor { @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (sender.hasPermission("playervaults.signs.set")) { - if (!PlayerVaults.getInstance().getConfig().getBoolean("signs-enabled")) { + if (!PlayerVaults.getInstance().getConf().isSigns()) { sender.sendMessage(Lang.TITLE.toString() + Lang.SIGNS_DISABLED.toString()); return true; } diff --git a/src/main/java/com/drtshock/playervaults/config/ConfigManager.java b/src/main/java/com/drtshock/playervaults/config/ConfigManager.java new file mode 100644 index 0000000..fe07d9d --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/config/ConfigManager.java @@ -0,0 +1,47 @@ +/* + * PlayerVaultsX + * Copyright (C) 2013 Trent Hensler, Laxwashere, CmdrKittens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.drtshock.playervaults.config; + + +import com.drtshock.playervaults.PlayerVaults; +import com.drtshock.playervaults.config.file.Config; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.io.IOException; +import java.util.logging.Level; + +public class ConfigManager { + private final PlayerVaults plugin; + private final Config config = new Config(); + + public ConfigManager(@NonNull PlayerVaults plugin) { + this.plugin = plugin; + } + + public void loadConfig() { + try { + Loader.loadAndSave("main", this.config); + } catch (IOException | IllegalAccessException e) { + this.plugin.getLogger().log(Level.SEVERE, "Could not load config. Using all default values until resolved.", e); + } + } + + public @NonNull Config getConf() { + return this.config; + } +} diff --git a/src/main/java/com/drtshock/playervaults/config/Loader.java b/src/main/java/com/drtshock/playervaults/config/Loader.java new file mode 100644 index 0000000..ac854aa --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/config/Loader.java @@ -0,0 +1,161 @@ +/* + * PlayerVaultsX + * Copyright (C) 2013 Trent Hensler, Laxwashere, CmdrKittens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.drtshock.playervaults.config; + +import com.drtshock.playervaults.PlayerVaults; +import com.drtshock.playervaults.config.annotation.WipeOnReload; +import com.drtshock.playervaults.lib.com.typesafe.config.Config; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigFactory; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigRenderOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueFactory; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import com.drtshock.playervaults.config.annotation.Comment; +import com.drtshock.playervaults.config.annotation.ConfigName; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class Loader { + public static void loadAndSave(@NonNull String fileName, @NonNull Object config) throws IOException, IllegalAccessException { + File file = Loader.getFile(fileName); + Loader.loadAndSave(file, Loader.getConf(file), config); + } + + public static @NonNull File getFile(@NonNull String file) { + Path configFolder = PlayerVaults.getInstance().getDataFolder().toPath(); + if (!configFolder.toFile().exists()) { + configFolder.toFile().mkdir(); + } + Path path = configFolder.resolve(file + ".conf"); + return path.toFile(); + } + + public static @NonNull Config getConf(@NonNull File file) { + return ConfigFactory.parseFile(file); + } + + public static void loadAndSave(@NonNull File file, @NonNull Config config, @NonNull Object configObject) throws IOException, IllegalAccessException { + ConfigValue value = Loader.loadNode(config, configObject); + String s = value.render(ConfigRenderOptions.defaults().setOriginComments(false).setComments(true).setJson(false)); + Files.write(file.toPath(), s.getBytes(StandardCharsets.UTF_8)); + } + + public static ConfigValue load(Config config, Object configObject) throws IOException, IllegalAccessException { + return Loader.loadNode(config, configObject); + } + + private static Set> types = new HashSet<>(); + + static { + Loader.types.add(Boolean.TYPE); + Loader.types.add(Byte.TYPE); + Loader.types.add(Character.TYPE); + Loader.types.add(Double.TYPE); + Loader.types.add(Float.TYPE); + Loader.types.add(Integer.TYPE); + Loader.types.add(Long.TYPE); + Loader.types.add(Short.TYPE); + Loader.types.add(List.class); + Loader.types.add(Map.class); + Loader.types.add(Set.class); + Loader.types.add(String.class); + } + + private static @NonNull ConfigValue loadNode(@NonNull Config current, @NonNull Object object) throws IllegalAccessException { + Map map = new HashMap<>(); + for (Field field : Loader.getFields(object.getClass())) { + if (field.isSynthetic()) { + continue; + } + if ((field.getModifiers() & Modifier.TRANSIENT) != 0) { + if (field.getAnnotation(WipeOnReload.class) != null) { + field.setAccessible(true); + field.set(object, null); + } + continue; + } + field.setAccessible(true); + ConfigName configName = field.getAnnotation(ConfigName.class); + Comment comment = field.getAnnotation(Comment.class); + String confName = configName == null || configName.value().isEmpty() ? field.getName() : configName.value(); + ConfigValue curValue = Loader.getOrNull(current, confName); + boolean needsValue = curValue == null; + + ConfigValue newValue; + Object defaultValue = field.get(object); + if (Loader.types.contains(field.getType())) { + if (needsValue) { + newValue = ConfigValueFactory.fromAnyRef(defaultValue); + } else { + try { + if (Set.class.isAssignableFrom(field.getType()) && curValue.valueType() == ConfigValueType.LIST) { + field.set(object, new HashSet((List) curValue.unwrapped())); + } else { + field.set(object, curValue.unwrapped()); + } + newValue = curValue; + } catch (IllegalArgumentException ex) { + System.out.println("Found incorrect type for " + confName + ": Expected " + field.getType() + ", found " + curValue.unwrapped().getClass()); + field.set(object, defaultValue); + newValue = ConfigValueFactory.fromAnyRef(defaultValue); + } + } + } else { + newValue = Loader.loadNode(current, defaultValue); + } + if (comment != null) { + newValue = newValue.withOrigin(newValue.origin().withComments(Arrays.asList(comment.value().split("\n")))); + } + map.put(confName, newValue); + } + return ConfigValueFactory.fromMap(map); + } + + private static @Nullable ConfigValue getOrNull(@NonNull Config config, @NonNull String path) { + return config.hasPath(path) ? config.getValue(path) : null; + } + + private static @NonNull List getFields(Class clazz) { + return Loader.getFields(new ArrayList<>(), clazz); + } + + private static @NonNull List getFields(@NonNull List fields, @NonNull Class clazz) { + fields.addAll(Arrays.asList(clazz.getDeclaredFields())); + + if (clazz.getSuperclass() != null) { + Loader.getFields(fields, clazz.getSuperclass()); + } + + return fields; + } +} diff --git a/src/main/java/com/drtshock/playervaults/config/annotation/Comment.java b/src/main/java/com/drtshock/playervaults/config/annotation/Comment.java new file mode 100644 index 0000000..902d8fe --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/config/annotation/Comment.java @@ -0,0 +1,31 @@ +/* + * PlayerVaultsX + * Copyright (C) 2013 Trent Hensler, Laxwashere, CmdrKittens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.drtshock.playervaults.config.annotation; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Comment { + @NonNull String value(); +} diff --git a/src/main/java/com/drtshock/playervaults/config/annotation/ConfigName.java b/src/main/java/com/drtshock/playervaults/config/annotation/ConfigName.java new file mode 100644 index 0000000..2d802cc --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/config/annotation/ConfigName.java @@ -0,0 +1,31 @@ +/* + * PlayerVaultsX + * Copyright (C) 2013 Trent Hensler, Laxwashere, CmdrKittens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.drtshock.playervaults.config.annotation; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ConfigName { + @NonNull String value(); +} diff --git a/src/main/java/com/drtshock/playervaults/config/annotation/WipeOnReload.java b/src/main/java/com/drtshock/playervaults/config/annotation/WipeOnReload.java new file mode 100644 index 0000000..764110c --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/config/annotation/WipeOnReload.java @@ -0,0 +1,28 @@ +/* + * PlayerVaultsX + * Copyright (C) 2013 Trent Hensler, Laxwashere, CmdrKittens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.drtshock.playervaults.config.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface WipeOnReload { +} \ No newline at end of file diff --git a/src/main/java/com/drtshock/playervaults/config/file/Config.java b/src/main/java/com/drtshock/playervaults/config/file/Config.java new file mode 100644 index 0000000..f0bada7 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/config/file/Config.java @@ -0,0 +1,209 @@ +/* + * PlayerVaultsX + * Copyright (C) 2013 Trent Hensler, Laxwashere, CmdrKittens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.drtshock.playervaults.config.file; + +import com.drtshock.playervaults.config.annotation.Comment; +import org.bukkit.configuration.file.FileConfiguration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@SuppressWarnings({"FieldCanBeLocal", "InnerClassMayBeStatic", "unused"}) +public class Config { + public class Block { + private boolean enabled = true; + @Comment("Material list for blocked items (does not support ID's), only effective if the feature is enabled.\n" + + " If you don't know material names: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html") + private List list = new ArrayList() { + { + this.add("PUMPKIN"); + this.add("DIAMOND_BLOCK"); + } + }; + + public boolean isEnabled() { + return this.enabled; + } + + public List getList() { + if (this.list == null) { + this.list = new ArrayList<>(); + } + return Collections.unmodifiableList(list); + } + } + + public class Economy { + @Comment("Set me to true to enable economy features!") + private boolean enabled = false; + private double feeToCreate = 100; + private double feeToOpen = 10; + private double refundOnDelete = 50; + + public boolean isEnabled() { + return this.enabled; + } + + public double getFeeToCreate() { + return this.feeToCreate; + } + + public double getFeeToOpen() { + return this.feeToOpen; + } + + public double getRefundOnDelete() { + return this.refundOnDelete; + } + } + + public class PurgePlanet { + private boolean enabled = false; + @Comment("Time, in days, since last edit") + private int daysSinceLastEdit = 30; + + public boolean isEnabled() { + return this.enabled; + } + + public int getDaysSinceLastEdit() { + return this.daysSinceLastEdit; + } + } + + public class Storage { + public class FlatFile { + @Comment("Backups\n" + + " Enabling this will create backups of vaults automagically.") + private boolean backups = true; + + public boolean isBackups() { + return this.backups; + } + } + + private FlatFile flatFile = new FlatFile(); + private String storageType = "flatfile"; + + public FlatFile getFlatFile() { + return this.flatFile; + } + + public String getStorageType() { + return this.storageType; + } + } + + @Comment("PlayerVaults\n" + + "Created by: https://github.com/drtshock/PlayerVaults/graphs/contributors/\n" + + "Resource page: https://www.spigotmc.org/resources/51204/\n" + + "Discord server: https://discordapp.com/invite/JZcWDEt/\n" + + "Made with love <3") + private boolean aPleasantHello=true; + + @Comment("Debug Mode\n" + + " This will print everything the plugin is doing to console.\n" + + " You should only enable this if you're working with a contributor to fix something.") + private boolean debug = false; + + @Comment("Language\n" + + " This determines which language file the plugin will read from.\n" + + " Valid options are (don't include .yml): bulgarian, dutch, english, german, turkish, russian") + private String language = "english"; + + @Comment("Signs\n" + + " This will determine whether vault signs are enabled.\n" + + " If you don't know what this is or if it's for you, see the resource page.") + private boolean signs = false; + + @Comment("Economy\n" + + " These are all of the settings for the economy integration. (Requires Vault)\n" + + " Bypass permission is: playervaults.free") + private Economy economy = new Economy(); + + @Comment("Blocked Items\n" + + " This will allow you to block specific materials from vaults.\n" + + " Bypass permission is: playervaults.bypassblockeditems") + private Block itemBlocking = new Block(); + + @Comment("Cleanup\n" + + " Enabling this will purge vaults that haven't been touched in the specified time frame.\n" + + " Reminder: This is only checked during startup.\n" + + " This will not lag your server or touch the backups folder.") + private PurgePlanet purge = new PurgePlanet(); + + @Comment("Sets the highest vault amount this plugin will test perms for") + private int maxVaultAmountPermTest = 99; + + @Comment("Storage option. Currently only flatfile, but soon more! :)") + private Storage storage = new Storage(); + + public void setFromConfig(FileConfiguration c) { + this.debug = c.getBoolean("debug", false); + this.language = c.getString("language", "english"); + this.signs = c.getBoolean("signs-enabled", false); + this.economy.enabled = c.getBoolean("economy.enabled", false); + this.economy.feeToCreate = c.getDouble("economy.cost-to-create", 100); + this.economy.feeToOpen = c.getDouble("economy.cost-to-open", 10); + this.economy.refundOnDelete = c.getDouble("economy.refund-on-delete", 50); + this.itemBlocking.enabled = c.getBoolean("blockitems", true); + this.itemBlocking.list = c.getStringList("blocked-items"); + if (this.itemBlocking.list == null) { + this.itemBlocking.list = new ArrayList<>(); + this.itemBlocking.list.add("PUMPKIN"); + this.itemBlocking.list.add("DIAMOND_BLOCK"); + } + this.purge.enabled = c.getBoolean("cleanup.enable", false); + this.purge.daysSinceLastEdit = c.getInt("cleanup.lastEdit", 30); + this.storage.flatFile.backups = c.getBoolean("backups.enabled", true); + this.maxVaultAmountPermTest = c.getInt("max-vault-amount-perm-to-test", 99); + } + + public boolean isDebug() { + return this.debug; + } + + public String getLanguage() { + return this.language; + } + + public boolean isSigns() { + return this.signs; + } + + public Economy getEconomy() { + return this.economy; + } + + public Block getItemBlocking() { + return this.itemBlocking; + } + + public PurgePlanet getPurge() { + return this.purge; + } + + public int getMaxVaultAmountPermTest() { + return this.maxVaultAmountPermTest; + } + + public Storage getStorage() { + return this.storage; + } +} \ No newline at end of file diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/Config.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/Config.java new file mode 100644 index 0000000..7f1d142 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/Config.java @@ -0,0 +1,1139 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.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; +import java.util.concurrent.TimeUnit; + +/** + * An immutable map from config paths to config values. Paths are dot-separated + * expressions such as foo.bar.baz. Values are as in JSON + * (booleans, strings, numbers, lists, or objects), represented by + * {@link ConfigValue} instances. Values accessed through the + * Config interface are never null. + * + *

+ * {@code Config} is an immutable object and thus safe to use from multiple + * threads. There's never a need for "defensive copies." + * + *

+ * Fundamental operations on a {@code Config} include getting configuration + * values, resolving substitutions with {@link Config#resolve()}, and + * merging configs using {@link Config#withFallback(ConfigMergeable)}. + * + *

+ * All operations return a new immutable {@code Config} rather than modifying + * the original instance. + * + *

+ * Examples + * + *

+ * You can find an example app and library on + * GitHub. Also be sure to read the package overview which + * describes the big picture as shown in those examples. + * + *

+ * Paths, keys, and Config vs. ConfigObject + * + *

+ * Config is a view onto a tree of {@link ConfigObject}; the + * corresponding object tree can be found through {@link Config#root()}. + * ConfigObject is a map from config keys, rather than + * paths, to config values. Think of ConfigObject as a JSON object + * and Config as a configuration API. + * + *

+ * The API tries to consistently use the terms "key" and "path." A key is a key + * in a JSON object; it's just a string that's the key in a map. A "path" is a + * parseable expression with a syntax and it refers to a series of keys. Path + * expressions are described in the spec for + * Human-Optimized Config Object Notation. In brief, a path is + * period-separated so "a.b.c" looks for key c in object b in object a in the + * root object. Sometimes double quotes are needed around special characters in + * path expressions. + * + *

+ * The API for a {@code Config} is in terms of path expressions, while the API + * for a {@code ConfigObject} is in terms of keys. Conceptually, {@code Config} + * is a one-level map from paths to values, while a + * {@code ConfigObject} is a tree of nested maps from keys to values. + * + *

+ * Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert + * between path expressions and individual path elements (keys). + * + *

+ * Another difference between {@code Config} and {@code ConfigObject} is that + * conceptually, {@code ConfigValue}s with a {@link ConfigValue#valueType() + * valueType()} of {@link ConfigValueType#NULL NULL} exist in a + * {@code ConfigObject}, while a {@code Config} treats null values as if they + * were missing. (With the exception of two methods: {@link Config#hasPathOrNull} + * and {@link Config#getIsNull} let you detect null values.) + * + *

+ * Getting configuration values + * + *

+ * The "getters" on a {@code Config} all work in the same way. They never return + * null, nor do they return a {@code ConfigValue} with + * {@link ConfigValue#valueType() valueType()} of {@link ConfigValueType#NULL + * NULL}. Instead, they throw {@link ConfigException.Missing} if the value is + * completely absent or set to null. If the value is set to null, a subtype of + * {@code ConfigException.Missing} called {@link ConfigException.Null} will be + * thrown. {@link ConfigException.WrongType} will be thrown anytime you ask for + * a type and the value has an incompatible type. Reasonable type conversions + * are performed for you though. + * + *

+ * Iteration + * + *

+ * If you want to iterate over the contents of a {@code Config}, you can get its + * {@code ConfigObject} with {@link #root()}, and then iterate over the + * {@code ConfigObject} (which implements java.util.Map). Or, you + * can use {@link #entrySet()} which recurses the object tree for you and builds + * up a Set of all path-value pairs where the value is not null. + * + *

+ * Resolving substitutions + * + *

+ * Substitutions are the ${foo.bar} syntax in config + * files, described in the specification. Resolving substitutions replaces these references with real + * values. + * + *

+ * Before using a {@code Config} it's necessary to call {@link Config#resolve()} + * to handle substitutions (though {@link ConfigFactory#load()} and similar + * methods will do the resolve for you already). + * + *

+ * Merging + * + *

+ * The full Config for your application can be constructed using + * the associative operation {@link Config#withFallback(ConfigMergeable)}. If + * you use {@link ConfigFactory#load()} (recommended), it merges system + * properties over the top of application.conf over the top of + * reference.conf, using withFallback. You can add in + * additional sources of configuration in the same way (usually, custom layers + * should go either just above or just below application.conf, + * keeping reference.conf at the bottom and system properties at + * the top). + * + *

+ * Serialization + * + *

+ * Convert a Config to a JSON or HOCON string by calling + * {@link ConfigObject#render()} on the root object, + * myConfig.root().render(). There's also a variant + * {@link ConfigObject#render(ConfigRenderOptions)} which allows you to control + * the format of the rendered string. (See {@link ConfigRenderOptions}.) Note + * that Config does not remember the formatting of the original + * file, so if you load, modify, and re-save a config file, it will be + * substantially reformatted. + * + *

+ * As an alternative to {@link ConfigObject#render()}, the + * toString() method produces a debug-output-oriented + * representation (which is not valid JSON). + * + *

+ * Java serialization is supported as well for Config and all + * subtypes of ConfigValue. + * + *

+ * This is an interface but don't implement it yourself + * + *

+ * Do not implement {@code Config}; it should only be implemented by + * the config library. Arbitrary implementations will not work because the + * library internals assume a specific concrete implementation. Also, this + * interface is likely to grow new methods over time, so third-party + * implementations will break. + */ +public interface Config extends ConfigMergeable { + /** + * Gets the {@code Config} as a tree of {@link ConfigObject}. This is a + * constant-time operation (it is not proportional to the number of values + * in the {@code Config}). + * + * @return the root object in the configuration + */ + ConfigObject root(); + + /** + * Gets the origin of the {@code Config}, which may be a file, or a file + * with a line number, or just a descriptive phrase. + * + * @return the origin of the {@code Config} for use in error messages + */ + ConfigOrigin origin(); + + @Override + Config withFallback(ConfigMergeable other); + + /** + * Returns a replacement config with all substitutions (the + * ${foo.bar} syntax, see the + * spec) resolved. Substitutions are looked up using this + * Config as the root object, that is, a substitution + * ${foo.bar} will be replaced with the result of + * getValue("foo.bar"). + * + *

+ * This method uses {@link ConfigResolveOptions#defaults()}, there is + * another variant {@link Config#resolve(ConfigResolveOptions)} which lets + * you specify non-default options. + * + *

+ * A given {@link Config} must be resolved before using it to retrieve + * config values, but ideally should be resolved one time for your entire + * stack of fallbacks (see {@link Config#withFallback}). Otherwise, some + * substitutions that could have resolved with all fallbacks available may + * not resolve, which will be potentially confusing for your application's + * users. + * + *

+ * resolve() should be invoked on root config objects, rather + * than on a subtree (a subtree is the result of something like + * config.getConfig("foo")). The problem with + * resolve() on a subtree is that substitutions are relative to + * the root of the config and the subtree will have no way to get values + * from the root. For example, if you did + * config.getConfig("foo").resolve() on the below config file, + * it would not work: + * + *

+     *   common-value = 10
+     *   foo {
+     *      whatever = ${common-value}
+     *   }
+     * 
+ * + *

+ * Many methods on {@link ConfigFactory} such as + * {@link ConfigFactory#load()} automatically resolve the loaded + * Config on the loaded stack of config files. + * + *

+ * Resolving an already-resolved config is a harmless no-op, but again, it + * is best to resolve an entire stack of fallbacks (such as all your config + * files combined) rather than resolving each one individually. + * + * @return an immutable object with substitutions resolved + * @throws ConfigException.UnresolvedSubstitution + * if any substitutions refer to nonexistent paths + * @throws ConfigException + * some other config exception if there are other problems + */ + Config resolve(); + + /** + * Like {@link Config#resolve()} but allows you to specify non-default + * options. + * + * @param options + * resolve options + * @return the resolved Config (may be only partially resolved if options are set to allow unresolved) + */ + Config resolve(ConfigResolveOptions options); + + /** + * Checks whether the config is completely resolved. After a successful call + * to {@link Config#resolve()} it will be completely resolved, but after + * calling {@link Config#resolve(ConfigResolveOptions)} with + * allowUnresolved set in the options, it may or may not be + * completely resolved. A newly-loaded config may or may not be completely + * resolved depending on whether there were substitutions present in the + * file. + * + * @return true if there are no unresolved substitutions remaining in this + * configuration. + * @since 1.2.0 + */ + boolean isResolved(); + + /** + * Like {@link Config#resolve()} except that substitution values are looked + * up in the given source, rather than in this instance. This is a + * special-purpose method which doesn't make sense to use in most cases; + * it's only needed if you're constructing some sort of app-specific custom + * approach to configuration. The more usual approach if you have a source + * of substitution values would be to merge that source into your config + * stack using {@link Config#withFallback} and then resolve. + *

+ * Note that this method does NOT look in this instance for substitution + * values. If you want to do that, you could either merge this instance into + * your value source using {@link Config#withFallback}, or you could resolve + * multiple times with multiple sources (using + * {@link ConfigResolveOptions#setAllowUnresolved(boolean)} so the partial + * resolves don't fail). + * + * @param source + * configuration to pull values from + * @return an immutable object with substitutions resolved + * @throws ConfigException.UnresolvedSubstitution + * if any substitutions refer to paths which are not in the + * source + * @throws ConfigException + * some other config exception if there are other problems + * @since 1.2.0 + */ + Config resolveWith(Config source); + + /** + * Like {@link Config#resolveWith(Config)} but allows you to specify + * non-default options. + * + * @param source + * source configuration to pull values from + * @param options + * resolve options + * @return the resolved Config (may be only partially resolved + * if options are set to allow unresolved) + * @since 1.2.0 + */ + Config resolveWith(Config source, ConfigResolveOptions options); + + /** + * Validates this config against a reference config, throwing an exception + * if it is invalid. The purpose of this method is to "fail early" with a + * comprehensive list of problems; in general, anything this method can find + * would be detected later when trying to use the config, but it's often + * more user-friendly to fail right away when loading the config. + * + *

+ * Using this method is always optional, since you can "fail late" instead. + * + *

+ * You must restrict validation to paths you "own" (those whose meaning are + * defined by your code module). If you validate globally, you may trigger + * errors about paths that happen to be in the config but have nothing to do + * with your module. It's best to allow the modules owning those paths to + * validate them. Also, if every module validates only its own stuff, there + * isn't as much redundant work being done. + * + *

+ * If no paths are specified in checkValid()'s parameter list, + * validation is for the entire config. + * + *

+ * If you specify paths that are not in the reference config, those paths + * are ignored. (There's nothing to validate.) + * + *

+ * Here's what validation involves: + * + *

    + *
  • All paths found in the reference config must be present in this + * config or an exception will be thrown. + *
  • + * Some changes in type from the reference config to this config will cause + * an exception to be thrown. Not all potential type problems are detected, + * in particular it's assumed that strings are compatible with everything + * except objects and lists. This is because string types are often "really" + * some other type (system properties always start out as strings, or a + * string like "5ms" could be used with {@link #getMilliseconds}). Also, + * it's allowed to set any type to null or override null with any type. + *
  • + * Any unresolved substitutions in this config will cause a validation + * failure; both the reference config and this config should be resolved + * before validation. If the reference config is unresolved, it's a bug in + * the caller of this method. + *
+ * + *

+ * If you want to allow a certain setting to have a flexible type (or + * otherwise want validation to be looser for some settings), you could + * either remove the problematic setting from the reference config provided + * to this method, or you could intercept the validation exception and + * screen out certain problems. Of course, this will only work if all other + * callers of this method are careful to restrict validation to their own + * paths, as they should be. + * + *

+ * If validation fails, the thrown exception contains a list of all problems + * found. See {@link ConfigException.ValidationFailed#problems}. The + * exception's getMessage() will have all the problems + * concatenated into one huge string, as well. + * + *

+ * Again, checkValid() can't guess every domain-specific way a + * setting can be invalid, so some problems may arise later when attempting + * to use the config. checkValid() is limited to reporting + * generic, but common, problems such as missing settings and blatant type + * incompatibilities. + * + * @param reference + * a reference configuration + * @param restrictToPaths + * only validate values underneath these paths that your code + * module owns and understands + * @throws ConfigException.ValidationFailed + * if there are any validation issues + * @throws ConfigException.NotResolved + * if this config is not resolved + * @throws ConfigException.BugOrBroken + * if the reference config is unresolved or caller otherwise + * misuses the API + */ + void checkValid(Config reference, String... restrictToPaths); + + /** + * Checks whether a value is present and non-null at the given path. This + * differs in two ways from {@code Map.containsKey()} as implemented by + * {@link ConfigObject}: it looks for a path expression, not a key; and it + * returns false for null values, while {@code containsKey()} returns true + * indicating that the object contains a null value for the key. + * + *

+ * If a path exists according to {@link #hasPath(String)}, then + * {@link #getValue(String)} will never throw an exception. However, the + * typed getters, such as {@link #getInt(String)}, will still throw if the + * value is not convertible to the requested type. + * + *

+ * Note that path expressions have a syntax and sometimes require quoting + * (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). + * + * @param path + * the path expression + * @return true if a non-null value is present at the path + * @throws ConfigException.BadPath + * if the path expression is invalid + */ + boolean hasPath(String path); + + /** + * Checks whether a value is present at the given path, even + * if the value is null. Most of the getters on + * Config will throw if you try to get a null + * value, so if you plan to call {@link #getValue(String)}, + * {@link #getInt(String)}, or another getter you may want to + * use plain {@link #hasPath(String)} rather than this method. + * + *

+ * To handle all three cases (unset, null, and a non-null value) + * the code might look like: + *


+     * if (config.hasPathOrNull(path)) {
+     *     if (config.getIsNull(path)) {
+     *        // handle null setting
+     *     } else {
+     *        // get and use non-null setting
+     *     }
+     * } else {
+     *     // handle entirely unset path
+     * }
+     * 
+ * + *

However, the usual thing is to allow entirely unset + * paths to be a bug that throws an exception (because you set + * a default in your reference.conf), so in that + * case it's OK to call {@link #getIsNull(String)} without + * checking hasPathOrNull first. + * + *

+ * Note that path expressions have a syntax and sometimes require quoting + * (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). + * + * @param path + * the path expression + * @return true if a value is present at the path, even if the value is null + * @throws ConfigException.BadPath + * if the path expression is invalid + */ + boolean hasPathOrNull(String path); + + /** + * Returns true if the {@code Config}'s root object contains no key-value + * pairs. + * + * @return true if the configuration is empty + */ + boolean isEmpty(); + + /** + * Returns the set of path-value pairs, excluding any null values, found by + * recursing {@link #root() the root object}. Note that this is very + * different from root().entrySet() which returns the set of + * immediate-child keys in the root object and includes null values. + *

+ * Entries contain path expressions meaning there may be quoting + * and escaping involved. Parse path expressions with + * {@link ConfigUtil#splitPath}. + *

+ * Because a Config is conceptually a single-level map from + * paths to values, there will not be any {@link ConfigObject} values in the + * entries (that is, all entries represent leaf nodes). Use + * {@link ConfigObject} rather than Config if you want a tree. + * (OK, this is a slight lie: Config entries may contain + * {@link ConfigList} and the lists may contain objects. But no objects are + * directly included as entry values.) + * + * @return set of paths with non-null values, built up by recursing the + * entire tree of {@link ConfigObject} and creating an entry for + * each leaf value. + */ + Set> entrySet(); + + /** + * Checks whether a value is set to null at the given path, + * but throws an exception if the value is entirely + * unset. This method will not throw if {@link + * #hasPathOrNull(String)} returned true for the same path, so + * to avoid any possible exception check + * hasPathOrNull() first. However, an exception + * for unset paths will usually be the right thing (because a + * reference.conf should exist that has the path + * set, the path should never be unset unless something is + * broken). + * + *

+ * Note that path expressions have a syntax and sometimes require quoting + * (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). + * + * @param path + * the path expression + * @return true if the value exists and is null, false if it + * exists and is not null + * @throws ConfigException.BadPath + * if the path expression is invalid + * @throws ConfigException.Missing + * if value is not set at all + */ + boolean getIsNull(String path); + + /** + * + * @param path + * path expression + * @return the boolean value at the requested path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to boolean + */ + boolean getBoolean(String path); + + /** + * @param path + * path expression + * @return the numeric value at the requested path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a number + */ + Number getNumber(String path); + + /** + * Gets the integer at the given path. If the value at the + * path has a fractional (floating point) component, it + * will be discarded and only the integer part will be + * returned (it works like a "narrowing primitive conversion" + * in the Java language specification). + * + * @param path + * path expression + * @return the 32-bit integer value at the requested path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to an int (for example it is out + * of range, or it's a boolean value) + */ + int getInt(String path); + + /** + * Gets the long integer at the given path. If the value at + * the path has a fractional (floating point) component, it + * will be discarded and only the integer part will be + * returned (it works like a "narrowing primitive conversion" + * in the Java language specification). + * + * @param path + * path expression + * @return the 64-bit long value at the requested path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a long + */ + long getLong(String path); + + /** + * @param path + * path expression + * @return the floating-point value at the requested path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a double + */ + double getDouble(String path); + + /** + * @param path + * path expression + * @return the string value at the requested path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a string + */ + String getString(String path); + + /** + * @param enumClass + * an enum class + * @param + * a generic denoting a specific type of enum + * @param path + * path expression + * @return the {@code Enum} value at the requested path + * of the requested enum class + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to an Enum + */ + public > T getEnum(Class enumClass, String path); + + /** + * @param path + * path expression + * @return the {@link ConfigObject} value at the requested path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to an object + */ + ConfigObject getObject(String path); + + /** + * @param path + * path expression + * @return the nested {@code Config} value at the requested path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a Config + */ + Config getConfig(String path); + + /** + * Gets the value at the path as an unwrapped Java boxed value ( + * {@link java.lang.Boolean Boolean}, {@link java.lang.Integer Integer}, and + * so on - see {@link ConfigValue#unwrapped()}). + * + * @param path + * path expression + * @return the unwrapped value at the requested path + * @throws ConfigException.Missing + * if value is absent or null + */ + Object getAnyRef(String path); + + /** + * Gets the value at the given path, unless the value is a + * null value or missing, in which case it throws just like + * the other getters. Use {@code get()} on the {@link + * Config#root()} object (or other object in the tree) if you + * want an unprocessed value. + * + * @param path + * path expression + * @return the value at the requested path + * @throws ConfigException.Missing + * if value is absent or null + */ + ConfigValue getValue(String path); + + /** + * Gets a value as a size in bytes (parses special strings like "128M"). If + * the value is already a number, then it's left alone; if it's a string, + * it's parsed understanding unit suffixes such as "128K", as documented in + * the the + * spec. + * + * @param path + * path expression + * @return the value at the requested path, in bytes + * @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 size in bytes + */ + Long getBytes(String path); + + /** + * Gets a value as an amount of memory (parses special strings like "128M"). If + * the value is already a number, then it's left alone; if it's a string, + * it's parsed understanding unit suffixes such as "128K", as documented in + * the the + * spec. + * + * @since 1.3.0 + * + * @param path + * path expression + * @return the value at the requested path, in bytes + * @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 size in bytes + */ + ConfigMemorySize getMemorySize(String path); + + /** + * Get value as a duration in milliseconds. If the value is already a + * number, then it's left alone; if it's a string, it's parsed understanding + * units suffixes like "10m" or "5ns" as documented in the the + * spec. + * + * @deprecated As of release 1.1, replaced by {@link #getDuration(String, TimeUnit)} + * + * @param path + * path expression + * @return the duration value at the requested path, in milliseconds + * @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 milliseconds + */ + @Deprecated Long getMilliseconds(String path); + + /** + * Get value as a duration in nanoseconds. If the value is already a number + * it's taken as milliseconds and converted to nanoseconds. If it's a + * string, it's parsed understanding unit suffixes, as for + * {@link #getDuration(String, TimeUnit)}. + * + * @deprecated As of release 1.1, replaced by {@link #getDuration(String, TimeUnit)} + * + * @param path + * path expression + * @return the duration value at the requested path, in nanoseconds + * @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 nanoseconds + */ + @Deprecated Long getNanoseconds(String path); + + /** + * Gets a value as a duration in a specified + * {@link java.util.concurrent.TimeUnit TimeUnit}. If the value is already a + * number, then it's taken as milliseconds and then converted to the + * requested TimeUnit; if it's a string, it's parsed understanding units + * suffixes like "10m" or "5ns" as documented in the the + * spec. + * + * @since 1.2.0 + * + * @param path + * path expression + * @param unit + * convert the return value to this time unit + * @return the duration value at the requested path, in the given TimeUnit + * @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 + */ + long getDuration(String path, TimeUnit unit); + + /** + * Gets a value as a java.time.Duration. If the value is + * already a number, then it's taken as milliseconds; if it's + * a string, it's parsed understanding units suffixes like + * "10m" or "5ns" as documented in the the + * spec. This method never returns null. + * + * @since 1.3.0 + * + * @param path + * path expression + * @return the duration 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 + */ + 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 the + * spec. 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}. Throws if the path is + * unset or null. + * + * @param path + * the path to the list value. + * @return the {@link ConfigList} at the path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a ConfigList + */ + ConfigList getList(String path); + + /** + * Gets a list value with boolean elements. Throws if the + * path is unset or null or not a list or contains values not + * convertible to boolean. + * + * @param path + * the path to the list value. + * @return the list at the path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a list of booleans + */ + List getBooleanList(String path); + + /** + * Gets a list value with number elements. Throws if the + * path is unset or null or not a list or contains values not + * convertible to number. + * + * @param path + * the path to the list value. + * @return the list at the path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a list of numbers + */ + List getNumberList(String path); + + /** + * Gets a list value with int elements. Throws if the + * path is unset or null or not a list or contains values not + * convertible to int. + * + * @param path + * the path to the list value. + * @return the list at the path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a list of ints + */ + List getIntList(String path); + + /** + * Gets a list value with long elements. Throws if the + * path is unset or null or not a list or contains values not + * convertible to long. + * + * @param path + * the path to the list value. + * @return the list at the path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a list of longs + */ + List getLongList(String path); + + /** + * Gets a list value with double elements. Throws if the + * path is unset or null or not a list or contains values not + * convertible to double. + * + * @param path + * the path to the list value. + * @return the list at the path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a list of doubles + */ + List getDoubleList(String path); + + /** + * Gets a list value with string elements. Throws if the + * path is unset or null or not a list or contains values not + * convertible to string. + * + * @param path + * the path to the list value. + * @return the list at the path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a list of strings + */ + List getStringList(String path); + + /** + * Gets a list value with {@code Enum} elements. Throws if the + * path is unset or null or not a list or contains values not + * convertible to {@code Enum}. + * + * @param enumClass + * the enum class + * @param + * a generic denoting a specific type of enum + * @param path + * the path to the list value. + * @return the list at the path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a list of {@code Enum} + */ + > List getEnumList(Class enumClass, String path); + + /** + * Gets a list value with object elements. Throws if the + * path is unset or null or not a list or contains values not + * convertible to ConfigObject. + * + * @param path + * the path to the list value. + * @return the list at the path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a list of objects + */ + List getObjectList(String path); + + /** + * Gets a list value with Config elements. + * Throws if the path is unset or null or not a list or + * contains values not convertible to Config. + * + * @param path + * the path to the list value. + * @return the list at the path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a list of configs + */ + List getConfigList(String path); + + /** + * Gets a list value with any kind of elements. Throws if the + * path is unset or null or not a list. Each element is + * "unwrapped" (see {@link ConfigValue#unwrapped()}). + * + * @param path + * the path to the list value. + * @return the list at the path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a list + */ + List getAnyRefList(String path); + + /** + * Gets a list value with elements representing a size in + * bytes. Throws if the path is unset or null or not a list + * or contains values not convertible to memory sizes. + * + * @param path + * the path to the list value. + * @return the list at the path + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a list of memory sizes + */ + List getBytesList(String path); + + /** + * Gets a list, converting each value in the list to a memory size, using the + * same rules as {@link #getMemorySize(String)}. + * + * @since 1.3.0 + * @param path + * a path expression + * @return list of memory sizes + * @throws ConfigException.Missing + * if value is absent or null + * @throws ConfigException.WrongType + * if value is not convertible to a list of memory sizes + */ + List getMemorySizeList(String path); + + /** + * @deprecated As of release 1.1, replaced by {@link #getDurationList(String, TimeUnit)} + * @param path the path + * @return list of millisecond values + */ + @Deprecated List getMillisecondsList(String path); + + /** + * @deprecated As of release 1.1, replaced by {@link #getDurationList(String, TimeUnit)} + * @param path the path + * @return list of nanosecond values + */ + @Deprecated List getNanosecondsList(String path); + + /** + * Gets a list, converting each value in the list to a duration, using the + * same rules as {@link #getDuration(String, TimeUnit)}. + * + * @since 1.2.0 + * @param path + * a path expression + * @param unit + * time units of the returned values + * @return list of durations, in the requested units + */ + List getDurationList(String path, TimeUnit unit); + + /** + * Gets a list, converting each value in the list to a duration, using the + * same rules as {@link #getDuration(String)}. + * + * @since 1.3.0 + * @param path + * a path expression + * @return list of durations + */ + List getDurationList(String path); + + /** + * Clone the config with only the given path (and its children) retained; + * all sibling paths are removed. + *

+ * Note that path expressions have a syntax and sometimes require quoting + * (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). + * + * @param path + * path to keep + * @return a copy of the config minus all paths except the one specified + */ + Config withOnlyPath(String path); + + /** + * Clone the config with the given path removed. + *

+ * Note that path expressions have a syntax and sometimes require quoting + * (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). + * + * @param path + * path expression to remove + * @return a copy of the config minus the specified path + */ + Config withoutPath(String path); + + /** + * Places the config inside another {@code Config} at the given path. + *

+ * Note that path expressions have a syntax and sometimes require quoting + * (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). + * + * @param path + * path expression to store this config at. + * @return a {@code Config} instance containing this config at the given + * path. + */ + Config atPath(String path); + + /** + * Places the config inside a {@code Config} at the given key. See also + * atPath(). Note that a key is NOT a path expression (see + * {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). + * + * @param key + * key to store this config at. + * @return a {@code Config} instance containing this config at the given + * key. + */ + Config atKey(String key); + + /** + * Returns a {@code Config} based on this one, but with the given path set + * to the given value. Does not modify this instance (since it's immutable). + * If the path already has a value, that value is replaced. To remove a + * value, use withoutPath(). + *

+ * Note that path expressions have a syntax and sometimes require quoting + * (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). + * + * @param path + * path expression for the value's new location + * @param value + * value at the new path + * @return the new instance with the new map entry + */ + Config withValue(String path, ConfigValue value); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigBeanFactory.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigBeanFactory.java new file mode 100644 index 0000000..d246dc2 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigBeanFactory.java @@ -0,0 +1,49 @@ +package com.drtshock.playervaults.lib.com.typesafe.config; + +import com.drtshock.playervaults.lib.com.typesafe.config.impl.ConfigBeanImpl; + +/** + * Factory for automatically creating a Java class from a {@link Config}. + * See {@link #create(Config,Class)}. + * + * @since 1.3.0 + */ +public class ConfigBeanFactory { + + /** + * Creates an instance of a class, initializing its fields from a {@link Config}. + * + * Example usage: + * + *

+     * Config configSource = ConfigFactory.load().getConfig("foo");
+     * FooConfig config = ConfigBeanFactory.create(configSource, FooConfig.class);
+     * 
+ * + * The Java class should follow JavaBean conventions. Field types + * can be any of the types you can normally get from a {@link + * Config}, including java.time.Duration or {@link + * ConfigMemorySize}. Fields may also be another JavaBean-style + * class. + * + * Fields are mapped to config by converting the config key to + * camel case. So the key foo-bar becomes JavaBean + * setter setFooBar. + * + * @since 1.3.0 + * + * @param config source of config information + * @param clazz class to be instantiated + * @param the type of the class to be instantiated + * @return an instance of the class populated with data from the config + * @throws ConfigException.BadBean + * If something is wrong with the JavaBean + * @throws ConfigException.ValidationFailed + * If the config doesn't conform to the bean's implied schema + * @throws ConfigException + * Can throw the same exceptions as the getters on Config + */ + public static T create(Config config, Class clazz) { + return ConfigBeanImpl.createInternal(config, clazz); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigException.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigException.java new file mode 100644 index 0000000..d5feb6c --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigException.java @@ -0,0 +1,447 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Field; + +import com.drtshock.playervaults.lib.com.typesafe.config.impl.ConfigImplUtil; + +/** + * All exceptions thrown by the library are subclasses of + * ConfigException. + */ +public abstract class ConfigException extends RuntimeException implements Serializable { + private static final long serialVersionUID = 1L; + + final private transient ConfigOrigin origin; + + protected ConfigException(ConfigOrigin origin, String message, + Throwable cause) { + super(origin.description() + ": " + message, cause); + this.origin = origin; + } + + protected ConfigException(ConfigOrigin origin, String message) { + this(origin.description() + ": " + message, null); + } + + protected ConfigException(String message, Throwable cause) { + super(message, cause); + this.origin = null; + } + + protected ConfigException(String message) { + this(message, null); + } + + /** + * Returns an "origin" (such as a filename and line number) for the + * exception, or null if none is available. If there's no sensible origin + * for a given exception, or the kind of exception doesn't meaningfully + * relate to a particular origin file, this returns null. Never assume this + * will return non-null, it can always return null. + * + * @return origin of the problem, or null if unknown/inapplicable + */ + public ConfigOrigin origin() { + return origin; + } + + // we customize serialization because ConfigOrigin isn't + // serializable and we don't want it to be (don't want to + // support it) + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + ConfigImplUtil.writeOrigin(out, origin); + } + + // For deserialization - uses reflection to set the final origin field on the object + private static void setOriginField(T hasOriginField, Class clazz, + ConfigOrigin origin) throws IOException { + // circumvent "final" + Field f; + try { + f = clazz.getDeclaredField("origin"); + } catch (NoSuchFieldException e) { + throw new IOException(clazz.getSimpleName() + " has no origin field?", e); + } catch (SecurityException e) { + throw new IOException("unable to fill out origin field in " + + clazz.getSimpleName(), e); + } + f.setAccessible(true); + try { + f.set(hasOriginField, origin); + } catch (IllegalArgumentException e) { + throw new IOException("unable to set origin field", e); + } catch (IllegalAccessException e) { + throw new IOException("unable to set origin field", e); + } + } + + private void readObject(java.io.ObjectInputStream in) throws IOException, + ClassNotFoundException { + in.defaultReadObject(); + ConfigOrigin origin = ConfigImplUtil.readOrigin(in); + setOriginField(this, ConfigException.class, origin); + } + + /** + * Exception indicating that the type of a value does not match the type you + * requested. + * + */ + public static class WrongType extends ConfigException { + private static final long serialVersionUID = 1L; + + public WrongType(ConfigOrigin origin, String path, String expected, String actual, + Throwable cause) { + super(origin, path + " has type " + actual + " rather than " + expected, cause); + } + + public WrongType(ConfigOrigin origin, String path, String expected, String actual) { + this(origin, path, expected, actual, null); + } + + public WrongType(ConfigOrigin origin, String message, Throwable cause) { + super(origin, message, cause); + } + + public WrongType(ConfigOrigin origin, String message) { + super(origin, message, null); + } + } + + /** + * Exception indicates that the setting was never set to anything, not even + * null. + */ + public static class Missing extends ConfigException { + private static final long serialVersionUID = 1L; + + public Missing(String path, Throwable cause) { + super("No configuration setting found for key '" + path + "'", + cause); + } + + public Missing(String path) { + this(path, null); + } + + protected Missing(ConfigOrigin origin, String message, Throwable cause) { + super(origin, message, cause); + } + + protected Missing(ConfigOrigin origin, String message) { + this(origin, message, null); + } + } + + /** + * Exception indicates that the setting was treated as missing because it + * was set to null. + */ + public static class Null extends Missing { + private static final long serialVersionUID = 1L; + + private static String makeMessage(String path, String expected) { + if (expected != null) { + return "Configuration key '" + path + + "' is set to null but expected " + expected; + } else { + return "Configuration key '" + path + "' is null"; + } + } + + public Null(ConfigOrigin origin, String path, String expected, + Throwable cause) { + super(origin, makeMessage(path, expected), cause); + } + + public Null(ConfigOrigin origin, String path, String expected) { + this(origin, path, expected, null); + } + } + + /** + * Exception indicating that a value was messed up, for example you may have + * asked for a duration and the value can't be sensibly parsed as a + * duration. + * + */ + public static class BadValue extends ConfigException { + private static final long serialVersionUID = 1L; + + public BadValue(ConfigOrigin origin, String path, String message, + Throwable cause) { + super(origin, "Invalid value at '" + path + "': " + message, cause); + } + + public BadValue(ConfigOrigin origin, String path, String message) { + this(origin, path, message, null); + } + + public BadValue(String path, String message, Throwable cause) { + super("Invalid value at '" + path + "': " + message, cause); + } + + public BadValue(String path, String message) { + this(path, message, null); + } + } + + /** + * Exception indicating that a path expression was invalid. Try putting + * double quotes around path elements that contain "special" characters. + * + */ + public static class BadPath extends ConfigException { + private static final long serialVersionUID = 1L; + + public BadPath(ConfigOrigin origin, String path, String message, + Throwable cause) { + super(origin, + path != null ? ("Invalid path '" + path + "': " + message) + : message, cause); + } + + public BadPath(ConfigOrigin origin, String path, String message) { + this(origin, path, message, null); + } + + public BadPath(String path, String message, Throwable cause) { + super(path != null ? ("Invalid path '" + path + "': " + message) + : message, cause); + } + + public BadPath(String path, String message) { + this(path, message, null); + } + + public BadPath(ConfigOrigin origin, String message) { + this(origin, null, message); + } + } + + /** + * Exception indicating that there's a bug in something (possibly the + * library itself) or the runtime environment is broken. This exception + * should never be handled; instead, something should be fixed to keep the + * exception from occurring. This exception can be thrown by any method in + * the library. + */ + public static class BugOrBroken extends ConfigException { + private static final long serialVersionUID = 1L; + + public BugOrBroken(String message, Throwable cause) { + super(message, cause); + } + + public BugOrBroken(String message) { + this(message, null); + } + } + + /** + * Exception indicating that there was an IO error. + * + */ + public static class IO extends ConfigException { + private static final long serialVersionUID = 1L; + + public IO(ConfigOrigin origin, String message, Throwable cause) { + super(origin, message, cause); + } + + public IO(ConfigOrigin origin, String message) { + this(origin, message, null); + } + } + + /** + * Exception indicating that there was a parse error. + * + */ + public static class Parse extends ConfigException { + private static final long serialVersionUID = 1L; + + public Parse(ConfigOrigin origin, String message, Throwable cause) { + super(origin, message, cause); + } + + public Parse(ConfigOrigin origin, String message) { + this(origin, message, null); + } + } + + /** + * Exception indicating that a substitution did not resolve to anything. + * Thrown by {@link Config#resolve}. + */ + public static class UnresolvedSubstitution extends Parse { + private static final long serialVersionUID = 1L; + + public UnresolvedSubstitution(ConfigOrigin origin, String detail, Throwable cause) { + super(origin, "Could not resolve substitution to a value: " + detail, cause); + } + + public UnresolvedSubstitution(ConfigOrigin origin, String detail) { + this(origin, detail, null); + } + } + + /** + * Exception indicating that you tried to use a function that requires + * substitutions to be resolved, but substitutions have not been resolved + * (that is, {@link Config#resolve} was not called). This is always a bug in + * either application code or the library; it's wrong to write a handler for + * this exception because you should be able to fix the code to avoid it by + * adding calls to {@link Config#resolve}. + */ + public static class NotResolved extends BugOrBroken { + private static final long serialVersionUID = 1L; + + public NotResolved(String message, Throwable cause) { + super(message, cause); + } + + public NotResolved(String message) { + this(message, null); + } + } + + /** + * Information about a problem that occurred in {@link Config#checkValid}. A + * {@link ConfigException.ValidationFailed} exception thrown from + * checkValid() includes a list of problems encountered. + */ + public static class ValidationProblem implements Serializable { + + final private String path; + final private transient ConfigOrigin origin; + final private String problem; + + public ValidationProblem(String path, ConfigOrigin origin, String problem) { + this.path = path; + this.origin = origin; + this.problem = problem; + } + + /** + * Returns the config setting causing the problem. + * @return the path of the problem setting + */ + public String path() { + return path; + } + + /** + * Returns where the problem occurred (origin may include info on the + * file, line number, etc.). + * @return the origin of the problem setting + */ + public ConfigOrigin origin() { + return origin; + } + + /** + * Returns a description of the problem. + * @return description of the problem + */ + public String problem() { + return problem; + } + + // We customize serialization because ConfigOrigin isn't + // serializable and we don't want it to be + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + ConfigImplUtil.writeOrigin(out, origin); + } + + private void readObject(java.io.ObjectInputStream in) throws IOException, + ClassNotFoundException { + in.defaultReadObject(); + ConfigOrigin origin = ConfigImplUtil.readOrigin(in); + setOriginField(this, ValidationProblem.class, origin); + } + + @Override + public String toString() { + return "ValidationProblem(" + path + "," + origin + "," + problem + ")"; + } + } + + /** + * Exception indicating that {@link Config#checkValid} found validity + * problems. The problems are available via the {@link #problems()} method. + * The getMessage() of this exception is a potentially very + * long string listing all the problems found. + */ + public static class ValidationFailed extends ConfigException { + private static final long serialVersionUID = 1L; + + final private Iterable problems; + + public ValidationFailed(Iterable problems) { + super(makeMessage(problems), null); + this.problems = problems; + } + + public Iterable problems() { + return problems; + } + + private static String makeMessage(Iterable problems) { + StringBuilder sb = new StringBuilder(); + for (ValidationProblem p : problems) { + sb.append(p.origin().description()); + sb.append(": "); + sb.append(p.path()); + sb.append(": "); + sb.append(p.problem()); + sb.append(", "); + } + if (sb.length() == 0) + throw new ConfigException.BugOrBroken( + "ValidationFailed must have a non-empty list of problems"); + sb.setLength(sb.length() - 2); // chop comma and space + + return sb.toString(); + } + } + + /** + * Some problem with a JavaBean we are trying to initialize. + * @since 1.3.0 + */ + public static class BadBean extends BugOrBroken { + private static final long serialVersionUID = 1L; + + public BadBean(String message, Throwable cause) { + super(message, cause); + } + + public BadBean(String message) { + this(message, null); + } + } + + /** + * Exception that doesn't fall into any other category. + */ + public static class Generic extends ConfigException { + private static final long serialVersionUID = 1L; + + public Generic(String message, Throwable cause) { + super(message, cause); + } + + public Generic(String message) { + this(message, null); + } + } + +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigFactory.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigFactory.java new file mode 100644 index 0000000..eecb9e1 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigFactory.java @@ -0,0 +1,1066 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +import com.drtshock.playervaults.lib.com.typesafe.config.impl.ConfigImpl; +import com.drtshock.playervaults.lib.com.typesafe.config.impl.Parseable; + +import java.io.File; +import java.io.Reader; +import java.net.URL; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Callable; + +/** + * Contains static methods for creating {@link Config} instances. + * + *

+ * See also {@link ConfigValueFactory} which contains static methods for + * converting Java values into a {@link ConfigObject}. You can then convert a + * {@code ConfigObject} into a {@code Config} with {@link ConfigObject#toConfig}. + * + *

+ * The static methods with "load" in the name do some sort of higher-level + * operation potentially parsing multiple resources and resolving substitutions, + * while the ones with "parse" in the name just create a {@link ConfigValue} + * from a resource and nothing else. + * + *

You can find an example app and library on + * GitHub. Also be sure to read the package + * overview which describes the big picture as shown in those + * examples. + */ +public final class ConfigFactory { + private static final String STRATEGY_PROPERTY_NAME = "config.strategy"; + + private ConfigFactory() { + } + + /** + * Loads an application's configuration from the given classpath resource or + * classpath resource basename, sandwiches it between default reference + * config and default overrides, and then resolves it. The classpath + * resource is "raw" (it should have no "/" prefix, and is not made relative + * to any package, so it's like {@link ClassLoader#getResource} not + * {@link Class#getResource}). + * + *

+ * Resources are loaded from the current thread's + * {@link Thread#getContextClassLoader()}. In general, a library needs its + * configuration to come from the class loader used to load that library, so + * the proper "reference.conf" are present. + * + *

+ * The loaded object will already be resolved (substitutions have already + * been processed). As a result, if you add more fallbacks then they won't + * be seen by substitutions. Substitutions are the "${foo.bar}" syntax. If + * you want to parse additional files or something then you need to use + * {@link #load(Config)}. + * + *

+ * To load a standalone resource (without the default reference and default + * overrides), use {@link #parseResourcesAnySyntax(String)} rather than this + * method. To load only the reference config use {@link #defaultReference()} + * and to load only the overrides use {@link #defaultOverrides()}. + * + * @param resourceBasename + * name (optionally without extension) of a resource on classpath + * @return configuration for an application relative to context class loader + */ + public static Config load(String resourceBasename) { + return load(resourceBasename, ConfigParseOptions.defaults(), + ConfigResolveOptions.defaults()); + } + + /** + * Like {@link #load(String)} but uses the supplied class loader instead of + * the current thread's context class loader. + * + *

+ * To load a standalone resource (without the default reference and default + * overrides), use {@link #parseResourcesAnySyntax(ClassLoader, String)} + * rather than this method. To load only the reference config use + * {@link #defaultReference(ClassLoader)} and to load only the overrides use + * {@link #defaultOverrides(ClassLoader)}. + * + * @param loader class loader to look for resources in + * @param resourceBasename basename (no .conf/.json/.properties suffix) + * @return configuration for an application relative to given class loader + */ + public static Config load(ClassLoader loader, String resourceBasename) { + return load(resourceBasename, ConfigParseOptions.defaults().setClassLoader(loader), + ConfigResolveOptions.defaults()); + } + + /** + * Like {@link #load(String)} but allows you to specify parse and resolve + * options. + * + * @param resourceBasename + * the classpath resource name with optional extension + * @param parseOptions + * options to use when parsing the resource + * @param resolveOptions + * options to use when resolving the stack + * @return configuration for an application + */ + public static Config load(String resourceBasename, ConfigParseOptions parseOptions, + ConfigResolveOptions resolveOptions) { + ConfigParseOptions withLoader = ensureClassLoader(parseOptions, "load"); + Config appConfig = ConfigFactory.parseResourcesAnySyntax(resourceBasename, withLoader); + return load(withLoader.getClassLoader(), appConfig, resolveOptions); + } + + /** + * Like {@link #load(String,ConfigParseOptions,ConfigResolveOptions)} but + * has a class loader parameter that overrides any from the + * {@code ConfigParseOptions}. + * + * @param loader + * class loader in which to find resources (overrides loader in + * parse options) + * @param resourceBasename + * the classpath resource name with optional extension + * @param parseOptions + * options to use when parsing the resource (class loader + * overridden) + * @param resolveOptions + * options to use when resolving the stack + * @return configuration for an application + */ + public static Config load(ClassLoader loader, String resourceBasename, + ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) { + return load(resourceBasename, parseOptions.setClassLoader(loader), resolveOptions); + } + + private static ClassLoader checkedContextClassLoader(String methodName) { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + if (loader == null) + throw new ConfigException.BugOrBroken("Context class loader is not set for the current thread; " + + "if Thread.currentThread().getContextClassLoader() returns null, you must pass a ClassLoader " + + "explicitly to ConfigFactory." + methodName); + else + return loader; + } + + private static ConfigParseOptions ensureClassLoader(ConfigParseOptions options, String methodName) { + if (options.getClassLoader() == null) + return options.setClassLoader(checkedContextClassLoader(methodName)); + else + return options; + } + + /** + * Assembles a standard configuration using a custom Config + * object rather than loading "application.conf". The Config + * object will be sandwiched between the default reference config and + * default overrides and then resolved. + * + * @param config + * the application's portion of the configuration + * @return resolved configuration with overrides and fallbacks added + */ + public static Config load(Config config) { + return load(checkedContextClassLoader("load"), config); + } + + /** + * Like {@link #load(Config)} but allows you to specify + * the class loader for looking up resources. + * + * @param loader + * the class loader to use to find resources + * @param config + * the application's portion of the configuration + * @return resolved configuration with overrides and fallbacks added + */ + public static Config load(ClassLoader loader, Config config) { + return load(loader, config, ConfigResolveOptions.defaults()); + } + + /** + * Like {@link #load(Config)} but allows you to specify + * {@link ConfigResolveOptions}. + * + * @param config + * the application's portion of the configuration + * @param resolveOptions + * options for resolving the assembled config stack + * @return resolved configuration with overrides and fallbacks added + */ + public static Config load(Config config, ConfigResolveOptions resolveOptions) { + return load(checkedContextClassLoader("load"), config, resolveOptions); + } + + /** + * Like {@link #load(Config,ConfigResolveOptions)} but allows you to specify + * a class loader other than the context class loader. + * + * @param loader + * class loader to use when looking up override and reference + * configs + * @param config + * the application's portion of the configuration + * @param resolveOptions + * options for resolving the assembled config stack + * @return resolved configuration with overrides and fallbacks added + */ + public static Config load(ClassLoader loader, Config config, ConfigResolveOptions resolveOptions) { + return defaultOverrides(loader).withFallback(config).withFallback(defaultReference(loader)) + .resolve(resolveOptions); + } + + + + /** + * Loads a default configuration, equivalent to {@link #load(Config) + * load(defaultApplication())} in most cases. This configuration should be used by + * libraries and frameworks unless an application provides a different one. + *

+ * This method may return a cached singleton so will not see changes to + * system properties or config files. (Use {@link #invalidateCaches()} to + * force it to reload.) + * + * @return configuration for an application + */ + public static Config load() { + ClassLoader loader = checkedContextClassLoader("load"); + return load(loader); + } + + /** + * Like {@link #load()} but allows specifying parse options. + * + * @param parseOptions + * Options for parsing resources + * @return configuration for an application + */ + public static Config load(ConfigParseOptions parseOptions) { + return load(parseOptions, ConfigResolveOptions.defaults()); + } + + /** + * Like {@link #load()} but allows specifying a class loader other than the + * thread's current context class loader. + * + * @param loader + * class loader for finding resources + * @return configuration for an application + */ + public static Config load(final ClassLoader loader) { + final ConfigParseOptions withLoader = ConfigParseOptions.defaults().setClassLoader(loader); + return ConfigImpl.computeCachedConfig(loader, "load", new Callable() { + @Override + public Config call() { + return load(loader, defaultApplication(withLoader)); + } + }); + } + + /** + * Like {@link #load()} but allows specifying a class loader other than the + * thread's current context class loader and also specify parse options. + * + * @param loader + * class loader for finding resources (overrides any loader in parseOptions) + * @param parseOptions + * Options for parsing resources + * @return configuration for an application + */ + public static Config load(ClassLoader loader, ConfigParseOptions parseOptions) { + return load(parseOptions.setClassLoader(loader)); + } + + /** + * Like {@link #load()} but allows specifying a class loader other than the + * thread's current context class loader and also specify resolve options. + * + * @param loader + * class loader for finding resources + * @param resolveOptions + * options for resolving the assembled config stack + * @return configuration for an application + */ + public static Config load(ClassLoader loader, ConfigResolveOptions resolveOptions) { + return load(loader, ConfigParseOptions.defaults(), resolveOptions); + } + + + /** + * Like {@link #load()} but allows specifying a class loader other than the + * thread's current context class loader, parse options, and resolve options. + * + * @param loader + * class loader for finding resources (overrides any loader in parseOptions) + * @param parseOptions + * Options for parsing resources + * @param resolveOptions + * options for resolving the assembled config stack + * @return configuration for an application + */ + public static Config load(ClassLoader loader, ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) { + final ConfigParseOptions withLoader = ensureClassLoader(parseOptions, "load"); + return load(loader, defaultApplication(withLoader), resolveOptions); + } + + /** + * Like {@link #load()} but allows specifying parse options and resolve + * options. + * + * @param parseOptions + * Options for parsing resources + * @param resolveOptions + * options for resolving the assembled config stack + * @return configuration for an application + * + * @since 1.3.0 + */ + public static Config load(ConfigParseOptions parseOptions, final ConfigResolveOptions resolveOptions) { + final ConfigParseOptions withLoader = ensureClassLoader(parseOptions, "load"); + return load(defaultApplication(withLoader), resolveOptions); + } + + /** + * Obtains the default reference configuration, which is currently created + * by merging all resources "reference.conf" found on the classpath and + * overriding the result with system properties. The returned reference + * configuration will already have substitutions resolved. + * + *

+ * Libraries and frameworks should ship with a "reference.conf" in their + * jar. + * + *

+ * The reference config must be looked up in the class loader that contains + * the libraries that you want to use with this config, so the + * "reference.conf" for each library can be found. Use + * {@link #defaultReference(ClassLoader)} if the context class loader is not + * suitable. + * + *

+ * The {@link #load()} methods merge this configuration for you + * automatically. + * + *

+ * Future versions may look for reference configuration in more places. It + * is not guaranteed that this method only looks at + * "reference.conf". + * + * @return the default reference config for context class loader + */ + public static Config defaultReference() { + return defaultReference(checkedContextClassLoader("defaultReference")); + } + + /** + * Like {@link #defaultReference()} but allows you to specify a class loader + * to use rather than the current context class loader. + * + * @param loader class loader to look for resources in + * @return the default reference config for this class loader + */ + public static Config defaultReference(ClassLoader loader) { + return ConfigImpl.defaultReference(loader); + } + + /** + * Obtains the default override configuration, which currently consists of + * system properties. The returned override configuration will already have + * substitutions resolved. + * + *

+ * The {@link #load()} methods merge this configuration for you + * automatically. + * + *

+ * Future versions may get overrides in more places. It is not guaranteed + * that this method only uses system properties. + * + * @return the default override configuration + */ + public static Config defaultOverrides() { + return systemProperties(); + } + + /** + * Like {@link #defaultOverrides()} but allows you to specify a class loader + * to use rather than the current context class loader. + * + * @param loader class loader to look for resources in + * @return the default override configuration + */ + public static Config defaultOverrides(ClassLoader loader) { + return systemProperties(); + } + + /** + * Obtains the default application-specific configuration, + * which defaults to parsing application.conf, + * application.json, and + * application.properties on the classpath, but + * can also be rerouted using the config.file, + * config.resource, and config.url + * system properties. + * + *

The no-arguments {@link #load()} method automatically + * stacks the {@link #defaultReference()}, {@link + * #defaultApplication()}, and {@link #defaultOverrides()} + * configs. You would use defaultApplication() + * directly only if you're somehow customizing behavior by + * reimplementing load(). + * + *

The configuration returned by + * defaultApplication() will not be resolved + * already, in contrast to defaultReference() and + * defaultOverrides(). This is because + * application.conf would normally be resolved after + * merging with the reference and override configs. + * + *

+ * If the system properties config.resource, + * config.file, or config.url are set, then the + * classpath resource, file, or URL specified in those properties will be + * used rather than the default + * application.{conf,json,properties} classpath resources. + * These system properties should not be set in code (after all, you can + * just parse whatever you want manually and then use {@link #load(Config)} + * if you don't want to use application.conf). The properties + * are intended for use by the person or script launching the application. + * For example someone might have a production.conf that + * include application.conf but then change a couple of values. + * When launching the app they could specify + * -Dconfig.resource=production.conf to get production mode. + * + *

+ * If no system properties are set to change the location of the default + * configuration, defaultApplication() is equivalent to + * ConfigFactory.parseResources("application"). + * + * @since 1.3.0 + * + * @return the default application.conf or system-property-configured configuration + */ + public static Config defaultApplication() { + return defaultApplication(ConfigParseOptions.defaults()); + } + + /** + * Like {@link #defaultApplication()} but allows you to specify a class loader + * to use rather than the current context class loader. + * + * @since 1.3.0 + * + * @param loader class loader to look for resources in + * @return the default application configuration + */ + public static Config defaultApplication(ClassLoader loader) { + return defaultApplication(ConfigParseOptions.defaults().setClassLoader(loader)); + } + + /** + * Like {@link #defaultApplication()} but allows you to specify parse options. + * + * @since 1.3.0 + * + * @param options the options + * @return the default application configuration + */ + public static Config defaultApplication(ConfigParseOptions options) { + return getConfigLoadingStrategy().parseApplicationConfig(ensureClassLoader(options, "defaultApplication")); + } + + /** + * Reloads any cached configs, picking up changes to system properties for + * example. Because a {@link Config} is immutable, anyone with a reference + * to the old configs will still have the same outdated objects. However, + * new calls to {@link #load()} or {@link #defaultOverrides()} or + * {@link #defaultReference} may return a new object. + *

+ * This method is primarily intended for use in unit tests, for example, + * that may want to update a system property then confirm that it's used + * correctly. In many cases, use of this method may indicate there's a + * better way to set up your code. + *

+ * Caches may be reloaded immediately or lazily; once you call this method, + * the reload can occur at any time, even during the invalidation process. + * So FIRST make the changes you'd like the caches to notice, then SECOND + * call this method to invalidate caches. Don't expect that invalidating, + * making changes, then calling {@link #load()}, will work. Make changes + * before you invalidate. + */ + public static void invalidateCaches() { + // We rely on this having the side effect that it drops + // all caches + ConfigImpl.reloadSystemPropertiesConfig(); + ConfigImpl.reloadEnvVariablesConfig(); + } + + /** + * Gets an empty configuration. See also {@link #empty(String)} to create an + * empty configuration with a description, which may improve user-visible + * error messages. + * + * @return an empty configuration + */ + public static Config empty() { + return empty(null); + } + + /** + * Gets an empty configuration with a description to be used to create a + * {@link ConfigOrigin} for this Config. The description should + * be very short and say what the configuration is, like "default settings" + * or "foo settings" or something. (Presumably you will merge some actual + * settings into this empty config using {@link Config#withFallback}, making + * the description more useful.) + * + * @param originDescription + * description of the config + * @return an empty configuration + */ + public static Config empty(String originDescription) { + return ConfigImpl.emptyConfig(originDescription); + } + + /** + * Gets a Config containing the system properties from + * {@link java.lang.System#getProperties()}, parsed and converted as with + * {@link #parseProperties}. + *

+ * This method can return a global immutable singleton, so it's preferred + * over parsing system properties yourself. + *

+ * {@link #load} will include the system properties as overrides already, as + * will {@link #defaultReference} and {@link #defaultOverrides}. + * + *

+ * Because this returns a singleton, it will not notice changes to system + * properties made after the first time this method is called. Use + * {@link #invalidateCaches()} to force the singleton to reload if you + * modify system properties. + * + * @return system properties parsed into a Config + */ + public static Config systemProperties() { + return ConfigImpl.systemPropertiesAsConfig(); + } + + /** + * Gets a Config containing the system's environment variables. + * This method can return a global immutable singleton. + * + *

+ * Environment variables are used as fallbacks when resolving substitutions + * whether or not this object is included in the config being resolved, so + * you probably don't need to use this method for most purposes. It can be a + * nicer API for accessing environment variables than raw + * {@link java.lang.System#getenv(String)} though, since you can use methods + * such as {@link Config#getInt}. + * + * @return system environment variables parsed into a Config + */ + public static Config systemEnvironment() { + return ConfigImpl.envVariablesAsConfig(); + } + + /** + * Converts a Java {@link java.util.Properties} object to a + * {@link ConfigObject} using the rules documented in the HOCON + * spec. The keys in the Properties object are split on the + * period character '.' and treated as paths. The values will all end up as + * string values. If you have both "a=foo" and "a.b=bar" in your properties + * file, so "a" is both the object containing "b" and the string "foo", then + * the string value is dropped. + * + *

+ * If you want to have System.getProperties() as a + * ConfigObject, it's better to use the {@link #systemProperties()} method + * which returns a cached global singleton. + * + * @param properties + * a Java Properties object + * @param options + * the parse options + * @return the parsed configuration + */ + public static Config parseProperties(Properties properties, + ConfigParseOptions options) { + return Parseable.newProperties(properties, options).parse().toConfig(); + } + + /** + * Like {@link #parseProperties(Properties, ConfigParseOptions)} but uses default + * parse options. + * @param properties + * a Java Properties object + * @return the parsed configuration + */ + public static Config parseProperties(Properties properties) { + return parseProperties(properties, ConfigParseOptions.defaults()); + } + + /** + * Parses a Reader into a Config instance. Does not call + * {@link Config#resolve} or merge the parsed stream with any + * other configuration; this method parses a single stream and + * does nothing else. It does process "include" statements in + * the parsed stream, and may end up doing other IO due to those + * statements. + * + * @param reader + * the reader to parse + * @param options + * parse options to control how the reader is interpreted + * @return the parsed configuration + * @throws ConfigException on IO or parse errors + */ + public static Config parseReader(Reader reader, ConfigParseOptions options) { + return Parseable.newReader(reader, options).parse().toConfig(); + } + + /** + * Parses a reader into a Config instance as with + * {@link #parseReader(Reader,ConfigParseOptions)} but always uses the + * default parse options. + * + * @param reader + * the reader to parse + * @return the parsed configuration + * @throws ConfigException on IO or parse errors + */ + public static Config parseReader(Reader reader) { + return parseReader(reader, ConfigParseOptions.defaults()); + } + + /** + * Parses a URL into a Config instance. Does not call + * {@link Config#resolve} or merge the parsed stream with any + * other configuration; this method parses a single stream and + * does nothing else. It does process "include" statements in + * the parsed stream, and may end up doing other IO due to those + * statements. + * + * @param url + * the url to parse + * @param options + * parse options to control how the url is interpreted + * @return the parsed configuration + * @throws ConfigException on IO or parse errors + */ + public static Config parseURL(URL url, ConfigParseOptions options) { + return Parseable.newURL(url, options).parse().toConfig(); + } + + /** + * Parses a url into a Config instance as with + * {@link #parseURL(URL,ConfigParseOptions)} but always uses the + * default parse options. + * + * @param url + * the url to parse + * @return the parsed configuration + * @throws ConfigException on IO or parse errors + */ + public static Config parseURL(URL url) { + return parseURL(url, ConfigParseOptions.defaults()); + } + + /** + * Parses a file into a Config instance. Does not call + * {@link Config#resolve} or merge the file with any other + * configuration; this method parses a single file and does + * nothing else. It does process "include" statements in the + * parsed file, and may end up doing other IO due to those + * statements. + * + * @param file + * the file to parse + * @param options + * parse options to control how the file is interpreted + * @return the parsed configuration + * @throws ConfigException on IO or parse errors + */ + public static Config parseFile(File file, ConfigParseOptions options) { + return Parseable.newFile(file, options).parse().toConfig(); + } + + /** + * Parses a file into a Config instance as with + * {@link #parseFile(File,ConfigParseOptions)} but always uses the + * default parse options. + * + * @param file + * the file to parse + * @return the parsed configuration + * @throws ConfigException on IO or parse errors + */ + public static Config parseFile(File file) { + return parseFile(file, ConfigParseOptions.defaults()); + } + + /** + * Parses a file with a flexible extension. If the fileBasename + * already ends in a known extension, this method parses it according to + * that extension (the file's syntax must match its extension). If the + * fileBasename does not end in an extension, it parses files + * with all known extensions and merges whatever is found. + * + *

+ * In the current implementation, the extension ".conf" forces + * {@link ConfigSyntax#CONF}, ".json" forces {@link ConfigSyntax#JSON}, and + * ".properties" forces {@link ConfigSyntax#PROPERTIES}. When merging files, + * ".conf" falls back to ".json" falls back to ".properties". + * + *

+ * Future versions of the implementation may add additional syntaxes or + * additional extensions. However, the ordering (fallback priority) of the + * three current extensions will remain the same. + * + *

+ * If options forces a specific syntax, this method only parses + * files with an extension matching that syntax. + * + *

+ * If {@link ConfigParseOptions#getAllowMissing options.getAllowMissing()} + * is true, then no files have to exist; if false, then at least one file + * has to exist. + * + * @param fileBasename + * a filename with or without extension + * @param options + * parse options + * @return the parsed configuration + */ + public static Config parseFileAnySyntax(File fileBasename, + ConfigParseOptions options) { + return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig(); + } + + /** + * Like {@link #parseFileAnySyntax(File,ConfigParseOptions)} but always uses + * default parse options. + * + * @param fileBasename + * a filename with or without extension + * @return the parsed configuration + */ + public static Config parseFileAnySyntax(File fileBasename) { + return parseFileAnySyntax(fileBasename, ConfigParseOptions.defaults()); + } + + /** + * Parses all resources on the classpath with the given name and merges them + * into a single Config. + * + *

+ * If the resource name does not begin with a "/", it will have the supplied + * class's package added to it, in the same way as + * {@link java.lang.Class#getResource}. + * + *

+ * Duplicate resources with the same name are merged such that ones returned + * earlier from {@link ClassLoader#getResources} fall back to (have higher + * priority than) the ones returned later. This implies that resources + * earlier in the classpath override those later in the classpath when they + * configure the same setting. However, in practice real applications may + * not be consistent about classpath ordering, so be careful. It may be best + * to avoid assuming too much. + * + * @param klass + * klass.getClassLoader() will be used to load + * resources, and non-absolute resource names will have this + * class's package added + * @param resource + * resource to look up, relative to klass's package + * or absolute starting with a "/" + * @param options + * parse options + * @return the parsed configuration + */ + public static Config parseResources(Class klass, String resource, + ConfigParseOptions options) { + return Parseable.newResources(klass, resource, options).parse() + .toConfig(); + } + + /** + * Like {@link #parseResources(Class,String,ConfigParseOptions)} but always uses + * default parse options. + * + * @param klass + * klass.getClassLoader() will be used to load + * resources, and non-absolute resource names will have this + * class's package added + * @param resource + * resource to look up, relative to klass's package + * or absolute starting with a "/" + * @return the parsed configuration + */ + public static Config parseResources(Class klass, String resource) { + return parseResources(klass, resource, ConfigParseOptions.defaults()); + } + + /** + * Parses classpath resources with a flexible extension. In general, this + * method has the same behavior as + * {@link #parseFileAnySyntax(File,ConfigParseOptions)} but for classpath + * resources instead, as in {@link #parseResources}. + * + *

+ * There is a thorny problem with this method, which is that + * {@link java.lang.ClassLoader#getResources} must be called separately for + * each possible extension. The implementation ends up with separate lists + * of resources called "basename.conf" and "basename.json" for example. As a + * result, the ideal ordering between two files with different extensions is + * unknown; there is no way to figure out how to merge the two lists in + * classpath order. To keep it simple, the lists are simply concatenated, + * with the same syntax priorities as + * {@link #parseFileAnySyntax(File,ConfigParseOptions) parseFileAnySyntax()} + * - all ".conf" resources are ahead of all ".json" resources which are + * ahead of all ".properties" resources. + * + * @param klass + * class which determines the ClassLoader and the + * package for relative resource names + * @param resourceBasename + * a resource name as in {@link java.lang.Class#getResource}, + * with or without extension + * @param options + * parse options (class loader is ignored in favor of the one + * from klass) + * @return the parsed configuration + */ + public static Config parseResourcesAnySyntax(Class klass, String resourceBasename, + ConfigParseOptions options) { + return ConfigImpl.parseResourcesAnySyntax(klass, resourceBasename, + options).toConfig(); + } + + /** + * Like {@link #parseResourcesAnySyntax(Class,String,ConfigParseOptions)} + * but always uses default parse options. + * + * @param klass + * klass.getClassLoader() will be used to load + * resources, and non-absolute resource names will have this + * class's package added + * @param resourceBasename + * a resource name as in {@link java.lang.Class#getResource}, + * with or without extension + * @return the parsed configuration + */ + public static Config parseResourcesAnySyntax(Class klass, String resourceBasename) { + return parseResourcesAnySyntax(klass, resourceBasename, ConfigParseOptions.defaults()); + } + + /** + * Parses all resources on the classpath with the given name and merges them + * into a single Config. + * + *

+ * This works like {@link java.lang.ClassLoader#getResource}, not like + * {@link java.lang.Class#getResource}, so the name never begins with a + * slash. + * + *

+ * See {@link #parseResources(Class,String,ConfigParseOptions)} for full + * details. + * + * @param loader + * will be used to load resources by setting this loader on the + * provided options + * @param resource + * resource to look up + * @param options + * parse options (class loader is ignored) + * @return the parsed configuration + */ + public static Config parseResources(ClassLoader loader, String resource, + ConfigParseOptions options) { + return parseResources(resource, options.setClassLoader(loader)); + } + + /** + * Like {@link #parseResources(ClassLoader,String,ConfigParseOptions)} but always uses + * default parse options. + * + * @param loader + * will be used to load resources + * @param resource + * resource to look up in the loader + * @return the parsed configuration + */ + public static Config parseResources(ClassLoader loader, String resource) { + return parseResources(loader, resource, ConfigParseOptions.defaults()); + } + + /** + * Parses classpath resources with a flexible extension. In general, this + * method has the same behavior as + * {@link #parseFileAnySyntax(File,ConfigParseOptions)} but for classpath + * resources instead, as in + * {@link #parseResources(ClassLoader,String,ConfigParseOptions)}. + * + *

+ * {@link #parseResourcesAnySyntax(Class,String,ConfigParseOptions)} differs + * in the syntax for the resource name, but otherwise see + * {@link #parseResourcesAnySyntax(Class,String,ConfigParseOptions)} for + * some details and caveats on this method. + * + * @param loader + * class loader to look up resources in, will be set on options + * @param resourceBasename + * a resource name as in + * {@link java.lang.ClassLoader#getResource}, with or without + * extension + * @param options + * parse options (class loader ignored) + * @return the parsed configuration + */ + public static Config parseResourcesAnySyntax(ClassLoader loader, String resourceBasename, + ConfigParseOptions options) { + return ConfigImpl.parseResourcesAnySyntax(resourceBasename, options.setClassLoader(loader)) + .toConfig(); + } + + /** + * Like {@link #parseResourcesAnySyntax(ClassLoader,String,ConfigParseOptions)} but always uses + * default parse options. + * + * @param loader + * will be used to load resources + * @param resourceBasename + * a resource name as in + * {@link java.lang.ClassLoader#getResource}, with or without + * extension + * @return the parsed configuration + */ + public static Config parseResourcesAnySyntax(ClassLoader loader, String resourceBasename) { + return parseResourcesAnySyntax(loader, resourceBasename, ConfigParseOptions.defaults()); + } + + /** + * Like {@link #parseResources(ClassLoader,String,ConfigParseOptions)} but + * uses thread's current context class loader if none is set in the + * ConfigParseOptions. + * @param resource the resource name + * @param options parse options + * @return the parsed configuration + */ + public static Config parseResources(String resource, ConfigParseOptions options) { + ConfigParseOptions withLoader = ensureClassLoader(options, "parseResources"); + return Parseable.newResources(resource, withLoader).parse().toConfig(); + } + + /** + * Like {@link #parseResources(ClassLoader,String)} but uses thread's + * current context class loader. + * @param resource the resource name + * @return the parsed configuration + */ + public static Config parseResources(String resource) { + return parseResources(resource, ConfigParseOptions.defaults()); + } + + /** + * Like + * {@link #parseResourcesAnySyntax(ClassLoader,String,ConfigParseOptions)} + * but uses thread's current context class loader. + * @param resourceBasename the resource basename (no file type suffix) + * @param options parse options + * @return the parsed configuration + */ + public static Config parseResourcesAnySyntax(String resourceBasename, ConfigParseOptions options) { + return ConfigImpl.parseResourcesAnySyntax(resourceBasename, options).toConfig(); + } + + /** + * Like {@link #parseResourcesAnySyntax(ClassLoader,String)} but uses + * thread's current context class loader. + * @param resourceBasename the resource basename (no file type suffix) + * @return the parsed configuration + */ + public static Config parseResourcesAnySyntax(String resourceBasename) { + return parseResourcesAnySyntax(resourceBasename, ConfigParseOptions.defaults()); + } + + /** + * Parses a string (which should be valid HOCON or JSON by default, or + * the syntax specified in the options otherwise). + * + * @param s string to parse + * @param options parse options + * @return the parsed configuration + */ + public static Config parseString(String s, ConfigParseOptions options) { + return Parseable.newString(s, options).parse().toConfig(); + } + + /** + * Parses a string (which should be valid HOCON or JSON). + * + * @param s string to parse + * @return the parsed configuration + */ + public static Config parseString(String s) { + return parseString(s, ConfigParseOptions.defaults()); + } + + /** + * Creates a {@code Config} based on a {@link java.util.Map} from paths to + * plain Java values. Similar to + * {@link ConfigValueFactory#fromMap(Map,String)}, except the keys in the + * map are path expressions, rather than keys; and correspondingly it + * returns a {@code Config} instead of a {@code ConfigObject}. This is more + * convenient if you are writing literal maps in code, and less convenient + * if you are getting your maps from some data source such as a parser. + * + *

+ * An exception will be thrown (and it is a bug in the caller of the method) + * if a path is both an object and a value, for example if you had both + * "a=foo" and "a.b=bar", then "a" is both the string "foo" and the parent + * object of "b". The caller of this method should ensure that doesn't + * happen. + * + * @param values map from paths to plain Java objects + * @param originDescription + * description of what this map represents, like a filename, or + * "default settings" (origin description is used in error + * messages) + * @return the map converted to a {@code Config} + */ + public static Config parseMap(Map values, + String originDescription) { + return ConfigImpl.fromPathMap(values, originDescription).toConfig(); + } + + /** + * See the other overload of {@link #parseMap(Map, String)} for details, + * this one just uses a default origin description. + * + * @param values map from paths to plain Java values + * @return the map converted to a {@code Config} + */ + public static Config parseMap(Map values) { + return parseMap(values, null); + } + + private static ConfigLoadingStrategy getConfigLoadingStrategy() { + String className = System.getProperties().getProperty(STRATEGY_PROPERTY_NAME); + + if (className != null) { + try { + return ConfigLoadingStrategy.class.cast(Class.forName(className).newInstance()); + } catch (Throwable e) { + throw new ConfigException.BugOrBroken("Failed to load strategy: " + className, e); + } + } else { + return new DefaultConfigLoadingStrategy(); + } + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncludeContext.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncludeContext.java new file mode 100644 index 0000000..42a5b87 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncludeContext.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + + +/** + * Context provided to a {@link ConfigIncluder}; this interface is only useful + * inside a {@code ConfigIncluder} implementation, and is not intended for apps + * to implement. + * + *

+ * Do not implement this interface; it should only be implemented by + * the config library. Arbitrary implementations will not work because the + * library internals assume a specific concrete implementation. Also, this + * interface is likely to grow new methods over time, so third-party + * implementations will break. + */ +public interface ConfigIncludeContext { + /** + * Tries to find a name relative to whatever is doing the including, for + * example in the same directory as the file doing the including. Returns + * null if it can't meaningfully create a relative name. The returned + * parseable may not exist; this function is not required to do any IO, just + * compute what the name would be. + * + * The passed-in filename has to be a complete name (with extension), not + * just a basename. (Include statements in config files are allowed to give + * just a basename.) + * + * @param filename + * the name to make relative to the resource doing the including + * @return parseable item relative to the resource doing the including, or + * null + */ + ConfigParseable relativeTo(String filename); + + /** + * Parse options to use (if you use another method to get a + * {@link ConfigParseable} then use {@link ConfigParseable#options()} + * instead though). + * + * @return the parse options + */ + ConfigParseOptions parseOptions(); + + + /** + * Copy this {@link ConfigIncludeContext} giving it a new value for its parseOptions. + * + * @param options new parse options to use + * + * @return the updated copy of this context + */ + ConfigIncludeContext setParseOptions(ConfigParseOptions options); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluder.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluder.java new file mode 100644 index 0000000..21db0dc --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluder.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +/** + * Implement this interface and provide an instance to + * {@link ConfigParseOptions#setIncluder ConfigParseOptions.setIncluder()} to + * customize handling of {@code include} statements in config files. You may + * also want to implement {@link ConfigIncluderClasspath}, + * {@link ConfigIncluderFile}, and {@link ConfigIncluderURL}, or not. + */ +public interface ConfigIncluder { + /** + * Returns a new includer that falls back to the given includer. This is how + * you can obtain the default includer; it will be provided as a fallback. + * It's up to your includer to chain to it if you want to. You might want to + * merge any files found by the fallback includer with any objects you load + * yourself. + * + * 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 includer + */ + ConfigIncluder withFallback(ConfigIncluder fallback); + + /** + * Parses another item to be included. The returned object typically would + * not have substitutions resolved. You can throw a ConfigException here to + * abort parsing, or return an empty object, but may not return null. + * + * This method is used for a "heuristic" include statement that does not + * specify file, URL, or classpath resource. If the include statement does + * specify, then the same class implementing {@link ConfigIncluder} must + * also implement {@link ConfigIncluderClasspath}, + * {@link ConfigIncluderFile}, or {@link ConfigIncluderURL} as needed, or a + * default includer will be used. + * + * @param context + * some info about the include context + * @param what + * the include statement's argument + * @return a non-null ConfigObject + */ + ConfigObject include(ConfigIncludeContext context, String what); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluderClasspath.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluderClasspath.java new file mode 100644 index 0000000..0903073 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluderClasspath.java @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +/** + * Implement this in addition to {@link ConfigIncluder} if you want to + * support inclusion of files with the {@code include classpath("resource")} + * syntax. If you do not implement this but do implement {@link ConfigIncluder}, + * attempts to load classpath resources will use the default includer. + */ +public interface ConfigIncluderClasspath { + /** + * Parses another item to be included. The returned object typically would + * not have substitutions resolved. You can throw a ConfigException here to + * abort parsing, or return an empty object, but may not return null. + * + * @param context + * some info about the include context + * @param what + * the include statement's argument + * @return a non-null ConfigObject + */ + ConfigObject includeResources(ConfigIncludeContext context, String what); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluderFile.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluderFile.java new file mode 100644 index 0000000..57f85f6 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluderFile.java @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +import java.io.File; + +/** + * Implement this in addition to {@link ConfigIncluder} if you want to + * support inclusion of files with the {@code include file("filename")} syntax. + * If you do not implement this but do implement {@link ConfigIncluder}, + * attempts to load files will use the default includer. + */ +public interface ConfigIncluderFile { + /** + * Parses another item to be included. The returned object typically would + * not have substitutions resolved. You can throw a ConfigException here to + * abort parsing, or return an empty object, but may not return null. + * + * @param context + * some info about the include context + * @param what + * the include statement's argument + * @return a non-null ConfigObject + */ + ConfigObject includeFile(ConfigIncludeContext context, File what); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluderURL.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluderURL.java new file mode 100644 index 0000000..c5c40e4 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigIncluderURL.java @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +import java.net.URL; + +/** + * Implement this in addition to {@link ConfigIncluder} if you want to + * support inclusion of files with the {@code include url("http://example.com")} + * syntax. If you do not implement this but do implement {@link ConfigIncluder}, + * attempts to load URLs will use the default includer. + */ +public interface ConfigIncluderURL { + /** + * Parses another item to be included. The returned object typically would + * not have substitutions resolved. You can throw a ConfigException here to + * abort parsing, or return an empty object, but may not return null. + * + * @param context + * some info about the include context + * @param what + * the include statement's argument + * @return a non-null ConfigObject + */ + ConfigObject includeURL(ConfigIncludeContext context, URL what); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigList.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigList.java new file mode 100644 index 0000000..dace555 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigList.java @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +import java.util.List; + +/** + * Subtype of {@link ConfigValue} representing a list value, as in JSON's + * {@code [1,2,3]} syntax. + * + *

+ * {@code ConfigList} implements {@code java.util.List} so you can + * use it like a regular Java list. Or call {@link #unwrapped()} to unwrap the + * list elements into plain Java values. + * + *

+ * Like all {@link ConfigValue} subtypes, {@code ConfigList} is immutable. This + * makes it threadsafe and you never have to create "defensive copies." The + * mutator methods from {@link java.util.List} all throw + * {@link java.lang.UnsupportedOperationException}. + * + *

+ * The {@link ConfigValue#valueType} method on a list returns + * {@link ConfigValueType#LIST}. + * + *

+ * Do not implement {@code ConfigList}; it should only be implemented + * by the config library. Arbitrary implementations will not work because the + * library internals assume a specific concrete implementation. Also, this + * interface is likely to grow new methods over time, so third-party + * implementations will break. + * + */ +public interface ConfigList extends List, ConfigValue { + + /** + * Recursively unwraps the list, returning a list of plain Java values such + * as Integer or String or whatever is in the list. + * + * @return a {@link java.util.List} containing plain Java objects + */ + @Override + List unwrapped(); + + @Override + ConfigList withOrigin(ConfigOrigin origin); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigLoadingStrategy.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigLoadingStrategy.java new file mode 100644 index 0000000..226d533 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigLoadingStrategy.java @@ -0,0 +1,20 @@ +package com.drtshock.playervaults.lib.com.typesafe.config; + +/** + * This method allows you to alter default config loading strategy for all the code which + * calls {@link ConfigFactory#load}. + * + * Usually you don't have to implement this interface but it may be required + * when you fixing a improperly implemented library with unavailable source code. + * + * You have to define VM property {@code config.strategy} to replace default strategy with your own. + */ +public interface ConfigLoadingStrategy { + /** + * This method must load and parse application config. + * + * @param parseOptions {@link ConfigParseOptions} to use + * @return loaded config + */ + Config parseApplicationConfig(ConfigParseOptions parseOptions); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigMemorySize.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigMemorySize.java new file mode 100644 index 0000000..319196a --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigMemorySize.java @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2015 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +/** + * An immutable class representing an amount of memory. Use + * static factory methods such as {@link + * ConfigMemorySize#ofBytes(long)} to create instances. + * + * @since 1.3.0 + */ +public final class ConfigMemorySize { + private final long bytes; + + private ConfigMemorySize(long bytes) { + if (bytes < 0) + throw new IllegalArgumentException("Attempt to construct ConfigMemorySize with negative number: " + bytes); + this.bytes = bytes; + } + + /** + * Constructs a ConfigMemorySize representing the given + * number of bytes. + * @since 1.3.0 + * @param bytes a number of bytes + * @return an instance representing the number of bytes + */ + public static ConfigMemorySize ofBytes(long bytes) { + return new ConfigMemorySize(bytes); + } + + /** + * Gets the size in bytes. + * @since 1.3.0 + * @return how many bytes + */ + public long toBytes() { + return bytes; + } + + @Override + public String toString() { + return "ConfigMemorySize(" + bytes + ")"; + } + + @Override + public boolean equals(Object other) { + if (other instanceof ConfigMemorySize) { + return ((ConfigMemorySize)other).bytes == this.bytes; + } else { + return false; + } + } + + @Override + public int hashCode() { + // in Java 8 this can become Long.hashCode(bytes) + return Long.valueOf(bytes).hashCode(); + } + +} + diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigMergeable.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigMergeable.java new file mode 100644 index 0000000..7159b1a --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigMergeable.java @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +/** + * Marker for types whose instances can be merged, that is {@link Config} and + * {@link ConfigValue}. Instances of {@code Config} and {@code ConfigValue} can + * be combined into a single new instance using the + * {@link ConfigMergeable#withFallback withFallback()} method. + * + *

+ * Do not implement this interface; it should only be implemented by + * the config library. Arbitrary implementations will not work because the + * library internals assume a specific concrete implementation. Also, this + * interface is likely to grow new methods over time, so third-party + * implementations will break. + */ +public interface ConfigMergeable { + /** + * Returns a new value computed by merging this value with another, with + * keys in this value "winning" over the other one. + * + *

+ * This associative operation may be used to combine configurations from + * multiple sources (such as multiple configuration files). + * + *

+ * The semantics of merging are described in the spec + * for HOCON. Merging typically occurs when either the same object is + * created twice in the same file, or two config files are both loaded. For + * example: + * + *

+     *  foo = { a: 42 }
+     *  foo = { b: 43 }
+     * 
+ * + * Here, the two objects are merged as if you had written: + * + *
+     *  foo = { a: 42, b: 43 }
+     * 
+ * + *

+ * Only {@link ConfigObject} and {@link Config} instances do anything in + * this method (they need to merge the fallback keys into themselves). All + * other values just return the original value, since they automatically + * override any fallback. This means that objects do not merge "across" + * non-objects; if you write + * object.withFallback(nonObject).withFallback(otherObject), + * then otherObject will simply be ignored. This is an + * intentional part of how merging works, because non-objects such as + * strings and integers replace (rather than merging with) any prior value: + * + *

+     * foo = { a: 42 }
+     * foo = 10
+     * 
+ * + * Here, the number 10 "wins" and the value of foo would be + * simply 10. Again, for details see the spec. + * + * @param other + * an object whose keys should be used as fallbacks, if the keys + * are not present in this one + * @return a new object (or the original one, if the fallback doesn't get + * used) + */ + ConfigMergeable withFallback(ConfigMergeable other); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigObject.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigObject.java new file mode 100644 index 0000000..2bfcfab --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigObject.java @@ -0,0 +1,135 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +import java.util.Map; + +/** + * Subtype of {@link ConfigValue} representing an object (AKA dictionary or map) + * value, as in JSON's curly brace { "a" : 42 } syntax. + * + *

+ * An object may also be viewed as a {@link Config} by calling + * {@link ConfigObject#toConfig()}. + * + *

+ * {@code ConfigObject} implements {@code java.util.Map} so + * you can use it like a regular Java map. Or call {@link #unwrapped()} to + * unwrap the map to a map with plain Java values rather than + * {@code ConfigValue}. + * + *

+ * Like all {@link ConfigValue} subtypes, {@code ConfigObject} is immutable. + * This makes it threadsafe and you never have to create "defensive copies." The + * mutator methods from {@link java.util.Map} all throw + * {@link java.lang.UnsupportedOperationException}. + * + *

+ * The {@link ConfigValue#valueType} method on an object returns + * {@link ConfigValueType#OBJECT}. + * + *

+ * In most cases you want to use the {@link Config} interface rather than this + * one. Call {@link #toConfig()} to convert a {@code ConfigObject} to a + * {@code Config}. + * + *

+ * The API for a {@code ConfigObject} is in terms of keys, while the API for a + * {@link Config} is in terms of path expressions. Conceptually, + * {@code ConfigObject} is a tree of maps from keys to values, while a + * {@code Config} is a one-level map from paths to values. + * + *

+ * Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert + * between path expressions and individual path elements (keys). + * + *

+ * A {@code ConfigObject} may contain null values, which will have + * {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If + * {@link ConfigObject#get(Object)} returns Java's null then the key was not + * present in the parsed file (or wherever this value tree came from). If + * {@code get("key")} returns a {@link ConfigValue} with type + * {@code ConfigValueType#NULL} then the key was set to null explicitly in the + * config file. + * + *

+ * Do not implement interface {@code ConfigObject}; it should only be + * implemented by the config library. Arbitrary implementations will not work + * because the library internals assume a specific concrete implementation. + * Also, this interface is likely to grow new methods over time, so third-party + * implementations will break. + */ +public interface ConfigObject extends ConfigValue, Map { + + /** + * Converts this object to a {@link Config} instance, enabling you to use + * path expressions to find values in the object. This is a constant-time + * operation (it is not proportional to the size of the object). + * + * @return a {@link Config} with this object as its root + */ + Config toConfig(); + + /** + * Recursively unwraps the object, returning a map from String to whatever + * plain Java values are unwrapped from the object's values. + * + * @return a {@link java.util.Map} containing plain Java objects + */ + @Override + Map unwrapped(); + + @Override + ConfigObject withFallback(ConfigMergeable other); + + /** + * Gets a {@link ConfigValue} at the given key, or returns null if there is + * no value. The returned {@link ConfigValue} may have + * {@link ConfigValueType#NULL} or any other type, and the passed-in key + * must be a key in this object (rather than a path expression). + * + * @param key + * key to look up + * + * @return the value at the key or null if none + */ + @Override + ConfigValue get(Object key); + + /** + * Clone the object with only the given key (and its children) retained; all + * sibling keys are removed. + * + * @param key + * key to keep + * @return a copy of the object minus all keys except the one specified + */ + ConfigObject withOnlyKey(String key); + + /** + * Clone the object with the given key removed. + * + * @param key + * key to remove + * @return a copy of the object minus the specified key + */ + ConfigObject withoutKey(String key); + + /** + * Returns a {@code ConfigObject} based on this one, but with the given key + * set to the given value. Does not modify this instance (since it's + * immutable). If the key already has a value, that value is replaced. To + * remove a value, use {@link ConfigObject#withoutKey(String)}. + * + * @param key + * key to add + * @param value + * value at the new key + * @return the new instance with the new map entry + */ + ConfigObject withValue(String key, ConfigValue value); + + @Override + ConfigObject withOrigin(ConfigOrigin origin); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigOrigin.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigOrigin.java new file mode 100644 index 0000000..9760992 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigOrigin.java @@ -0,0 +1,118 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +import java.net.URL; +import java.util.List; + + +/** + * Represents the origin (such as filename and line number) of a + * {@link ConfigValue} for use in error messages. Obtain the origin of a value + * with {@link ConfigValue#origin}. Exceptions may have an origin, see + * {@link ConfigException#origin}, but be careful because + * ConfigException.origin() may return null. + * + *

+ * It's best to use this interface only for debugging; its accuracy is + * "best effort" rather than guaranteed, and a potentially-noticeable amount of + * memory could probably be saved if origins were not kept around, so in the + * future there might be some option to discard origins. + * + *

+ * Do not implement this interface; it should only be implemented by + * the config library. Arbitrary implementations will not work because the + * library internals assume a specific concrete implementation. Also, this + * interface is likely to grow new methods over time, so third-party + * implementations will break. + */ +public interface ConfigOrigin { + /** + * Returns a string describing the origin of a value or exception. This will + * never return null. + * + * @return string describing the origin + */ + public String description(); + + /** + * Returns a filename describing the origin. This will return null if the + * origin was not a file. + * + * @return filename of the origin or null + */ + public String filename(); + + /** + * Returns a URL describing the origin. This will return null if the origin + * has no meaningful URL. + * + * @return url of the origin or null + */ + public URL url(); + + /** + * Returns a classpath resource name describing the origin. This will return + * null if the origin was not a classpath resource. + * + * @return resource name of the origin or null + */ + public String resource(); + + /** + * Returns a line number where the value or exception originated. This will + * return -1 if there's no meaningful line number. + * + * @return line number or -1 if none is available + */ + public int lineNumber(); + + /** + * Returns any comments that appeared to "go with" this place in the file. + * Often an empty list, but never null. The details of this are subject to + * change, but at the moment comments that are immediately before an array + * element or object field, with no blank line after the comment, "go with" + * that element or field. + * + * @return any comments that seemed to "go with" this origin, empty list if + * none + */ + public List comments(); + + /** + * Returns a {@code ConfigOrigin} based on this one, but with the given + * comments. Does not modify this instance or any {@code ConfigValue}s with + * this origin (since they are immutable). To set the returned origin to a + * {@code ConfigValue}, use {@link ConfigValue#withOrigin}. + * + *

+ * Note that when the given comments are equal to the comments on this object, + * a new instance may not be created and {@code this} is returned directly. + * + * @since 1.3.0 + * + * @param comments the comments used on the returned origin + * @return the ConfigOrigin with the given comments + */ + public ConfigOrigin withComments(List comments); + + /** + * Returns a {@code ConfigOrigin} based on this one, but with the given + * line number. This origin must be a FILE, URL or RESOURCE. Does not modify + * this instance or any {@code ConfigValue}s with this origin (since they are + * immutable). To set the returned origin to a {@code ConfigValue}, use + * {@link ConfigValue#withOrigin}. + * + *

+ * Note that when the given lineNumber are equal to the lineNumber on this + * object, a new instance may not be created and {@code this} is returned + * directly. + * + * @since 1.3.0 + * + * @param lineNumber the new line number + * @return the created ConfigOrigin + */ + public ConfigOrigin withLineNumber(int lineNumber); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigOriginFactory.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigOriginFactory.java new file mode 100644 index 0000000..b1dd318 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigOriginFactory.java @@ -0,0 +1,68 @@ +package com.drtshock.playervaults.lib.com.typesafe.config; + +import java.net.URL; + +import com.drtshock.playervaults.lib.com.typesafe.config.impl.ConfigImpl; + +/** + * This class contains some static factory methods for building a {@link + * ConfigOrigin}. {@code ConfigOrigin}s are automatically created when you + * call other API methods to get a {@code ConfigValue} or {@code Config}. + * But you can also set the origin of an existing {@code ConfigValue}, using + * {@link ConfigValue#withOrigin(ConfigOrigin)}. + * + * @since 1.3.0 + */ +public final class ConfigOriginFactory { + private ConfigOriginFactory() { + } + + /** + * Returns the default origin for values when no other information is + * provided. This is the origin used in {@link ConfigValueFactory + * #fromAnyRef(Object)}. + * + * @since 1.3.0 + * + * @return the default origin + */ + public static ConfigOrigin newSimple() { + return newSimple(null); + } + + /** + * Returns an origin with the given description. + * + * @since 1.3.0 + * + * @param description brief description of what the origin is + * @return a new origin + */ + public static ConfigOrigin newSimple(String description) { + return ConfigImpl.newSimpleOrigin(description); + } + + /** + * Creates a file origin with the given filename. + * + * @since 1.3.0 + * + * @param filename the filename of this origin + * @return a new origin + */ + public static ConfigOrigin newFile(String filename) { + return ConfigImpl.newFileOrigin(filename); + } + + /** + * Creates a url origin with the given URL object. + * + * @since 1.3.0 + * + * @param url the url of this origin + * @return a new origin + */ + public static ConfigOrigin newURL(URL url) { + return ConfigImpl.newURLOrigin(url); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigParseOptions.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigParseOptions.java new file mode 100644 index 0000000..6a08315 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigParseOptions.java @@ -0,0 +1,228 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + + +/** + * A set of options related to parsing. + * + *

+ * This object is immutable, so the "setters" return a new object. + * + *

+ * Here is an example of creating a custom {@code ConfigParseOptions}: + * + *

+ *     ConfigParseOptions options = ConfigParseOptions.defaults()
+ *         .setSyntax(ConfigSyntax.JSON)
+ *         .setAllowMissing(false)
+ * 
+ * + */ +public final class ConfigParseOptions { + final ConfigSyntax syntax; + final String originDescription; + final boolean allowMissing; + final ConfigIncluder includer; + final ClassLoader classLoader; + + private ConfigParseOptions(ConfigSyntax syntax, String originDescription, boolean allowMissing, + ConfigIncluder includer, ClassLoader classLoader) { + this.syntax = syntax; + this.originDescription = originDescription; + this.allowMissing = allowMissing; + this.includer = includer; + this.classLoader = classLoader; + } + + /** + * Gets an instance of ConfigParseOptions with all fields + * set to the default values. Start with this instance and make any + * changes you need. + * @return the default parse options + */ + public static ConfigParseOptions defaults() { + return new ConfigParseOptions(null, null, true, null, null); + } + + /** + * Set the file format. If set to null, try to guess from any available + * filename extension; if guessing fails, assume {@link ConfigSyntax#CONF}. + * + * @param syntax + * a syntax or {@code null} for best guess + * @return options with the syntax set + */ + public ConfigParseOptions setSyntax(ConfigSyntax syntax) { + if (this.syntax == syntax) + return this; + else + return new ConfigParseOptions(syntax, this.originDescription, this.allowMissing, + this.includer, this.classLoader); + } + + /** + * Gets the current syntax option, which may be null for "any". + * @return the current syntax or null + */ + public ConfigSyntax getSyntax() { + return syntax; + } + + /** + * Set a description for the thing being parsed. In most cases this will be + * set up for you to something like the filename, but if you provide just an + * input stream you might want to improve on it. Set to null to allow the + * library to come up with something automatically. This description is the + * basis for the {@link ConfigOrigin} of the parsed values. + * + * @param originDescription description to put in the {@link ConfigOrigin} + * @return options with the origin description set + */ + public ConfigParseOptions setOriginDescription(String originDescription) { + // findbugs complains about == here but is wrong, do not "fix" + if (this.originDescription == originDescription) + return this; + else if (this.originDescription != null && originDescription != null + && this.originDescription.equals(originDescription)) + return this; + else + return new ConfigParseOptions(this.syntax, originDescription, this.allowMissing, + this.includer, this.classLoader); + } + + /** + * Gets the current origin description, which may be null for "automatic". + * @return the current origin description or null + */ + public String getOriginDescription() { + return originDescription; + } + + /** this is package-private, not public API */ + ConfigParseOptions withFallbackOriginDescription(String originDescription) { + if (this.originDescription == null) + return setOriginDescription(originDescription); + else + return this; + } + + /** + * Set to false to throw an exception if the item being parsed (for example + * a file) is missing. Set to true to just return an empty document in that + * case. Note that this setting applies on only to fetching the root document, + * it has no effect on any nested includes. + * + * @param allowMissing true to silently ignore missing item + * @return options with the "allow missing" flag set + */ + public ConfigParseOptions setAllowMissing(boolean allowMissing) { + if (this.allowMissing == allowMissing) + return this; + else + return new ConfigParseOptions(this.syntax, this.originDescription, allowMissing, + this.includer, this.classLoader); + } + + /** + * Gets the current "allow missing" flag. + * @return whether we allow missing files + */ + public boolean getAllowMissing() { + return allowMissing; + } + + /** + * Set a {@link ConfigIncluder} which customizes how includes are handled. + * null means to use the default includer. + * + * @param includer the includer to use or null for default + * @return new version of the parse options with different includer + */ + public ConfigParseOptions setIncluder(ConfigIncluder includer) { + if (this.includer == includer) + return this; + else + return new ConfigParseOptions(this.syntax, this.originDescription, this.allowMissing, + includer, this.classLoader); + } + + /** + * Prepends a {@link ConfigIncluder} which customizes how + * includes are handled. To prepend your includer, the + * library calls {@link ConfigIncluder#withFallback} on your + * includer to append the existing includer to it. + * + * @param includer the includer to prepend (may not be null) + * @return new version of the parse options with different includer + */ + public ConfigParseOptions prependIncluder(ConfigIncluder includer) { + if (includer == null) + throw new NullPointerException("null includer passed to prependIncluder"); + if (this.includer == includer) + return this; + else if (this.includer != null) + return setIncluder(includer.withFallback(this.includer)); + else + return setIncluder(includer); + } + + /** + * Appends a {@link ConfigIncluder} which customizes how + * includes are handled. To append, the library calls {@link + * ConfigIncluder#withFallback} on the existing includer. + * + * @param includer the includer to append (may not be null) + * @return new version of the parse options with different includer + */ + public ConfigParseOptions appendIncluder(ConfigIncluder includer) { + if (includer == null) + throw new NullPointerException("null includer passed to appendIncluder"); + if (this.includer == includer) + return this; + else if (this.includer != null) + return setIncluder(this.includer.withFallback(includer)); + else + return setIncluder(includer); + } + + /** + * Gets the current includer (will be null for the default includer). + * @return current includer or null + */ + public ConfigIncluder getIncluder() { + return includer; + } + + /** + * Set the class loader. If set to null, + * Thread.currentThread().getContextClassLoader() will be used. + * + * @param loader + * a class loader or {@code null} to use thread context class + * loader + * @return options with the class loader set + */ + public ConfigParseOptions setClassLoader(ClassLoader loader) { + if (this.classLoader == loader) + return this; + else + return new ConfigParseOptions(this.syntax, this.originDescription, this.allowMissing, + this.includer, loader); + } + + /** + * Get the class loader; never returns {@code null}, if the class loader was + * unset, returns + * Thread.currentThread().getContextClassLoader(). + * + * @return class loader to use + */ + public ClassLoader getClassLoader() { + if (this.classLoader == null) + return Thread.currentThread().getContextClassLoader(); + else + return this.classLoader; + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigParseable.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigParseable.java new file mode 100644 index 0000000..3f226c1 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigParseable.java @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + + +/** + * An opaque handle to something that can be parsed, obtained from + * {@link ConfigIncludeContext}. + * + *

+ * Do not implement this interface; it should only be implemented by + * the config library. Arbitrary implementations will not work because the + * library internals assume a specific concrete implementation. Also, this + * interface is likely to grow new methods over time, so third-party + * implementations will break. + */ +public interface ConfigParseable { + /** + * Parse whatever it is. The options should come from + * {@link ConfigParseable#options options()} but you could tweak them if you + * like. + * + * @param options + * parse options, should be based on the ones from + * {@link ConfigParseable#options options()} + * @return the parsed object + */ + ConfigObject parse(ConfigParseOptions options); + + /** + * Returns a {@link ConfigOrigin} describing the origin of the parseable + * item. + * @return the origin of the parseable item + */ + ConfigOrigin origin(); + + /** + * Get the initial options, which can be modified then passed to parse(). + * These options will have the right description, includer, and other + * parameters already set up. + * @return the initial options + */ + ConfigParseOptions options(); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigRenderOptions.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigRenderOptions.java new file mode 100644 index 0000000..90f4a83 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigRenderOptions.java @@ -0,0 +1,182 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +/** + *

+ * A set of options related to rendering a {@link ConfigValue}. Passed to + * {@link ConfigValue#render(ConfigRenderOptions)}. + * + *

+ * Here is an example of creating a {@code ConfigRenderOptions}: + * + *

+ *     ConfigRenderOptions options =
+ *         ConfigRenderOptions.defaults().setComments(false)
+ * 
+ */ +public final class ConfigRenderOptions { + private final boolean originComments; + private final boolean comments; + private final boolean formatted; + private final boolean json; + + private ConfigRenderOptions(boolean originComments, boolean comments, boolean formatted, + boolean json) { + this.originComments = originComments; + this.comments = comments; + this.formatted = formatted; + this.json = json; + } + + /** + * Returns the default render options which are verbose (commented and + * formatted). See {@link ConfigRenderOptions#concise} for stripped-down + * options. This rendering will not be valid JSON since it has comments. + * + * @return the default render options + */ + public static ConfigRenderOptions defaults() { + return new ConfigRenderOptions(true, true, true, true); + } + + /** + * Returns concise render options (no whitespace or comments). For a + * resolved {@link Config}, the concise rendering will be valid JSON. + * + * @return the concise render options + */ + public static ConfigRenderOptions concise() { + return new ConfigRenderOptions(false, false, false, true); + } + + /** + * Returns options with comments toggled. This controls human-written + * comments but not the autogenerated "origin of this setting" comments, + * which are controlled by {@link ConfigRenderOptions#setOriginComments}. + * + * @param value + * true to include comments in the render + * @return options with requested setting for comments + */ + public ConfigRenderOptions setComments(boolean value) { + if (value == comments) + return this; + else + return new ConfigRenderOptions(originComments, value, formatted, json); + } + + /** + * Returns whether the options enable comments. This method is mostly used + * by the config lib internally, not by applications. + * + * @return true if comments should be rendered + */ + public boolean getComments() { + return comments; + } + + /** + * Returns options with origin comments toggled. If this is enabled, the + * library generates comments for each setting based on the + * {@link ConfigValue#origin} of that setting's value. For example these + * comments might tell you which file a setting comes from. + * + *

+ * {@code setOriginComments()} controls only these autogenerated + * "origin of this setting" comments, to toggle regular comments use + * {@link ConfigRenderOptions#setComments}. + * + * @param value + * true to include autogenerated setting-origin comments in the + * render + * @return options with origin comments toggled + */ + public ConfigRenderOptions setOriginComments(boolean value) { + if (value == originComments) + return this; + else + return new ConfigRenderOptions(value, comments, formatted, json); + } + + /** + * Returns whether the options enable automated origin comments. This method + * is mostly used by the config lib internally, not by applications. + * + * @return true if origin comments should be rendered + */ + public boolean getOriginComments() { + return originComments; + } + + /** + * Returns options with formatting toggled. Formatting means indentation and + * whitespace, enabling formatting makes things prettier but larger. + * + * @param value + * true to enable formatting + * @return options with requested setting for formatting + */ + public ConfigRenderOptions setFormatted(boolean value) { + if (value == formatted) + return this; + else + return new ConfigRenderOptions(originComments, comments, value, json); + } + + /** + * Returns whether the options enable formatting. This method is mostly used + * by the config lib internally, not by applications. + * + * @return true if the options enable formatting + */ + public boolean getFormatted() { + return formatted; + } + + /** + * Returns options with JSON toggled. JSON means that HOCON extensions + * (omitting commas, quotes for example) won't be used. However, whether to + * use comments is controlled by the separate {@link #setComments(boolean)} + * and {@link #setOriginComments(boolean)} options. So if you enable + * comments you will get invalid JSON despite setting this to true. + * + * @param value + * true to include non-JSON extensions in the render + * @return options with requested setting for JSON + */ + public ConfigRenderOptions setJson(boolean value) { + if (value == json) + return this; + else + return new ConfigRenderOptions(originComments, comments, formatted, value); + } + + /** + * Returns whether the options enable JSON. This method is mostly used by + * the config lib internally, not by applications. + * + * @return true if only JSON should be rendered + */ + public boolean getJson() { + return json; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("ConfigRenderOptions("); + if (originComments) + sb.append("originComments,"); + if (comments) + sb.append("comments,"); + if (formatted) + sb.append("formatted,"); + if (json) + sb.append("json,"); + if (sb.charAt(sb.length() - 1) == ',') + sb.setLength(sb.length() - 1); + sb.append(")"); + return sb.toString(); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigResolveOptions.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigResolveOptions.java new file mode 100644 index 0000000..34c4128 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigResolveOptions.java @@ -0,0 +1,176 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +/** + * A set of options related to resolving substitutions. Substitutions use the + * ${foo.bar} syntax and are documented in the HOCON + * spec. + *

+ * Typically this class would be used with the method + * {@link Config#resolve(ConfigResolveOptions)}. + *

+ * This object is immutable, so the "setters" return a new object. + *

+ * Here is an example of creating a custom {@code ConfigResolveOptions}: + * + *

+ *     ConfigResolveOptions options = ConfigResolveOptions.defaults()
+ *         .setUseSystemEnvironment(false)
+ * 
+ *

+ * In addition to {@link ConfigResolveOptions#defaults}, there's a prebuilt + * {@link ConfigResolveOptions#noSystem} which avoids looking at any system + * environment variables or other external system information. (Right now, + * environment variables are the only example.) + */ +public final class ConfigResolveOptions { + private final boolean useSystemEnvironment; + private final boolean allowUnresolved; + private final ConfigResolver resolver; + + private ConfigResolveOptions(boolean useSystemEnvironment, boolean allowUnresolved, + ConfigResolver resolver) { + this.useSystemEnvironment = useSystemEnvironment; + this.allowUnresolved = allowUnresolved; + this.resolver = resolver; + } + + /** + * Returns the default resolve options. By default the system environment + * will be used and unresolved substitutions are not allowed. + * + * @return the default resolve options + */ + public static ConfigResolveOptions defaults() { + return new ConfigResolveOptions(true, false, NULL_RESOLVER); + } + + /** + * Returns resolve options that disable any reference to "system" data + * (currently, this means environment variables). + * + * @return the resolve options with env variables disabled + */ + public static ConfigResolveOptions noSystem() { + return defaults().setUseSystemEnvironment(false); + } + + /** + * Returns options with use of environment variables set to the given value. + * + * @param value + * true to resolve substitutions falling back to environment + * variables. + * @return options with requested setting for use of environment variables + */ + public ConfigResolveOptions setUseSystemEnvironment(boolean value) { + return new ConfigResolveOptions(value, allowUnresolved, resolver); + } + + /** + * Returns whether the options enable use of system environment variables. + * This method is mostly used by the config lib internally, not by + * applications. + * + * @return true if environment variables should be used + */ + public boolean getUseSystemEnvironment() { + return useSystemEnvironment; + } + + /** + * Returns options with "allow unresolved" set to the given value. By + * default, unresolved substitutions are an error. If unresolved + * substitutions are allowed, then a future attempt to use the unresolved + * value may fail, but {@link Config#resolve(ConfigResolveOptions)} itself + * will not throw. + * + * @param value + * true to silently ignore unresolved substitutions. + * @return options with requested setting for whether to allow substitutions + * @since 1.2.0 + */ + public ConfigResolveOptions setAllowUnresolved(boolean 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, + * + *

+     *     ConfigResolveOptions options = ConfigResolveOptions.defaults()
+     *         .appendResolver(primary)
+     *         .appendResolver(secondary)
+     *         .appendResolver(tertiary);
+     * 
+ * + * 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; + } + + /** + * Returns whether the options allow unresolved substitutions. This method + * is mostly used by the config lib internally, not by applications. + * + * @return true if unresolved substitutions are allowed + * @since 1.2.0 + */ + 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; + } + + }; + +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigResolver.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigResolver.java new file mode 100644 index 0000000..0db30e7 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigResolver.java @@ -0,0 +1,38 @@ +package com.drtshock.playervaults.lib.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); + +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigSyntax.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigSyntax.java new file mode 100644 index 0000000..03de86c --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigSyntax.java @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +/** + * The syntax of a character stream (JSON, HOCON + * aka ".conf", or Java properties). + * + */ +public enum ConfigSyntax { + /** + * Pedantically strict JSON format; no + * comments, no unexpected commas, no duplicate keys in the same object. + * Associated with the .json file extension and + * application/json Content-Type. + */ + JSON, + /** + * The JSON-superset HOCON format. Associated with the .conf file extension + * and application/hocon Content-Type. + */ + CONF, + /** + * Standard Java properties format. Associated with the .properties + * file extension and text/x-java-properties Content-Type. + */ + PROPERTIES; +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigUtil.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigUtil.java new file mode 100644 index 0000000..48c44c6 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigUtil.java @@ -0,0 +1,83 @@ +package com.drtshock.playervaults.lib.com.typesafe.config; + +import java.util.List; + +import com.drtshock.playervaults.lib.com.typesafe.config.impl.ConfigImplUtil; + +/** + * Contains static utility methods. + * + */ +public final class ConfigUtil { + private ConfigUtil() { + + } + + /** + * Quotes and escapes a string, as in the JSON specification. + * + * @param s + * a string + * @return the string quoted and escaped + */ + public static String quoteString(String s) { + return ConfigImplUtil.renderJsonString(s); + } + + /** + * Converts a list of keys to a path expression, by quoting the path + * elements as needed and then joining them separated by a period. A path + * expression is usable with a {@link Config}, while individual path + * elements are usable with a {@link ConfigObject}. + *

+ * See the overview documentation for {@link Config} for more detail on path + * expressions vs. keys. + * + * @param elements + * the keys in the path + * @return a path expression + * @throws ConfigException + * if there are no elements + */ + public static String joinPath(String... elements) { + return ConfigImplUtil.joinPath(elements); + } + + /** + * Converts a list of strings to a path expression, by quoting the path + * elements as needed and then joining them separated by a period. A path + * expression is usable with a {@link Config}, while individual path + * elements are usable with a {@link ConfigObject}. + *

+ * See the overview documentation for {@link Config} for more detail on path + * expressions vs. keys. + * + * @param elements + * the keys in the path + * @return a path expression + * @throws ConfigException + * if the list is empty + */ + public static String joinPath(List elements) { + return ConfigImplUtil.joinPath(elements); + } + + /** + * Converts a path expression into a list of keys, by splitting on period + * and unquoting the individual path elements. A path expression is usable + * with a {@link Config}, while individual path elements are usable with a + * {@link ConfigObject}. + *

+ * See the overview documentation for {@link Config} for more detail on path + * expressions vs. keys. + * + * @param path + * a path expression + * @return the individual keys in the path + * @throws ConfigException + * if the path expression is invalid + */ + public static List splitPath(String path) { + return ConfigImplUtil.splitPath(path); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigValue.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigValue.java new file mode 100644 index 0000000..72ecf63 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigValue.java @@ -0,0 +1,122 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +/** + * An immutable value, following the JSON type + * schema. + * + *

+ * Because this object is immutable, it is safe to use from multiple threads and + * there's no need for "defensive copies." + * + *

+ * Do not implement interface {@code ConfigValue}; it should only be + * implemented by the config library. Arbitrary implementations will not work + * because the library internals assume a specific concrete implementation. + * Also, this interface is likely to grow new methods over time, so third-party + * implementations will break. + */ +public interface ConfigValue extends ConfigMergeable { + /** + * The origin of the value (file, line number, etc.), for debugging and + * error messages. + * + * @return where the value came from + */ + ConfigOrigin origin(); + + /** + * The {@link ConfigValueType} of the value; matches the JSON type schema. + * + * @return value's type + */ + ConfigValueType valueType(); + + /** + * Returns the value as a plain Java boxed value, that is, a {@code String}, + * {@code Number}, {@code Boolean}, {@code Map}, + * {@code List}, or {@code null}, matching the {@link #valueType()} + * of this {@code ConfigValue}. If the value is a {@link ConfigObject} or + * {@link ConfigList}, it is recursively unwrapped. + * @return a plain Java value corresponding to this ConfigValue + */ + Object unwrapped(); + + /** + * Renders the config value as a HOCON string. This method is primarily + * intended for debugging, so it tries to add helpful comments and + * whitespace. + * + *

+ * If the config value has not been resolved (see {@link Config#resolve}), + * it's possible that it can't be rendered as valid HOCON. In that case the + * rendering should still be useful for debugging but you might not be able + * to parse it. If the value has been resolved, it will always be parseable. + * + *

+ * This method is equivalent to + * {@code render(ConfigRenderOptions.defaults())}. + * + * @return the rendered value + */ + String render(); + + /** + * Renders the config value to a string, using the provided options. + * + *

+ * If the config value has not been resolved (see {@link Config#resolve}), + * it's possible that it can't be rendered as valid HOCON. In that case the + * rendering should still be useful for debugging but you might not be able + * to parse it. If the value has been resolved, it will always be parseable. + * + *

+ * If the config value has been resolved and the options disable all + * HOCON-specific features (such as comments), the rendering will be valid + * JSON. If you enable HOCON-only features such as comments, the rendering + * will not be valid JSON. + * + * @param options + * the rendering options + * @return the rendered value + */ + String render(ConfigRenderOptions options); + + @Override + ConfigValue withFallback(ConfigMergeable other); + + /** + * Places the value inside a {@link Config} at the given path. See also + * {@link ConfigValue#atKey(String)}. + * + * @param path + * path to store this value at. + * @return a {@code Config} instance containing this value at the given + * path. + */ + Config atPath(String path); + + /** + * Places the value inside a {@link Config} at the given key. See also + * {@link ConfigValue#atPath(String)}. + * + * @param key + * key to store this value at. + * @return a {@code Config} instance containing this value at the given key. + */ + Config atKey(String key); + + /** + * Returns a {@code ConfigValue} based on this one, but with the given + * origin. This is useful when you are parsing a new format of file or setting + * comments for a single ConfigValue. + * + * @since 1.3.0 + * + * @param origin the origin set on the returned value + * @return the new ConfigValue with the given origin + */ + ConfigValue withOrigin(ConfigOrigin origin); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigValueFactory.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigValueFactory.java new file mode 100644 index 0000000..4f8d8ee --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigValueFactory.java @@ -0,0 +1,153 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +import java.util.Map; + +import com.drtshock.playervaults.lib.com.typesafe.config.impl.ConfigImpl; + +/** + * This class holds some static factory methods for building {@link ConfigValue} + * instances. See also {@link ConfigFactory} which has methods for parsing files + * and certain in-memory data structures. + */ +public final class ConfigValueFactory { + private ConfigValueFactory() { + } + + /** + * Creates a {@link ConfigValue} from a plain Java boxed value, which may be + * a Boolean, Number, String, + * Map, Iterable, or null. A + * Map must be a Map from String to more values + * that can be supplied to fromAnyRef(). An + * Iterable must iterate over more values that can be supplied + * to fromAnyRef(). A Map will become a + * {@link ConfigObject} and an Iterable will become a + * {@link ConfigList}. If the Iterable is not an ordered + * collection, results could be strange, since ConfigList is + * ordered. + * + *

+ * In a Map passed to fromAnyRef(), the map's keys + * are plain keys, not path expressions. So if your Map has a + * key "foo.bar" then you will get one object with a key called "foo.bar", + * rather than an object with a key "foo" containing another object with a + * key "bar". + * + *

+ * The originDescription will be used to set the origin() field on the + * ConfigValue. It should normally be the name of the file the values came + * from, or something short describing the value such as "default settings". + * The originDescription is prefixed to error messages so users can tell + * where problematic values are coming from. + * + *

+ * Supplying the result of ConfigValue.unwrapped() to this function is + * guaranteed to work and should give you back a ConfigValue that matches + * the one you unwrapped. The re-wrapped ConfigValue will lose some + * information that was present in the original such as its origin, but it + * will have matching values. + * + *

+ * If you pass in a ConfigValue to this + * function, it will be returned unmodified. (The + * originDescription will be ignored in this + * case.) + * + *

+ * This function throws if you supply a value that cannot be converted to a + * ConfigValue, but supplying such a value is a bug in your program, so you + * should never handle the exception. Just fix your program (or report a bug + * against this library). + * + * @param object + * object to convert to ConfigValue + * @param originDescription + * name of origin file or brief description of what the value is + * @return a new value + */ + public static ConfigValue fromAnyRef(Object object, String originDescription) { + return ConfigImpl.fromAnyRef(object, originDescription); + } + + /** + * See the {@link #fromAnyRef(Object,String)} documentation for details. + * This is a typesafe wrapper that only works on {@link java.util.Map} and + * returns {@link ConfigObject} rather than {@link ConfigValue}. + * + *

+ * If your Map has a key "foo.bar" then you will get one object + * with a key called "foo.bar", rather than an object with a key "foo" + * containing another object with a key "bar". The keys in the map are keys; + * not path expressions. That is, the Map corresponds exactly + * to a single {@code ConfigObject}. The keys will not be parsed or + * modified, and the values are wrapped in ConfigValue. To get nested + * {@code ConfigObject}, some of the values in the map would have to be more + * maps. + * + *

+ * See also {@link ConfigFactory#parseMap(Map,String)} which interprets the + * keys in the map as path expressions. + * + * @param values map from keys to plain Java values + * @param originDescription description to use in {@link ConfigOrigin} of created values + * @return a new {@link ConfigObject} value + */ + public static ConfigObject fromMap(Map values, + String originDescription) { + return (ConfigObject) fromAnyRef(values, originDescription); + } + + /** + * See the {@link #fromAnyRef(Object,String)} documentation for details. + * This is a typesafe wrapper that only works on {@link java.lang.Iterable} + * and returns {@link ConfigList} rather than {@link ConfigValue}. + * + * @param values list of plain Java values + * @param originDescription description to use in {@link ConfigOrigin} of created values + * @return a new {@link ConfigList} value + */ + public static ConfigList fromIterable(Iterable values, + String originDescription) { + return (ConfigList) fromAnyRef(values, originDescription); + } + + /** + * See the other overload {@link #fromAnyRef(Object,String)} for details, + * this one just uses a default origin description. + * + * @param object a plain Java value + * @return a new {@link ConfigValue} + */ + public static ConfigValue fromAnyRef(Object object) { + return fromAnyRef(object, null); + } + + /** + * See the other overload {@link #fromMap(Map,String)} for details, this one + * just uses a default origin description. + * + *

+ * See also {@link ConfigFactory#parseMap(Map)} which interprets the keys in + * the map as path expressions. + * + * @param values map from keys to plain Java values + * @return a new {@link ConfigObject} + */ + public static ConfigObject fromMap(Map values) { + return fromMap(values, null); + } + + /** + * See the other overload of {@link #fromIterable(Iterable, String)} for + * details, this one just uses a default origin description. + * + * @param values list of plain Java values + * @return a new {@link ConfigList} + */ + public static ConfigList fromIterable(Iterable values) { + return fromIterable(values, null); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigValueType.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigValueType.java new file mode 100644 index 0000000..1de6a1e --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/ConfigValueType.java @@ -0,0 +1,12 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config; + +/** + * The type of a configuration value (following the JSON type schema). + */ +public enum ConfigValueType { + OBJECT, LIST, NUMBER, BOOLEAN, NULL, STRING +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/DefaultConfigLoadingStrategy.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/DefaultConfigLoadingStrategy.java new file mode 100644 index 0000000..e46765e --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/DefaultConfigLoadingStrategy.java @@ -0,0 +1,62 @@ +package com.drtshock.playervaults.lib.com.typesafe.config; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Default config loading strategy. Able to load resource, file or URL. + * Behavior may be altered by defining one of VM properties + * {@code config.resource}, {@code config.file} or {@code config.url} + */ +public class DefaultConfigLoadingStrategy implements ConfigLoadingStrategy { + @Override + public Config parseApplicationConfig(ConfigParseOptions parseOptions) { + ClassLoader loader = parseOptions.getClassLoader(); + if (loader == null) + throw new ConfigException.BugOrBroken( + "ClassLoader should have been set here; bug in ConfigFactory. " + + "(You can probably work around this bug by passing in a class loader or calling currentThread().setContextClassLoader() though.)"); + + int specified = 0; + + // override application.conf with config.file, config.resource, + // config.url if requested. + String resource = System.getProperty("config.resource"); + if (resource != null) + specified += 1; + String file = System.getProperty("config.file"); + if (file != null) + specified += 1; + String url = System.getProperty("config.url"); + if (url != null) + specified += 1; + + if (specified == 0) { + return ConfigFactory.parseResourcesAnySyntax("application", parseOptions); + } else if (specified > 1) { + throw new ConfigException.Generic("You set more than one of config.file='" + file + + "', config.url='" + url + "', config.resource='" + resource + + "'; don't know which one to use!"); + } else { + // the override file/url/resource MUST be present or it's an error + ConfigParseOptions overrideOptions = parseOptions.setAllowMissing(false); + if (resource != null) { + if (resource.startsWith("/")) + resource = resource.substring(1); + // this deliberately does not parseResourcesAnySyntax; if + // people want that they can use an include statement. + return ConfigFactory.parseResources(loader, resource, overrideOptions); + } else if (file != null) { + return ConfigFactory.parseFile(new File(file), overrideOptions); + } else { + try { + return ConfigFactory.parseURL(new URL(url), overrideOptions); + } catch (MalformedURLException e) { + throw new ConfigException.Generic("Bad URL in config.url system property: '" + + url + "': " + e.getMessage(), e); + } + } + } + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/Optional.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/Optional.java new file mode 100644 index 0000000..9223525 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/Optional.java @@ -0,0 +1,14 @@ +package com.drtshock.playervaults.lib.com.typesafe.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Allows an config property to be {@code null}. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Optional { + +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigNode.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigNode.java new file mode 100644 index 0000000..1de6857 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigNode.java @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2015 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.parser.ConfigNode; +import java.util.Collection; + +abstract class AbstractConfigNode implements ConfigNode { + abstract Collection tokens(); + final public String render() { + StringBuilder origText = new StringBuilder(); + Iterable tokens = tokens(); + for (Token t : tokens) { + origText.append(t.tokenText()); + } + return origText.toString(); + } + + @Override + final public boolean equals(Object other) { + return other instanceof AbstractConfigNode && render().equals(((AbstractConfigNode)other).render()); + } + + @Override + final public int hashCode() { + return render().hashCode(); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigNodeValue.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigNodeValue.java new file mode 100644 index 0000000..81d1cf1 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigNodeValue.java @@ -0,0 +1,11 @@ +/** + * Copyright (C) 2015 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +// This is required if we want +// to be referencing the AbstractConfigNode class in implementation rather than the +// ConfigNode interface, as we can't cast an AbstractConfigNode to an interface +abstract class AbstractConfigNodeValue extends AbstractConfigNode { + +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigObject.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigObject.java new file mode 100644 index 0000000..948f2b6 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigObject.java @@ -0,0 +1,221 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigMergeable; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigObject; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigRenderOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +abstract class AbstractConfigObject extends AbstractConfigValue implements ConfigObject, Container { + final private SimpleConfig config; + + protected AbstractConfigObject(ConfigOrigin origin) { + super(origin); + this.config = new SimpleConfig(this); + } + + @Override + public SimpleConfig toConfig() { + return config; + } + + @Override + public AbstractConfigObject toFallbackValue() { + return this; + } + + @Override + abstract public AbstractConfigObject withOnlyKey(String key); + + @Override + abstract public AbstractConfigObject withoutKey(String key); + + @Override + abstract public AbstractConfigObject withValue(String key, ConfigValue value); + + abstract protected AbstractConfigObject withOnlyPathOrNull(Path path); + + abstract AbstractConfigObject withOnlyPath(Path path); + + abstract AbstractConfigObject withoutPath(Path path); + + abstract AbstractConfigObject withValue(Path path, ConfigValue value); + + /** + * This looks up the key with no transformation or type conversion of any + * kind, and returns null if the key is not present. The object must be + * resolved along the nodes needed to get the key or + * ConfigException.NotResolved will be thrown. + * + * @param key + * @return the unmodified raw value or null + */ + protected final AbstractConfigValue peekAssumingResolved(String key, Path originalPath) { + try { + return attemptPeekWithPartialResolve(key); + } catch (ConfigException.NotResolved e) { + throw ConfigImpl.improveNotResolved(originalPath, e); + } + } + + /** + * Look up the key on an only-partially-resolved object, with no + * transformation or type conversion of any kind; if 'this' is not resolved + * then try to look up the key anyway if possible. + * + * @param key + * key to look up + * @return the value of the key, or null if known not to exist + * @throws ConfigException.NotResolved + * if can't figure out key's value (or existence) without more + * resolving + */ + abstract AbstractConfigValue attemptPeekWithPartialResolve(String key); + + /** + * Looks up the path with no transformation or type conversion. Returns null + * if the path is not found; throws ConfigException.NotResolved if we need + * to go through an unresolved node to look up the path. + */ + protected AbstractConfigValue peekPath(Path path) { + return peekPath(this, path); + } + + private static AbstractConfigValue peekPath(AbstractConfigObject self, Path path) { + try { + // we'll fail if anything along the path can't + // be looked at without resolving. + Path next = path.remainder(); + AbstractConfigValue v = self.attemptPeekWithPartialResolve(path.first()); + + if (next == null) { + return v; + } else { + if (v instanceof AbstractConfigObject) { + return peekPath((AbstractConfigObject) v, next); + } else { + return null; + } + } + } catch (ConfigException.NotResolved e) { + throw ConfigImpl.improveNotResolved(path, e); + } + } + + @Override + public ConfigValueType valueType() { + return ConfigValueType.OBJECT; + } + + protected abstract AbstractConfigObject newCopy(ResolveStatus status, ConfigOrigin origin); + + @Override + protected AbstractConfigObject newCopy(ConfigOrigin origin) { + return newCopy(resolveStatus(), origin); + } + + @Override + protected AbstractConfigObject constructDelayedMerge(ConfigOrigin origin, + List stack) { + return new ConfigDelayedMergeObject(origin, stack); + } + + @Override + protected abstract AbstractConfigObject mergedWithObject(AbstractConfigObject fallback); + + @Override + public AbstractConfigObject withFallback(ConfigMergeable mergeable) { + return (AbstractConfigObject) super.withFallback(mergeable); + } + + static ConfigOrigin mergeOrigins( + Collection stack) { + if (stack.isEmpty()) + throw new ConfigException.BugOrBroken( + "can't merge origins on empty list"); + List origins = new ArrayList(); + ConfigOrigin firstOrigin = null; + int numMerged = 0; + for (AbstractConfigValue v : stack) { + if (firstOrigin == null) + firstOrigin = v.origin(); + + if (v instanceof AbstractConfigObject + && ((AbstractConfigObject) v).resolveStatus() == ResolveStatus.RESOLVED + && ((ConfigObject) v).isEmpty()) { + // don't include empty files or the .empty() + // config in the description, since they are + // likely to be "implementation details" + } else { + origins.add(v.origin()); + numMerged += 1; + } + } + + if (numMerged == 0) { + // the configs were all empty, so just use the first one + origins.add(firstOrigin); + } + + return SimpleConfigOrigin.mergeOrigins(origins); + } + + static ConfigOrigin mergeOrigins(AbstractConfigObject... stack) { + return mergeOrigins(Arrays.asList(stack)); + } + + @Override + abstract ResolveResult resolveSubstitutions(ResolveContext context, + ResolveSource source) + throws NotPossibleToResolve; + + @Override + abstract AbstractConfigObject relativized(final Path prefix); + + @Override + public abstract AbstractConfigValue get(Object key); + + @Override + protected abstract void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options); + + private static UnsupportedOperationException weAreImmutable(String method) { + return new UnsupportedOperationException("ConfigObject is immutable, you can't call Map." + + method); + } + + @Override + public void clear() { + throw weAreImmutable("clear"); + } + + @Override + public ConfigValue put(String arg0, ConfigValue arg1) { + throw weAreImmutable("put"); + } + + @Override + public void putAll(Map arg0) { + throw weAreImmutable("putAll"); + } + + @Override + public ConfigValue remove(Object arg0) { + throw weAreImmutable("remove"); + } + + @Override + public AbstractConfigObject withOrigin(ConfigOrigin origin) { + return (AbstractConfigObject) super.withOrigin(origin); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigValue.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigValue.java new file mode 100644 index 0000000..a63f50d --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/AbstractConfigValue.java @@ -0,0 +1,411 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigMergeable; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigObject; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigRenderOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; + +/** + * + * Trying very hard to avoid a parent reference in config values; when you have + * a tree like this, the availability of parent() tends to result in a lot of + * improperly-factored and non-modular code. Please don't add parent(). + * + */ +abstract class AbstractConfigValue implements ConfigValue, MergeableValue { + + final private SimpleConfigOrigin origin; + + AbstractConfigValue(ConfigOrigin origin) { + this.origin = (SimpleConfigOrigin) origin; + } + + @Override + public SimpleConfigOrigin origin() { + return this.origin; + } + + /** + * This exception means that a value is inherently not resolveable, at the + * moment the only known cause is a cycle of substitutions. This is a + * checked exception since it's internal to the library and we want to be + * sure we handle it before passing it out to public API. This is only + * supposed to be thrown by the target of a cyclic reference and it's + * supposed to be caught by the ConfigReference looking up that reference, + * so it should be impossible for an outermost resolve() to throw this. + * + * Contrast with ConfigException.NotResolved which just means nobody called + * resolve(). + */ + static class NotPossibleToResolve extends Exception { + private static final long serialVersionUID = 1L; + + final private String traceString; + + NotPossibleToResolve(ResolveContext context) { + super("was not possible to resolve"); + this.traceString = context.traceString(); + } + + String traceString() { + return traceString; + } + } + + /** + * Called only by ResolveContext.resolve(). + * + * @param context + * state of the current resolve + * @param source + * where to look up values + * @return a new value if there were changes, or this if no changes + */ + ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) + throws NotPossibleToResolve { + return ResolveResult.make(context, this); + } + + ResolveStatus resolveStatus() { + return ResolveStatus.RESOLVED; + } + + protected static List replaceChildInList(List list, + AbstractConfigValue child, AbstractConfigValue replacement) { + int i = 0; + while (i < list.size() && list.get(i) != child) + ++i; + if (i == list.size()) + throw new ConfigException.BugOrBroken("tried to replace " + child + " which is not in " + list); + List newStack = new ArrayList(list); + if (replacement != null) + newStack.set(i, replacement); + else + newStack.remove(i); + + if (newStack.isEmpty()) + return null; + else + return newStack; + } + + protected static boolean hasDescendantInList(List list, AbstractConfigValue descendant) { + for (AbstractConfigValue v : list) { + if (v == descendant) + return true; + } + // now the expensive traversal + for (AbstractConfigValue v : list) { + if (v instanceof Container && ((Container) v).hasDescendant(descendant)) + return true; + } + return false; + } + + /** + * This is used when including one file in another; the included file is + * relativized to the path it's included into in the parent file. The point + * is that if you include a file at foo.bar in the parent, and the included + * file as a substitution ${a.b.c}, the included substitution now needs to + * be ${foo.bar.a.b.c} because we resolve substitutions globally only after + * parsing everything. + * + * @param prefix + * @return value relativized to the given path or the same value if nothing + * to do + */ + AbstractConfigValue relativized(Path prefix) { + return this; + } + + protected interface Modifier { + // keyOrNull is null for non-objects + AbstractConfigValue modifyChildMayThrow(String keyOrNull, AbstractConfigValue v) + throws Exception; + } + + protected abstract class NoExceptionsModifier implements Modifier { + @Override + public final AbstractConfigValue modifyChildMayThrow(String keyOrNull, AbstractConfigValue v) + throws Exception { + try { + return modifyChild(keyOrNull, v); + } catch (RuntimeException e) { + throw e; + } catch(Exception e) { + throw new ConfigException.BugOrBroken("Unexpected exception", e); + } + } + + abstract AbstractConfigValue modifyChild(String keyOrNull, AbstractConfigValue v); + } + + @Override + public AbstractConfigValue toFallbackValue() { + return this; + } + + protected abstract AbstractConfigValue newCopy(ConfigOrigin origin); + + // this is virtualized rather than a field because only some subclasses + // really need to store the boolean, and they may be able to pack it + // with another boolean to save space. + protected boolean ignoresFallbacks() { + // if we are not resolved, then somewhere in this value there's + // a substitution that may need to look at the fallbacks. + return resolveStatus() == ResolveStatus.RESOLVED; + } + + protected AbstractConfigValue withFallbacksIgnored() { + if (ignoresFallbacks()) + return this; + else + throw new ConfigException.BugOrBroken( + "value class doesn't implement forced fallback-ignoring " + this); + } + + // the withFallback() implementation is supposed to avoid calling + // mergedWith* if we're ignoring fallbacks. + protected final void requireNotIgnoringFallbacks() { + if (ignoresFallbacks()) + throw new ConfigException.BugOrBroken( + "method should not have been called with ignoresFallbacks=true " + + getClass().getSimpleName()); + } + + protected AbstractConfigValue constructDelayedMerge(ConfigOrigin origin, + List stack) { + return new ConfigDelayedMerge(origin, stack); + } + + protected final AbstractConfigValue mergedWithTheUnmergeable( + Collection stack, Unmergeable fallback) { + requireNotIgnoringFallbacks(); + + // if we turn out to be an object, and the fallback also does, + // then a merge may be required; delay until we resolve. + List newStack = new ArrayList(); + newStack.addAll(stack); + newStack.addAll(fallback.unmergedValues()); + return constructDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack); + } + + private final AbstractConfigValue delayMerge(Collection stack, + AbstractConfigValue fallback) { + // if we turn out to be an object, and the fallback also does, + // then a merge may be required. + // if we contain a substitution, resolving it may need to look + // back to the fallback. + List newStack = new ArrayList(); + newStack.addAll(stack); + newStack.add(fallback); + return constructDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack); + } + + protected final AbstractConfigValue mergedWithObject(Collection stack, + AbstractConfigObject fallback) { + requireNotIgnoringFallbacks(); + + if (this instanceof AbstractConfigObject) + throw new ConfigException.BugOrBroken("Objects must reimplement mergedWithObject"); + + return mergedWithNonObject(stack, fallback); + } + + protected final AbstractConfigValue mergedWithNonObject(Collection stack, + AbstractConfigValue fallback) { + requireNotIgnoringFallbacks(); + + if (resolveStatus() == ResolveStatus.RESOLVED) { + // falling back to a non-object doesn't merge anything, and also + // prohibits merging any objects that we fall back to later. + // so we have to switch to ignoresFallbacks mode. + return withFallbacksIgnored(); + } else { + // if unresolved, we may have to look back to fallbacks as part of + // the resolution process, so always delay + return delayMerge(stack, fallback); + } + } + + protected AbstractConfigValue mergedWithTheUnmergeable(Unmergeable fallback) { + requireNotIgnoringFallbacks(); + + return mergedWithTheUnmergeable(Collections.singletonList(this), fallback); + } + + protected AbstractConfigValue mergedWithObject(AbstractConfigObject fallback) { + requireNotIgnoringFallbacks(); + + return mergedWithObject(Collections.singletonList(this), fallback); + } + + protected AbstractConfigValue mergedWithNonObject(AbstractConfigValue fallback) { + requireNotIgnoringFallbacks(); + + return mergedWithNonObject(Collections.singletonList(this), fallback); + } + + @Override + public AbstractConfigValue withOrigin(ConfigOrigin origin) { + if (this.origin == origin) + return this; + else + return newCopy(origin); + } + + // this is only overridden to change the return type + @Override + public AbstractConfigValue withFallback(ConfigMergeable mergeable) { + if (ignoresFallbacks()) { + return this; + } else { + ConfigValue other = ((MergeableValue) mergeable).toFallbackValue(); + + if (other instanceof Unmergeable) { + return mergedWithTheUnmergeable((Unmergeable) other); + } else if (other instanceof AbstractConfigObject) { + return mergedWithObject((AbstractConfigObject) other); + } else { + return mergedWithNonObject((AbstractConfigValue) other); + } + } + } + + protected boolean canEqual(Object other) { + return other instanceof ConfigValue; + } + + @Override + public boolean equals(Object other) { + // note that "origin" is deliberately NOT part of equality + if (other instanceof ConfigValue) { + return canEqual(other) + && (this.valueType() == + ((ConfigValue) other).valueType()) + && ConfigImplUtil.equalsHandlingNull(this.unwrapped(), + ((ConfigValue) other).unwrapped()); + } else { + return false; + } + } + + @Override + public int hashCode() { + // note that "origin" is deliberately NOT part of equality + Object o = this.unwrapped(); + if (o == null) + return 0; + else + return o.hashCode(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + render(sb, 0, true /* atRoot */, null /* atKey */, ConfigRenderOptions.concise()); + return getClass().getSimpleName() + "(" + sb.toString() + ")"; + } + + protected static void indent(StringBuilder sb, int indent, ConfigRenderOptions options) { + if (options.getFormatted()) { + int remaining = indent; + while (remaining > 0) { + sb.append(" "); + --remaining; + } + } + } + + protected void render(StringBuilder sb, int indent, boolean atRoot, String atKey, ConfigRenderOptions options) { + if (atKey != null) { + String renderedKey; + if (options.getJson()) + renderedKey = ConfigImplUtil.renderJsonString(atKey); + else + renderedKey = ConfigImplUtil.renderStringUnquotedIfPossible(atKey); + + sb.append(renderedKey); + + if (options.getJson()) { + if (options.getFormatted()) + sb.append(" : "); + else + sb.append(":"); + } else { + // in non-JSON we can omit the colon or equals before an object + if (this instanceof ConfigObject) { + if (options.getFormatted()) + sb.append(' '); + } else { + sb.append("="); + } + } + } + render(sb, indent, atRoot, options); + } + + protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { + Object u = unwrapped(); + sb.append(u.toString()); + } + + @Override + public final String render() { + return render(ConfigRenderOptions.defaults()); + } + + @Override + public final String render(ConfigRenderOptions options) { + StringBuilder sb = new StringBuilder(); + render(sb, 0, true, null, options); + return sb.toString(); + } + + // toString() is a debugging-oriented string but this is defined + // to create a string that would parse back to the value in JSON. + // It only works for primitive values (that would be a single token) + // which are auto-converted to strings when concatenating with + // other strings or by the DefaultTransformer. + String transformToString() { + return null; + } + + SimpleConfig atKey(ConfigOrigin origin, String key) { + Map m = Collections.singletonMap(key, this); + return (new SimpleConfigObject(origin, m)).toConfig(); + } + + @Override + public SimpleConfig atKey(String key) { + return atKey(SimpleConfigOrigin.newSimple("atKey(" + key + ")"), key); + } + + SimpleConfig atPath(ConfigOrigin origin, Path path) { + Path parent = path.parent(); + SimpleConfig result = atKey(origin, path.last()); + while (parent != null) { + String key = parent.last(); + result = result.atKey(origin, key); + parent = parent.parent(); + } + return result; + } + + @Override + public SimpleConfig atPath(String pathExpression) { + SimpleConfigOrigin origin = SimpleConfigOrigin.newSimple("atPath(" + pathExpression + ")"); + return atPath(origin, Path.newPath(pathExpression)); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigBeanImpl.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigBeanImpl.java new file mode 100644 index 0000000..3a8c353 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigBeanImpl.java @@ -0,0 +1,305 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +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.drtshock.playervaults.lib.com.typesafe.config.Config; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigObject; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigList; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigMemorySize; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; +import com.drtshock.playervaults.lib.com.typesafe.config.Optional; + +/** + * Internal implementation detail, not ABI stable, do not touch. + * For use only by the {@link com.typesafe.config} package. + */ +public class ConfigBeanImpl { + + /** + * This is public ONLY for use by the "config" package, DO NOT USE this ABI + * may change. + * @param type of the bean + * @param config config to use + * @param clazz class of the bean + * @return the bean instance + */ + public static T createInternal(Config config, Class clazz) { + if (((SimpleConfig)config).root().resolveStatus() != ResolveStatus.RESOLVED) + throw new ConfigException.NotResolved( + "need to Config#resolve() a config before using it to initialize a bean, see the API docs for Config#resolve()"); + + Map configProps = new HashMap(); + Map originalNames = new HashMap(); + for (Map.Entry configProp : config.root().entrySet()) { + String originalName = configProp.getKey(); + String camelName = ConfigImplUtil.toCamelCase(originalName); + // if a setting is in there both as some hyphen name and the camel name, + // the camel one wins + if (originalNames.containsKey(camelName) && !originalName.equals(camelName)) { + // if we aren't a camel name to start with, we lose. + // if we are or we are the first matching key, we win. + } else { + configProps.put(camelName, (AbstractConfigValue) configProp.getValue()); + originalNames.put(camelName, originalName); + } + } + + BeanInfo beanInfo = null; + try { + beanInfo = Introspector.getBeanInfo(clazz); + } catch (IntrospectionException e) { + throw new ConfigException.BadBean("Could not get bean information for class " + clazz.getName(), e); + } + + try { + List beanProps = new ArrayList(); + for (PropertyDescriptor beanProp : beanInfo.getPropertyDescriptors()) { + if (beanProp.getReadMethod() == null || beanProp.getWriteMethod() == null) { + continue; + } + beanProps.add(beanProp); + } + + // Try to throw all validation issues at once (this does not comprehensively + // find every issue, but it should find common ones). + List problems = new ArrayList(); + for (PropertyDescriptor beanProp : beanProps) { + Method setter = beanProp.getWriteMethod(); + Class parameterClass = setter.getParameterTypes()[0]; + + ConfigValueType expectedType = getValueTypeOrNull(parameterClass); + if (expectedType != null) { + String name = originalNames.get(beanProp.getName()); + if (name == null) + name = beanProp.getName(); + Path path = Path.newKey(name); + AbstractConfigValue configValue = configProps.get(beanProp.getName()); + if (configValue != null) { + SimpleConfig.checkValid(path, expectedType, configValue, problems); + } else { + if (!isOptionalProperty(clazz, beanProp)) { + SimpleConfig.addMissing(problems, expectedType, path, config.origin()); + } + } + } + } + + if (!problems.isEmpty()) { + throw new ConfigException.ValidationFailed(problems); + } + + // Fill in the bean instance + T bean = clazz.newInstance(); + for (PropertyDescriptor beanProp : beanProps) { + Method setter = beanProp.getWriteMethod(); + Type parameterType = setter.getGenericParameterTypes()[0]; + Class parameterClass = setter.getParameterTypes()[0]; + String configPropName = originalNames.get(beanProp.getName()); + // Is the property key missing in the config? + if (configPropName == null) { + // If so, continue if the field is marked as @{link Optional} + if (isOptionalProperty(clazz, beanProp)) { + continue; + } + // Otherwise, raise a {@link Missing} exception right here + throw new ConfigException.Missing(beanProp.getName()); + } + Object unwrapped = getValue(clazz, parameterType, parameterClass, config, configPropName); + setter.invoke(bean, unwrapped); + } + return bean; + } catch (InstantiationException e) { + throw new ConfigException.BadBean(clazz.getName() + " needs a public no-args constructor to be used as a bean", e); + } catch (IllegalAccessException e) { + throw new ConfigException.BadBean(clazz.getName() + " getters and setters are not accessible, they must be for use as a bean", e); + } catch (InvocationTargetException e) { + throw new ConfigException.BadBean("Calling bean method on " + clazz.getName() + " caused an exception", e); + } + } + + // we could magically make this work in many cases by doing + // getAnyRef() (or getValue().unwrapped()), but anytime we + // rely on that, we aren't doing the type conversions Config + // usually does, and we will throw ClassCastException instead + // of a nicer error message giving the name of the bad + // setting. So, instead, we only support a limited number of + // types plus you can always use Object, ConfigValue, Config, + // ConfigObject, etc. as an escape hatch. + private static Object getValue(Class beanClass, Type parameterType, Class parameterClass, Config config, + String configPropName) { + if (parameterClass == Boolean.class || parameterClass == boolean.class) { + return config.getBoolean(configPropName); + } else if (parameterClass == Integer.class || parameterClass == int.class) { + return config.getInt(configPropName); + } else if (parameterClass == Double.class || parameterClass == double.class) { + return config.getDouble(configPropName); + } else if (parameterClass == Long.class || parameterClass == long.class) { + return config.getLong(configPropName); + } else if (parameterClass == String.class) { + return config.getString(configPropName); + } else if (parameterClass == Duration.class) { + return config.getDuration(configPropName); + } else if (parameterClass == ConfigMemorySize.class) { + return config.getMemorySize(configPropName); + } else if (parameterClass == Object.class) { + 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(); + if (typeArgs[0] != String.class || typeArgs[1] != Object.class) { + throw new ConfigException.BadBean("Bean property '" + configPropName + "' of class " + beanClass.getName() + " has unsupported Map<" + typeArgs[0] + "," + typeArgs[1] + ">, only Map is supported right now"); + } + return config.getObject(configPropName).unwrapped(); + } else if (parameterClass == Config.class) { + return config.getConfig(configPropName); + } else if (parameterClass == ConfigObject.class) { + return config.getObject(configPropName); + } else if (parameterClass == ConfigValue.class) { + return config.getValue(configPropName); + } else if (parameterClass == ConfigList.class) { + return config.getList(configPropName); + } else if (parameterClass.isEnum()) { + @SuppressWarnings("unchecked") + Enum enumValue = config.getEnum((Class) parameterClass, configPropName); + return enumValue; + } else if (hasAtLeastOneBeanProperty(parameterClass)) { + return createInternal(config.getConfig(configPropName), parameterClass); + } else { + throw new ConfigException.BadBean("Bean property " + configPropName + " of class " + beanClass.getName() + " has unsupported type " + parameterType); + } + } + + 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]; + + if (elementType == Boolean.class) { + return config.getBooleanList(configPropName); + } else if (elementType == Integer.class) { + return config.getIntList(configPropName); + } else if (elementType == Double.class) { + return config.getDoubleList(configPropName); + } else if (elementType == Long.class) { + return config.getLongList(configPropName); + } else if (elementType == String.class) { + return config.getStringList(configPropName); + } else if (elementType == Duration.class) { + return config.getDurationList(configPropName); + } else if (elementType == ConfigMemorySize.class) { + return config.getMemorySizeList(configPropName); + } else if (elementType == Object.class) { + return config.getAnyRefList(configPropName); + } else if (elementType == Config.class) { + return config.getConfigList(configPropName); + } else if (elementType == ConfigObject.class) { + return config.getObjectList(configPropName); + } else if (elementType == ConfigValue.class) { + return config.getList(configPropName); + } else if (((Class) elementType).isEnum()) { + @SuppressWarnings("unchecked") + List enumValues = config.getEnumList((Class) elementType, configPropName); + return enumValues; + } else if (hasAtLeastOneBeanProperty((Class) elementType)) { + List beanList = new ArrayList(); + List configList = config.getConfigList(configPropName); + for (Config listMember : configList) { + beanList.add(createInternal(listMember, (Class) elementType)); + } + return beanList; + } else { + throw new ConfigException.BadBean("Bean property '" + configPropName + "' of class " + beanClass.getName() + " has unsupported list element type " + elementType); + } + } + + // null if we can't easily say; this is heuristic/best-effort + private static ConfigValueType getValueTypeOrNull(Class parameterClass) { + if (parameterClass == Boolean.class || parameterClass == boolean.class) { + return ConfigValueType.BOOLEAN; + } else if (parameterClass == Integer.class || parameterClass == int.class) { + return ConfigValueType.NUMBER; + } else if (parameterClass == Double.class || parameterClass == double.class) { + return ConfigValueType.NUMBER; + } else if (parameterClass == Long.class || parameterClass == long.class) { + return ConfigValueType.NUMBER; + } else if (parameterClass == String.class) { + return ConfigValueType.STRING; + } else if (parameterClass == Duration.class) { + return null; + } else if (parameterClass == ConfigMemorySize.class) { + return null; + } else if (parameterClass == List.class) { + return ConfigValueType.LIST; + } else if (parameterClass == Map.class) { + return ConfigValueType.OBJECT; + } else if (parameterClass == Config.class) { + return ConfigValueType.OBJECT; + } else if (parameterClass == ConfigObject.class) { + return ConfigValueType.OBJECT; + } else if (parameterClass == ConfigList.class) { + return ConfigValueType.LIST; + } else { + return null; + } + } + + private static boolean hasAtLeastOneBeanProperty(Class clazz) { + BeanInfo beanInfo = null; + try { + beanInfo = Introspector.getBeanInfo(clazz); + } catch (IntrospectionException e) { + return false; + } + + for (PropertyDescriptor beanProp : beanInfo.getPropertyDescriptors()) { + if (beanProp.getReadMethod() != null && beanProp.getWriteMethod() != null) { + return true; + } + } + + return false; + } + + private static boolean isOptionalProperty(Class beanClass, PropertyDescriptor beanProp) { + Field field = getField(beanClass, beanProp.getName()); + return field != null && (field.getAnnotationsByType(Optional.class).length > 0); + } + + private static Field getField(Class beanClass, String fieldName) { + try { + Field field = beanClass.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException e) { + // Don't give up yet. Try to look for field in super class, if any. + } + beanClass = beanClass.getSuperclass(); + if (beanClass == null) { + return null; + } + return getField(beanClass, fieldName); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigBoolean.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigBoolean.java new file mode 100644 index 0000000..b121f45 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigBoolean.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.ObjectStreamException; +import java.io.Serializable; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +final class ConfigBoolean extends AbstractConfigValue implements Serializable { + + private static final long serialVersionUID = 2L; + + final private boolean value; + + ConfigBoolean(ConfigOrigin origin, boolean value) { + super(origin); + this.value = value; + } + + @Override + public ConfigValueType valueType() { + return ConfigValueType.BOOLEAN; + } + + @Override + public Boolean unwrapped() { + return value; + } + + @Override + String transformToString() { + return value ? "true" : "false"; + } + + @Override + protected ConfigBoolean newCopy(ConfigOrigin origin) { + return new ConfigBoolean(origin, value); + } + + // serialization all goes through SerializedConfigValue + private Object writeReplace() throws ObjectStreamException { + return new SerializedConfigValue(this); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigConcatenation.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigConcatenation.java new file mode 100644 index 0000000..82c4785 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigConcatenation.java @@ -0,0 +1,293 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigObject; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigRenderOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +/** + * A ConfigConcatenation represents a list of values to be concatenated (see the + * spec). It only has to exist if at least one value is an unresolved + * substitution, otherwise we could go ahead and collapse the list into a single + * value. + * + * Right now this is always a list of strings and ${} references, but in the + * future should support a list of ConfigList. We may also support + * concatenations of objects, but ConfigDelayedMerge should be used for that + * since a concat of objects really will merge, not concatenate. + */ +final class ConfigConcatenation extends AbstractConfigValue implements Unmergeable, Container { + + final private List pieces; + + ConfigConcatenation(ConfigOrigin origin, List pieces) { + super(origin); + this.pieces = pieces; + + if (pieces.size() < 2) + throw new ConfigException.BugOrBroken("Created concatenation with less than 2 items: " + + this); + + boolean hadUnmergeable = false; + for (AbstractConfigValue p : pieces) { + if (p instanceof ConfigConcatenation) + throw new ConfigException.BugOrBroken( + "ConfigConcatenation should never be nested: " + this); + if (p instanceof Unmergeable) + hadUnmergeable = true; + } + if (!hadUnmergeable) + throw new ConfigException.BugOrBroken( + "Created concatenation without an unmergeable in it: " + this); + } + + private ConfigException.NotResolved notResolved() { + return new ConfigException.NotResolved( + "need to Config#resolve(), see the API docs for Config#resolve(); substitution not resolved: " + + this); + } + + @Override + public ConfigValueType valueType() { + throw notResolved(); + } + + @Override + public Object unwrapped() { + throw notResolved(); + } + + @Override + protected ConfigConcatenation newCopy(ConfigOrigin newOrigin) { + return new ConfigConcatenation(newOrigin, pieces); + } + + @Override + protected boolean ignoresFallbacks() { + // we can never ignore fallbacks because if a child ConfigReference + // is self-referential we have to look lower in the merge stack + // for its value. + return false; + } + + @Override + public Collection unmergedValues() { + return Collections.singleton(this); + } + + private static boolean isIgnoredWhitespace(AbstractConfigValue value) { + return (value instanceof ConfigString) && !((ConfigString)value).wasQuoted(); + } + + /** + * Add left and right, or their merger, to builder. + */ + private static void join(ArrayList builder, AbstractConfigValue origRight) { + AbstractConfigValue left = builder.get(builder.size() - 1); + AbstractConfigValue right = origRight; + + // check for an object which can be converted to a list + // (this will be an object with numeric keys, like foo.0, foo.1) + if (left instanceof ConfigObject && right instanceof SimpleConfigList) { + left = DefaultTransformer.transform(left, ConfigValueType.LIST); + } else if (left instanceof SimpleConfigList && right instanceof ConfigObject) { + right = DefaultTransformer.transform(right, ConfigValueType.LIST); + } + + // Since this depends on the type of two instances, I couldn't think + // of much alternative to an instanceof chain. Visitors are sometimes + // used for multiple dispatch but seems like overkill. + AbstractConfigValue joined = null; + if (left instanceof ConfigObject && right instanceof ConfigObject) { + joined = right.withFallback(left); + } else if (left instanceof SimpleConfigList && right instanceof SimpleConfigList) { + joined = ((SimpleConfigList)left).concatenate((SimpleConfigList)right); + } else if ((left instanceof SimpleConfigList || left instanceof ConfigObject) && + isIgnoredWhitespace(right)) { + joined = left; + // it should be impossible that left is whitespace and right is a list or object + } else if (left instanceof ConfigConcatenation || right instanceof ConfigConcatenation) { + throw new ConfigException.BugOrBroken("unflattened ConfigConcatenation"); + } else if (left instanceof Unmergeable || right instanceof Unmergeable) { + // leave joined=null, cannot join + } else { + // handle primitive type or primitive type mixed with object or list + String s1 = left.transformToString(); + String s2 = right.transformToString(); + if (s1 == null || s2 == null) { + throw new ConfigException.WrongType(left.origin(), + "Cannot concatenate object or list with a non-object-or-list, " + left + + " and " + right + " are not compatible"); + } else { + ConfigOrigin joinedOrigin = SimpleConfigOrigin.mergeOrigins(left.origin(), + right.origin()); + joined = new ConfigString.Quoted(joinedOrigin, s1 + s2); + } + } + + if (joined == null) { + builder.add(right); + } else { + builder.remove(builder.size() - 1); + builder.add(joined); + } + } + + static List consolidate(List pieces) { + if (pieces.size() < 2) { + return pieces; + } else { + List flattened = new ArrayList(pieces.size()); + for (AbstractConfigValue v : pieces) { + if (v instanceof ConfigConcatenation) { + flattened.addAll(((ConfigConcatenation) v).pieces); + } else { + flattened.add(v); + } + } + + ArrayList consolidated = new ArrayList( + flattened.size()); + for (AbstractConfigValue v : flattened) { + if (consolidated.isEmpty()) + consolidated.add(v); + else + join(consolidated, v); + } + + return consolidated; + } + } + + static AbstractConfigValue concatenate(List pieces) { + List consolidated = consolidate(pieces); + if (consolidated.isEmpty()) { + return null; + } else if (consolidated.size() == 1) { + return consolidated.get(0); + } else { + ConfigOrigin mergedOrigin = SimpleConfigOrigin.mergeOrigins(consolidated); + return new ConfigConcatenation(mergedOrigin, consolidated); + } + } + + @Override + ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) + throws NotPossibleToResolve { + if (ConfigImpl.traceSubstitutionsEnabled()) { + int indent = context.depth() + 2; + ConfigImpl.trace(indent - 1, "concatenation has " + pieces.size() + " pieces:"); + int count = 0; + for (AbstractConfigValue v : pieces) { + ConfigImpl.trace(indent, count + ": " + v); + count += 1; + } + } + + // Right now there's no reason to pushParent here because the + // content of ConfigConcatenation should not need to replaceChild, + // but if it did we'd have to do this. + ResolveSource sourceWithParent = source; // .pushParent(this); + ResolveContext newContext = context; + + List resolved = new ArrayList(pieces.size()); + for (AbstractConfigValue p : pieces) { + // to concat into a string we have to do a full resolve, + // so unrestrict the context, then put restriction back afterward + Path restriction = newContext.restrictToChild(); + ResolveResult result = newContext.unrestricted() + .resolve(p, sourceWithParent); + AbstractConfigValue r = result.value; + newContext = result.context.restrict(restriction); + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(context.depth(), "resolved concat piece to " + r); + if (r == null) { + // it was optional... omit + } else { + resolved.add(r); + } + } + + // now need to concat everything + List joined = consolidate(resolved); + // if unresolved is allowed we can just become another + // ConfigConcatenation + if (joined.size() > 1 && context.options().getAllowUnresolved()) + return ResolveResult.make(newContext, new ConfigConcatenation(this.origin(), joined)); + else if (joined.isEmpty()) + // we had just a list of optional references using ${?} + return ResolveResult.make(newContext, null); + else if (joined.size() == 1) + return ResolveResult.make(newContext, joined.get(0)); + else + throw new ConfigException.BugOrBroken("Bug in the library; resolved list was joined to too many values: " + + joined); + } + + @Override + ResolveStatus resolveStatus() { + return ResolveStatus.UNRESOLVED; + } + + @Override + public ConfigConcatenation replaceChild(AbstractConfigValue child, AbstractConfigValue replacement) { + List newPieces = replaceChildInList(pieces, child, replacement); + if (newPieces == null) + return null; + else + return new ConfigConcatenation(origin(), newPieces); + } + + @Override + public boolean hasDescendant(AbstractConfigValue descendant) { + return hasDescendantInList(pieces, descendant); + } + + // when you graft a substitution into another object, + // you have to prefix it with the location in that object + // where you grafted it; but save prefixLength so + // system property and env variable lookups don't get + // broken. + @Override + ConfigConcatenation relativized(Path prefix) { + List newPieces = new ArrayList(); + for (AbstractConfigValue p : pieces) { + newPieces.add(p.relativized(prefix)); + } + return new ConfigConcatenation(origin(), newPieces); + } + + @Override + protected boolean canEqual(Object other) { + return other instanceof ConfigConcatenation; + } + + @Override + public boolean equals(Object other) { + // note that "origin" is deliberately NOT part of equality + if (other instanceof ConfigConcatenation) { + return canEqual(other) && this.pieces.equals(((ConfigConcatenation) other).pieces); + } else { + return false; + } + } + + @Override + public int hashCode() { + // note that "origin" is deliberately NOT part of equality + return pieces.hashCode(); + } + + @Override + protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { + for (AbstractConfigValue p : pieces) { + p.render(sb, indent, atRoot, options); + } + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDelayedMerge.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDelayedMerge.java new file mode 100644 index 0000000..19bbfb7 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDelayedMerge.java @@ -0,0 +1,342 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigRenderOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +/** + * The issue here is that we want to first merge our stack of config files, and + * then we want to evaluate substitutions. But if two substitutions both expand + * to an object, we might need to merge those two objects. Thus, we can't ever + * "override" a substitution when we do a merge; instead we have to save the + * stack of values that should be merged, and resolve the merge when we evaluate + * substitutions. + */ +final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeable, + ReplaceableMergeStack { + + // earlier items in the stack win + final private List stack; + + ConfigDelayedMerge(ConfigOrigin origin, List stack) { + super(origin); + this.stack = stack; + if (stack.isEmpty()) + throw new ConfigException.BugOrBroken( + "creating empty delayed merge value"); + + for (AbstractConfigValue v : stack) { + if (v instanceof ConfigDelayedMerge || v instanceof ConfigDelayedMergeObject) + throw new ConfigException.BugOrBroken( + "placed nested DelayedMerge in a ConfigDelayedMerge, should have consolidated stack"); + } + } + + @Override + public ConfigValueType valueType() { + throw new ConfigException.NotResolved( + "called valueType() on value with unresolved substitutions, need to Config#resolve() first, see API docs"); + } + + @Override + public Object unwrapped() { + throw new ConfigException.NotResolved( + "called unwrapped() on value with unresolved substitutions, need to Config#resolve() first, see API docs"); + } + + @Override + ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) + throws NotPossibleToResolve { + return resolveSubstitutions(this, stack, context, source); + } + + // static method also used by ConfigDelayedMergeObject + static ResolveResult resolveSubstitutions(ReplaceableMergeStack replaceable, + List stack, + ResolveContext context, ResolveSource source) throws NotPossibleToResolve { + if (ConfigImpl.traceSubstitutionsEnabled()) { + ConfigImpl.trace(context.depth(), "delayed merge stack has " + stack.size() + " items:"); + int count = 0; + for (AbstractConfigValue v : stack) { + ConfigImpl.trace(context.depth() + 1, count + ": " + v); + count += 1; + } + } + + // to resolve substitutions, we need to recursively resolve + // the stack of stuff to merge, and merge the stack so + // we won't be a delayed merge anymore. If restrictToChildOrNull + // is non-null, or resolve options allow partial resolves, + // we may remain a delayed merge though. + + ResolveContext newContext = context; + int count = 0; + AbstractConfigValue merged = null; + for (AbstractConfigValue end : stack) { + // the end value may or may not be resolved already + + ResolveSource sourceForEnd; + + if (end instanceof ReplaceableMergeStack) + throw new ConfigException.BugOrBroken("A delayed merge should not contain another one: " + replaceable); + else if (end instanceof Unmergeable) { + // the remainder could be any kind of value, including another + // ConfigDelayedMerge + AbstractConfigValue remainder = replaceable.makeReplacement(context, count + 1); + + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(newContext.depth(), "remainder portion: " + remainder); + + // If, while resolving 'end' we come back to the same + // merge stack, we only want to look _below_ 'end' + // in the stack. So we arrange to replace the + // ConfigDelayedMerge with a value that is only + // the remainder of the stack below this one. + + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(newContext.depth(), "building sourceForEnd"); + + // we resetParents() here because we'll be resolving "end" + // against a root which does NOT contain "end" + sourceForEnd = source.replaceWithinCurrentParent((AbstractConfigValue) replaceable, remainder); + + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(newContext.depth(), " sourceForEnd before reset parents but after replace: " + + sourceForEnd); + + sourceForEnd = sourceForEnd.resetParents(); + } else { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(newContext.depth(), + "will resolve end against the original source with parent pushed"); + + sourceForEnd = source.pushParent(replaceable); + } + + if (ConfigImpl.traceSubstitutionsEnabled()) { + ConfigImpl.trace(newContext.depth(), "sourceForEnd =" + sourceForEnd); + } + + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(newContext.depth(), "Resolving highest-priority item in delayed merge " + end + + " against " + sourceForEnd + " endWasRemoved=" + (source != sourceForEnd)); + ResolveResult result = newContext.resolve(end, sourceForEnd); + AbstractConfigValue resolvedEnd = result.value; + newContext = result.context; + + if (resolvedEnd != null) { + if (merged == null) { + merged = resolvedEnd; + } else { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(newContext.depth() + 1, "merging " + merged + " with fallback " + resolvedEnd); + merged = merged.withFallback(resolvedEnd); + } + } + + count += 1; + + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(newContext.depth(), "stack merged, yielding: " + merged); + } + + return ResolveResult.make(newContext, merged); + } + + @Override + public AbstractConfigValue makeReplacement(ResolveContext context, int skipping) { + return ConfigDelayedMerge.makeReplacement(context, stack, skipping); + } + + // static method also used by ConfigDelayedMergeObject; end may be null + static AbstractConfigValue makeReplacement(ResolveContext context, List stack, int skipping) { + List subStack = stack.subList(skipping, stack.size()); + + if (subStack.isEmpty()) { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(context.depth(), "Nothing else in the merge stack, replacing with null"); + return null; + } else { + // generate a new merge stack from only the remaining items + AbstractConfigValue merged = null; + for (AbstractConfigValue v : subStack) { + if (merged == null) + merged = v; + else + merged = merged.withFallback(v); + } + return merged; + } + } + + @Override + ResolveStatus resolveStatus() { + return ResolveStatus.UNRESOLVED; + } + + @Override + public AbstractConfigValue replaceChild(AbstractConfigValue child, AbstractConfigValue replacement) { + List newStack = replaceChildInList(stack, child, replacement); + if (newStack == null) + return null; + else + return new ConfigDelayedMerge(origin(), newStack); + } + + @Override + public boolean hasDescendant(AbstractConfigValue descendant) { + return hasDescendantInList(stack, descendant); + } + + @Override + ConfigDelayedMerge relativized(Path prefix) { + List newStack = new ArrayList(); + for (AbstractConfigValue o : stack) { + newStack.add(o.relativized(prefix)); + } + return new ConfigDelayedMerge(origin(), newStack); + } + + // static utility shared with ConfigDelayedMergeObject + static boolean stackIgnoresFallbacks(List stack) { + AbstractConfigValue last = stack.get(stack.size() - 1); + return last.ignoresFallbacks(); + } + + @Override + protected boolean ignoresFallbacks() { + return stackIgnoresFallbacks(stack); + } + + @Override + protected AbstractConfigValue newCopy(ConfigOrigin newOrigin) { + return new ConfigDelayedMerge(newOrigin, stack); + } + + @Override + protected final ConfigDelayedMerge mergedWithTheUnmergeable(Unmergeable fallback) { + return (ConfigDelayedMerge) mergedWithTheUnmergeable(stack, fallback); + } + + @Override + protected final ConfigDelayedMerge mergedWithObject(AbstractConfigObject fallback) { + return (ConfigDelayedMerge) mergedWithObject(stack, fallback); + } + + @Override + protected ConfigDelayedMerge mergedWithNonObject(AbstractConfigValue fallback) { + return (ConfigDelayedMerge) mergedWithNonObject(stack, fallback); + } + + @Override + public Collection unmergedValues() { + return stack; + } + + @Override + protected boolean canEqual(Object other) { + return other instanceof ConfigDelayedMerge; + } + + @Override + public boolean equals(Object other) { + // note that "origin" is deliberately NOT part of equality + if (other instanceof ConfigDelayedMerge) { + return canEqual(other) + && (this.stack == ((ConfigDelayedMerge) other).stack || this.stack + .equals(((ConfigDelayedMerge) other).stack)); + } else { + return false; + } + } + + @Override + public int hashCode() { + // note that "origin" is deliberately NOT part of equality + return stack.hashCode(); + } + + @Override + protected void render(StringBuilder sb, int indent, boolean atRoot, String atKey, ConfigRenderOptions options) { + render(stack, sb, indent, atRoot, atKey, options); + } + + @Override + protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { + render(sb, indent, atRoot, null, options); + } + + // static method also used by ConfigDelayedMergeObject. + static void render(List stack, StringBuilder sb, int indent, boolean atRoot, String atKey, + ConfigRenderOptions options) { + boolean commentMerge = options.getComments(); + if (commentMerge) { + sb.append("# unresolved merge of " + stack.size() + " values follows (\n"); + if (atKey == null) { + indent(sb, indent, options); + sb.append("# this unresolved merge will not be parseable because it's at the root of the object\n"); + indent(sb, indent, options); + sb.append("# the HOCON format has no way to list multiple root objects in a single file\n"); + } + } + + List reversed = new ArrayList(); + reversed.addAll(stack); + Collections.reverse(reversed); + + int i = 0; + for (AbstractConfigValue v : reversed) { + if (commentMerge) { + indent(sb, indent, options); + if (atKey != null) { + sb.append("# unmerged value " + i + " for key " + + ConfigImplUtil.renderJsonString(atKey) + " from "); + } else { + sb.append("# unmerged value " + i + " from "); + } + i += 1; + sb.append(v.origin().description()); + sb.append("\n"); + + for (String comment : v.origin().comments()) { + indent(sb, indent, options); + sb.append("# "); + sb.append(comment); + sb.append("\n"); + } + } + indent(sb, indent, options); + + if (atKey != null) { + sb.append(ConfigImplUtil.renderJsonString(atKey)); + if (options.getFormatted()) + sb.append(" : "); + else + sb.append(":"); + } + v.render(sb, indent, atRoot, options); + sb.append(","); + if (options.getFormatted()) + sb.append('\n'); + } + // chop comma or newline + sb.setLength(sb.length() - 1); + if (options.getFormatted()) { + sb.setLength(sb.length() - 1); // also chop comma + sb.append("\n"); // put a newline back + } + if (commentMerge) { + indent(sb, indent, options); + sb.append("# ) end of unresolved merge\n"); + } + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDelayedMergeObject.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDelayedMergeObject.java new file mode 100644 index 0000000..fb50805 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDelayedMergeObject.java @@ -0,0 +1,326 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigList; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigMergeable; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigRenderOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; + +// This is just like ConfigDelayedMerge except we know statically +// that it will turn out to be an object. +final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unmergeable, + ReplaceableMergeStack { + + final private List stack; + + ConfigDelayedMergeObject(ConfigOrigin origin, List stack) { + super(origin); + this.stack = stack; + + if (stack.isEmpty()) + throw new ConfigException.BugOrBroken( + "creating empty delayed merge object"); + if (!(stack.get(0) instanceof AbstractConfigObject)) + throw new ConfigException.BugOrBroken( + "created a delayed merge object not guaranteed to be an object"); + + for (AbstractConfigValue v : stack) { + if (v instanceof ConfigDelayedMerge || v instanceof ConfigDelayedMergeObject) + throw new ConfigException.BugOrBroken( + "placed nested DelayedMerge in a ConfigDelayedMergeObject, should have consolidated stack"); + } + } + + @Override + protected ConfigDelayedMergeObject newCopy(ResolveStatus status, ConfigOrigin origin) { + if (status != resolveStatus()) + throw new ConfigException.BugOrBroken( + "attempt to create resolved ConfigDelayedMergeObject"); + return new ConfigDelayedMergeObject(origin, stack); + } + + @Override + ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) + throws NotPossibleToResolve { + ResolveResult merged = ConfigDelayedMerge.resolveSubstitutions(this, stack, + context, source); + return merged.asObjectResult(); + } + + @Override + public AbstractConfigValue makeReplacement(ResolveContext context, int skipping) { + return ConfigDelayedMerge.makeReplacement(context, stack, skipping); + } + + @Override + ResolveStatus resolveStatus() { + return ResolveStatus.UNRESOLVED; + } + + @Override + public AbstractConfigValue replaceChild(AbstractConfigValue child, AbstractConfigValue replacement) { + List newStack = replaceChildInList(stack, child, replacement); + if (newStack == null) + return null; + else + return new ConfigDelayedMergeObject(origin(), newStack); + } + + @Override + public boolean hasDescendant(AbstractConfigValue descendant) { + return hasDescendantInList(stack, descendant); + } + + @Override + ConfigDelayedMergeObject relativized(Path prefix) { + List newStack = new ArrayList(); + for (AbstractConfigValue o : stack) { + newStack.add(o.relativized(prefix)); + } + return new ConfigDelayedMergeObject(origin(), newStack); + } + + @Override + protected boolean ignoresFallbacks() { + return ConfigDelayedMerge.stackIgnoresFallbacks(stack); + } + + @Override + protected final ConfigDelayedMergeObject mergedWithTheUnmergeable(Unmergeable fallback) { + requireNotIgnoringFallbacks(); + + return (ConfigDelayedMergeObject) mergedWithTheUnmergeable(stack, fallback); + } + + @Override + protected final ConfigDelayedMergeObject mergedWithObject(AbstractConfigObject fallback) { + return mergedWithNonObject(fallback); + } + + @Override + protected final ConfigDelayedMergeObject mergedWithNonObject(AbstractConfigValue fallback) { + requireNotIgnoringFallbacks(); + + return (ConfigDelayedMergeObject) mergedWithNonObject(stack, fallback); + } + + @Override + public ConfigDelayedMergeObject withFallback(ConfigMergeable mergeable) { + return (ConfigDelayedMergeObject) super.withFallback(mergeable); + } + + @Override + public ConfigDelayedMergeObject withOnlyKey(String key) { + throw notResolved(); + } + + @Override + public ConfigDelayedMergeObject withoutKey(String key) { + throw notResolved(); + } + + @Override + protected AbstractConfigObject withOnlyPathOrNull(Path path) { + throw notResolved(); + } + + @Override + AbstractConfigObject withOnlyPath(Path path) { + throw notResolved(); + } + + @Override + AbstractConfigObject withoutPath(Path path) { + throw notResolved(); + } + + @Override + public ConfigDelayedMergeObject withValue(String key, ConfigValue value) { + throw notResolved(); + } + + @Override + ConfigDelayedMergeObject withValue(Path path, ConfigValue value) { + throw notResolved(); + } + + @Override + public Collection unmergedValues() { + return stack; + } + + @Override + protected boolean canEqual(Object other) { + return other instanceof ConfigDelayedMergeObject; + } + + @Override + public boolean equals(Object other) { + // note that "origin" is deliberately NOT part of equality + if (other instanceof ConfigDelayedMergeObject) { + return canEqual(other) + && (this.stack == ((ConfigDelayedMergeObject) other).stack || this.stack + .equals(((ConfigDelayedMergeObject) other).stack)); + } else { + return false; + } + } + + @Override + public int hashCode() { + // note that "origin" is deliberately NOT part of equality + return stack.hashCode(); + } + + @Override + protected void render(StringBuilder sb, int indent, boolean atRoot, String atKey, ConfigRenderOptions options) { + ConfigDelayedMerge.render(stack, sb, indent, atRoot, atKey, options); + } + + @Override + protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { + render(sb, indent, atRoot, null, options); + } + + private static ConfigException notResolved() { + return new ConfigException.NotResolved( + "need to Config#resolve() before using this object, see the API docs for Config#resolve()"); + } + + @Override + public Map unwrapped() { + throw notResolved(); + } + + @Override + public AbstractConfigValue get(Object key) { + throw notResolved(); + } + + @Override + public boolean containsKey(Object key) { + throw notResolved(); + } + + @Override + public boolean containsValue(Object value) { + throw notResolved(); + } + + @Override + public Set> entrySet() { + throw notResolved(); + } + + @Override + public boolean isEmpty() { + throw notResolved(); + } + + @Override + public Set keySet() { + throw notResolved(); + } + + @Override + public int size() { + throw notResolved(); + } + + @Override + public Collection values() { + throw notResolved(); + } + + @Override + protected AbstractConfigValue attemptPeekWithPartialResolve(String key) { + // a partial resolve of a ConfigDelayedMergeObject always results in a + // SimpleConfigObject because all the substitutions in the stack get + // resolved in order to look up the partial. + // So we know here that we have not been resolved at all even + // partially. + // Given that, all this code is probably gratuitous, since the app code + // is likely broken. But in general we only throw NotResolved if you try + // to touch the exact key that isn't resolved, so this is in that + // spirit. + + // we'll be able to return a key if we have a value that ignores + // fallbacks, prior to any unmergeable values. + for (AbstractConfigValue layer : stack) { + if (layer instanceof AbstractConfigObject) { + AbstractConfigObject objectLayer = (AbstractConfigObject) layer; + AbstractConfigValue v = objectLayer.attemptPeekWithPartialResolve(key); + if (v != null) { + if (v.ignoresFallbacks()) { + // we know we won't need to merge anything in to this + // value + return v; + } else { + // we can't return this value because we know there are + // unmergeable values later in the stack that may + // contain values that need to be merged with this + // value. we'll throw the exception when we get to those + // unmergeable values, so continue here. + continue; + } + } else if (layer instanceof Unmergeable) { + // an unmergeable object (which would be another + // ConfigDelayedMergeObject) can't know that a key is + // missing, so it can't return null; it can only return a + // value or throw NotPossibleToResolve + throw new ConfigException.BugOrBroken( + "should not be reached: unmergeable object returned null value"); + } else { + // a non-unmergeable AbstractConfigObject that returned null + // for the key in question is not relevant, we can keep + // looking for a value. + continue; + } + } else if (layer instanceof Unmergeable) { + throw new ConfigException.NotResolved("Key '" + key + "' is not available at '" + + origin().description() + "' because value at '" + + layer.origin().description() + + "' has not been resolved and may turn out to contain or hide '" + key + + "'." + + " Be sure to Config#resolve() before using a config object."); + } else if (layer.resolveStatus() == ResolveStatus.UNRESOLVED) { + // if the layer is not an object, and not a substitution or + // merge, + // then it's something that's unresolved because it _contains_ + // an unresolved object... i.e. it's an array + if (!(layer instanceof ConfigList)) + throw new ConfigException.BugOrBroken("Expecting a list here, not " + layer); + // all later objects will be hidden so we can say we won't find + // the key + return null; + } else { + // non-object, but resolved, like an integer or something. + // has no children so the one we're after won't be in it. + // we would only have this in the stack in case something + // else "looks back" to it due to a cycle. + // anyway at this point we know we can't find the key anymore. + if (!layer.ignoresFallbacks()) { + throw new ConfigException.BugOrBroken( + "resolved non-object should ignore fallbacks"); + } + return null; + } + } + // If we get here, then we never found anything unresolved which means + // the ConfigDelayedMergeObject should not have existed. some + // invariant was violated. + throw new ConfigException.BugOrBroken( + "Delayed merge stack does not contain any unmergeable values"); + + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDocumentParser.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDocumentParser.java new file mode 100644 index 0000000..3862796 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDocumentParser.java @@ -0,0 +1,716 @@ +/** + * Copyright (C) 2015 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.*; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigParseOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigSyntax; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +final class ConfigDocumentParser { + static ConfigNodeRoot parse(Iterator tokens, ConfigOrigin origin, ConfigParseOptions options) { + ConfigSyntax syntax = options.getSyntax() == null ? ConfigSyntax.CONF : options.getSyntax(); + ParseContext context = new ParseContext(syntax, origin, tokens); + return context.parse(); + } + + static AbstractConfigNodeValue parseValue(Iterator tokens, ConfigOrigin origin, ConfigParseOptions options) { + ConfigSyntax syntax = options.getSyntax() == null ? ConfigSyntax.CONF : options.getSyntax(); + ParseContext context = new ParseContext(syntax, origin, tokens); + return context.parseSingleValue(); + } + + static private final class ParseContext { + private int lineNumber; + final private Stack buffer; + final private Iterator tokens; + final private ConfigSyntax flavor; + final private ConfigOrigin baseOrigin; + // this is the number of "equals" we are inside, + // used to modify the error message to reflect that + // someone may think this is .properties format. + int equalsCount; + + ParseContext(ConfigSyntax flavor, ConfigOrigin origin, Iterator tokens) { + lineNumber = 1; + buffer = new Stack(); + this.tokens = tokens; + this.flavor = flavor; + this.equalsCount = 0; + this.baseOrigin = origin; + } + + private Token popToken() { + if (buffer.isEmpty()) { + return tokens.next(); + } + return buffer.pop(); + } + + private Token nextToken() { + Token t = popToken(); + if (flavor == ConfigSyntax.JSON) { + if (Tokens.isUnquotedText(t) && !isUnquotedWhitespace(t)) { + throw parseError("Token not allowed in valid JSON: '" + + Tokens.getUnquotedText(t) + "'"); + } else if (Tokens.isSubstitution(t)) { + throw parseError("Substitutions (${} syntax) not allowed in JSON"); + } + } + return t; + } + + private Token nextTokenCollectingWhitespace(Collection nodes) { + while (true) { + Token t = nextToken(); + if (Tokens.isIgnoredWhitespace(t) || Tokens.isNewline(t) || isUnquotedWhitespace(t)) { + nodes.add(new ConfigNodeSingleToken(t)); + if (Tokens.isNewline(t)) { + lineNumber = t.lineNumber() + 1; + } + } else if (Tokens.isComment(t)) { + nodes.add(new ConfigNodeComment(t)); + } else { + int newNumber = t.lineNumber(); + if (newNumber >= 0) + lineNumber = newNumber; + return t; + } + } + } + + private void putBack(Token token) { + buffer.push(token); + } + + // In arrays and objects, comma can be omitted + // as long as there's at least one newline instead. + // this skips any newlines in front of a comma, + // skips the comma, and returns true if it found + // either a newline or a comma. The iterator + // is left just after the comma or the newline. + private boolean checkElementSeparator(Collection nodes) { + if (flavor == ConfigSyntax.JSON) { + Token t = nextTokenCollectingWhitespace(nodes); + if (t == Tokens.COMMA) { + nodes.add(new ConfigNodeSingleToken(t)); + return true; + } else { + putBack(t); + return false; + } + } else { + boolean sawSeparatorOrNewline = false; + Token t = nextToken(); + while (true) { + if (Tokens.isIgnoredWhitespace(t) || isUnquotedWhitespace(t)) { + nodes.add(new ConfigNodeSingleToken(t)); + } else if (Tokens.isComment(t)) { + nodes.add(new ConfigNodeComment(t)); + } else if (Tokens.isNewline(t)) { + sawSeparatorOrNewline = true; + lineNumber++; + nodes.add(new ConfigNodeSingleToken(t)); + // we want to continue to also eat + // a comma if there is one. + } else if (t == Tokens.COMMA) { + nodes.add(new ConfigNodeSingleToken(t)); + return true; + } else { + // non-newline-or-comma + putBack(t); + return sawSeparatorOrNewline; + } + t = nextToken(); + } + } + } + + // parse a concatenation. If there is no concatenation, return the next value + private AbstractConfigNodeValue consolidateValues(Collection nodes) { + // this trick is not done in JSON + if (flavor == ConfigSyntax.JSON) + return null; + + // create only if we have value tokens + ArrayList values = new ArrayList(); + int valueCount = 0; + + // ignore a newline up front + Token t = nextTokenCollectingWhitespace(nodes); + while (true) { + AbstractConfigNodeValue v = null; + if (Tokens.isIgnoredWhitespace(t)) { + values.add(new ConfigNodeSingleToken(t)); + t = nextToken(); + continue; + } + else if (Tokens.isValue(t) || Tokens.isUnquotedText(t) + || Tokens.isSubstitution(t) || t == Tokens.OPEN_CURLY + || t == Tokens.OPEN_SQUARE) { + // there may be newlines _within_ the objects and arrays + v = parseValue(t); + valueCount++; + } else { + break; + } + + if (v == null) + throw new ConfigException.BugOrBroken("no value"); + + values.add(v); + + t = nextToken(); // but don't consolidate across a newline + } + + putBack(t); + + // No concatenation was seen, but a single value may have been parsed, so return it, and put back + // all succeeding tokens + if (valueCount < 2) { + AbstractConfigNodeValue value = null; + for (AbstractConfigNode node : values) { + if (node instanceof AbstractConfigNodeValue) + value = (AbstractConfigNodeValue)node; + else if (value == null) + nodes.add(node); + else + putBack((new ArrayList(node.tokens())).get(0)); + } + return value; + } + + // Put back any trailing whitespace, as the parent object is responsible for tracking + // any leading/trailing whitespace + for (int i = values.size() - 1; i >= 0; i--) { + if (values.get(i) instanceof ConfigNodeSingleToken) { + putBack(((ConfigNodeSingleToken) values.get(i)).token()); + values.remove(i); + } else { + break; + } + } + return new ConfigNodeConcatenation(values); + } + + private ConfigException parseError(String message) { + return parseError(message, null); + } + + private ConfigException parseError(String message, Throwable cause) { + return new ConfigException.Parse(baseOrigin.withLineNumber(lineNumber), message, cause); + } + + private String addQuoteSuggestion(String badToken, String message) { + return addQuoteSuggestion(null, equalsCount > 0, badToken, message); + } + + private String addQuoteSuggestion(Path lastPath, boolean insideEquals, String badToken, + String message) { + String previousFieldName = lastPath != null ? lastPath.render() : null; + + String part; + if (badToken.equals(Tokens.END.toString())) { + // EOF requires special handling for the error to make sense. + if (previousFieldName != null) + part = message + " (if you intended '" + previousFieldName + + "' to be part of a value, instead of a key, " + + "try adding double quotes around the whole value"; + else + return message; + } else { + if (previousFieldName != null) { + part = message + " (if you intended " + badToken + + " to be part of the value for '" + previousFieldName + "', " + + "try enclosing the value in double quotes"; + } else { + part = message + " (if you intended " + badToken + + " to be part of a key or string value, " + + "try enclosing the key or value in double quotes"; + } + } + + if (insideEquals) + return part + + ", or you may be able to rename the file .properties rather than .conf)"; + else + return part + ")"; + } + + private AbstractConfigNodeValue parseValue(Token t) { + AbstractConfigNodeValue v = null; + int startingEqualsCount = equalsCount; + + if (Tokens.isValue(t) || Tokens.isUnquotedText(t) || Tokens.isSubstitution(t)) { + v = new ConfigNodeSimpleValue(t); + } else if (t == Tokens.OPEN_CURLY) { + v = parseObject(true); + } else if (t== Tokens.OPEN_SQUARE) { + v = parseArray(); + } else { + throw parseError(addQuoteSuggestion(t.toString(), + "Expecting a value but got wrong token: " + t)); + } + + if (equalsCount != startingEqualsCount) + throw new ConfigException.BugOrBroken("Bug in config parser: unbalanced equals count"); + + return v; + } + + private ConfigNodePath parseKey(Token token) { + if (flavor == ConfigSyntax.JSON) { + if (Tokens.isValueWithType(token, ConfigValueType.STRING)) { + return PathParser.parsePathNodeExpression(Collections.singletonList(token).iterator(), + baseOrigin.withLineNumber(lineNumber)); + } else { + throw parseError("Expecting close brace } or a field name here, got " + + token); + } + } else { + List expression = new ArrayList(); + Token t = token; + while (Tokens.isValue(t) || Tokens.isUnquotedText(t)) { + expression.add(t); + t = nextToken(); // note: don't cross a newline + } + + if (expression.isEmpty()) { + throw parseError(ExpectingClosingParenthesisError + t); + } + + putBack(t); // put back the token we ended with + return PathParser.parsePathNodeExpression(expression.iterator(), + baseOrigin.withLineNumber(lineNumber)); + } + } + + private static boolean isIncludeKeyword(Token t) { + return Tokens.isUnquotedText(t) + && Tokens.getUnquotedText(t).equals("include"); + } + + private static boolean isUnquotedWhitespace(Token t) { + if (!Tokens.isUnquotedText(t)) + return false; + + String s = Tokens.getUnquotedText(t); + + for (int i = 0; i < s.length(); ++i) { + char c = s.charAt(i); + if (!ConfigImplUtil.isWhitespace(c)) + return false; + } + return true; + } + + private boolean isKeyValueSeparatorToken(Token t) { + if (flavor == ConfigSyntax.JSON) { + return t == Tokens.COLON; + } else { + return t == Tokens.COLON || t == Tokens.EQUALS || t == Tokens.PLUS_EQUALS; + } + } + + private final String ExpectingClosingParenthesisError = "expecting a close parentheses ')' here, not: "; + + private ConfigNodeInclude parseInclude(ArrayList children) { + + Token t = nextTokenCollectingWhitespace(children); + + // we either have a 'required()' or a quoted string or the "file()" syntax + if (Tokens.isUnquotedText(t)) { + String kindText = Tokens.getUnquotedText(t); + + if (kindText.startsWith("required(")) { + String r = kindText.replaceFirst("required\\(",""); + if (r.length()>0) { + putBack(Tokens.newUnquotedText(t.origin(),r)); + } + + children.add(new ConfigNodeSingleToken(t)); + //children.add(new ConfigNodeSingleToken(tOpen)); + + ConfigNodeInclude res = parseIncludeResource(children, true); + + t = nextTokenCollectingWhitespace(children); + + if (Tokens.isUnquotedText(t) && Tokens.getUnquotedText(t).equals(")")) { + // OK, close paren + } else { + throw parseError(ExpectingClosingParenthesisError + t); + } + + return res; + } else { + putBack(t); + return parseIncludeResource(children, false); + } + } + else { + putBack(t); + return parseIncludeResource(children, false); + } + } + + private ConfigNodeInclude parseIncludeResource(ArrayList children, boolean isRequired) { + Token t = nextTokenCollectingWhitespace(children); + + // we either have a quoted string or the "file()" syntax + if (Tokens.isUnquotedText(t)) { + // get foo( + String kindText = Tokens.getUnquotedText(t); + ConfigIncludeKind kind; + String prefix; + + if (kindText.startsWith("url(")) { + kind = ConfigIncludeKind.URL; + prefix = "url("; + } else if (kindText.startsWith("file(")) { + kind = ConfigIncludeKind.FILE; + prefix = "file("; + } else if (kindText.startsWith("classpath(")) { + kind = ConfigIncludeKind.CLASSPATH; + prefix = "classpath("; + } else { + throw parseError("expecting include parameter to be quoted filename, file(), classpath(), or url(). No spaces are allowed before the open paren. Not expecting: " + + t); + } + String r = kindText.replaceFirst("[^(]*\\(",""); + if (r.length()>0) { + putBack(Tokens.newUnquotedText(t.origin(),r)); + } + + children.add(new ConfigNodeSingleToken(t)); + + // skip space inside parens + t = nextTokenCollectingWhitespace(children); + + // quoted string + if (!Tokens.isValueWithType(t, ConfigValueType.STRING)) { + throw parseError("expecting include " + prefix + ") parameter to be a quoted string, rather than: " + t); + } + children.add(new ConfigNodeSimpleValue(t)); + // skip space after string, inside parens + t = nextTokenCollectingWhitespace(children); + + if (Tokens.isUnquotedText(t) && Tokens.getUnquotedText(t).startsWith(")")) { + String rest = Tokens.getUnquotedText(t).substring(1); + if (rest.length()>0) { + putBack(Tokens.newUnquotedText(t.origin(),rest)); + } + // OK, close paren + } else { + throw parseError(ExpectingClosingParenthesisError + t); + } + + return new ConfigNodeInclude(children, kind, isRequired); + } else if (Tokens.isValueWithType(t, ConfigValueType.STRING)) { + children.add(new ConfigNodeSimpleValue(t)); + return new ConfigNodeInclude(children, ConfigIncludeKind.HEURISTIC, isRequired); + } else { + throw parseError("include keyword is not followed by a quoted string, but by: " + t); + } + } + + private ConfigNodeComplexValue parseObject(boolean hadOpenCurly) { + // invoked just after the OPEN_CURLY (or START, if !hadOpenCurly) + boolean afterComma = false; + Path lastPath = null; + boolean lastInsideEquals = false; + ArrayList objectNodes = new ArrayList(); + ArrayList keyValueNodes; + HashMap keys = new HashMap(); + if (hadOpenCurly) + objectNodes.add(new ConfigNodeSingleToken(Tokens.OPEN_CURLY)); + + while (true) { + Token t = nextTokenCollectingWhitespace(objectNodes); + if (t == Tokens.CLOSE_CURLY) { + if (flavor == ConfigSyntax.JSON && afterComma) { + throw parseError(addQuoteSuggestion(t.toString(), + "expecting a field name after a comma, got a close brace } instead")); + } else if (!hadOpenCurly) { + throw parseError(addQuoteSuggestion(t.toString(), + "unbalanced close brace '}' with no open brace")); + } + objectNodes.add(new ConfigNodeSingleToken(Tokens.CLOSE_CURLY)); + break; + } else if (t == Tokens.END && !hadOpenCurly) { + putBack(t); + break; + } else if (flavor != ConfigSyntax.JSON && isIncludeKeyword(t)) { + ArrayList includeNodes = new ArrayList(); + includeNodes.add(new ConfigNodeSingleToken(t)); + objectNodes.add(parseInclude(includeNodes)); + afterComma = false; + } else { + keyValueNodes = new ArrayList(); + Token keyToken = t; + ConfigNodePath path = parseKey(keyToken); + keyValueNodes.add(path); + Token afterKey = nextTokenCollectingWhitespace(keyValueNodes); + boolean insideEquals = false; + + AbstractConfigNodeValue nextValue; + if (flavor == ConfigSyntax.CONF && afterKey == Tokens.OPEN_CURLY) { + // can omit the ':' or '=' before an object value + nextValue = parseValue(afterKey); + } else { + if (!isKeyValueSeparatorToken(afterKey)) { + throw parseError(addQuoteSuggestion(afterKey.toString(), + "Key '" + path.render() + "' may not be followed by token: " + + afterKey)); + } + + keyValueNodes.add(new ConfigNodeSingleToken(afterKey)); + + if (afterKey == Tokens.EQUALS) { + insideEquals = true; + equalsCount += 1; + } + + nextValue = consolidateValues(keyValueNodes); + if (nextValue == null) { + nextValue = parseValue(nextTokenCollectingWhitespace(keyValueNodes)); + } + } + + keyValueNodes.add(nextValue); + if (insideEquals) { + equalsCount -= 1; + } + lastInsideEquals = insideEquals; + + String key = path.value().first(); + Path remaining = path.value().remainder(); + + if (remaining == null) { + Boolean existing = keys.get(key); + if (existing != null) { + // In strict JSON, dups should be an error; while in + // our custom config language, they should be merged + // if the value is an object (or substitution that + // could become an object). + + if (flavor == ConfigSyntax.JSON) { + throw parseError("JSON does not allow duplicate fields: '" + + key + + "' was already seen"); + } + } + keys.put(key, true); + } else { + if (flavor == ConfigSyntax.JSON) { + throw new ConfigException.BugOrBroken( + "somehow got multi-element path in JSON mode"); + } + keys.put(key, true); + } + + afterComma = false; + objectNodes.add(new ConfigNodeField(keyValueNodes)); + } + + if (checkElementSeparator(objectNodes)) { + // continue looping + afterComma = true; + } else { + t = nextTokenCollectingWhitespace(objectNodes); + if (t == Tokens.CLOSE_CURLY) { + if (!hadOpenCurly) { + throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals, + t.toString(), "unbalanced close brace '}' with no open brace")); + } + objectNodes.add(new ConfigNodeSingleToken(t)); + break; + } else if (hadOpenCurly) { + throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals, + t.toString(), "Expecting close brace } or a comma, got " + t)); + } else { + if (t == Tokens.END) { + putBack(t); + break; + } else { + throw parseError(addQuoteSuggestion(lastPath, lastInsideEquals, + t.toString(), "Expecting end of input or a comma, got " + t)); + } + } + } + } + + return new ConfigNodeObject(objectNodes); + } + + private ConfigNodeComplexValue parseArray() { + ArrayList children = new ArrayList(); + children.add(new ConfigNodeSingleToken(Tokens.OPEN_SQUARE)); + // invoked just after the OPEN_SQUARE + Token t; + + AbstractConfigNodeValue nextValue = consolidateValues(children); + if (nextValue != null) { + children.add(nextValue); + } else { + t = nextTokenCollectingWhitespace(children); + + // special-case the first element + if (t == Tokens.CLOSE_SQUARE) { + children.add(new ConfigNodeSingleToken(t)); + return new ConfigNodeArray(children); + } else if (Tokens.isValue(t) || t == Tokens.OPEN_CURLY + || t == Tokens.OPEN_SQUARE || Tokens.isUnquotedText(t) + || Tokens.isSubstitution(t)) { + nextValue = parseValue(t); + children.add(nextValue); + } else { + throw parseError("List should have ] or a first element after the open [, instead had token: " + + t + + " (if you want " + + t + + " to be part of a string value, then double-quote it)"); + } + } + + // now remaining elements + while (true) { + // just after a value + if (checkElementSeparator(children)) { + // comma (or newline equivalent) consumed + } else { + t = nextTokenCollectingWhitespace(children); + if (t == Tokens.CLOSE_SQUARE) { + children.add(new ConfigNodeSingleToken(t)); + return new ConfigNodeArray(children); + } else { + throw parseError("List should have ended with ] or had a comma, instead had token: " + + t + + " (if you want " + + t + + " to be part of a string value, then double-quote it)"); + } + } + + // now just after a comma + nextValue = consolidateValues(children); + if (nextValue != null) { + children.add(nextValue); + } else { + t = nextTokenCollectingWhitespace(children); + if (Tokens.isValue(t) || t == Tokens.OPEN_CURLY + || t == Tokens.OPEN_SQUARE || Tokens.isUnquotedText(t) + || Tokens.isSubstitution(t)) { + nextValue = parseValue(t); + children.add(nextValue); + } else if (flavor != ConfigSyntax.JSON && t == Tokens.CLOSE_SQUARE) { + // we allow one trailing comma + putBack(t); + } else { + throw parseError("List should have had new element after a comma, instead had token: " + + t + + " (if you want the comma or " + + t + + " to be part of a string value, then double-quote it)"); + } + } + } + } + + ConfigNodeRoot parse() { + ArrayList children = new ArrayList(); + Token t = nextToken(); + if (t == Tokens.START) { + // OK + } else { + throw new ConfigException.BugOrBroken( + "token stream did not begin with START, had " + t); + } + + t = nextTokenCollectingWhitespace(children); + AbstractConfigNode result = null; + boolean missingCurly = false; + if (t == Tokens.OPEN_CURLY || t == Tokens.OPEN_SQUARE) { + result = parseValue(t); + } else { + if (flavor == ConfigSyntax.JSON) { + if (t == Tokens.END) { + throw parseError("Empty document"); + } else { + throw parseError("Document must have an object or array at root, unexpected token: " + + t); + } + } else { + // the root object can omit the surrounding braces. + // this token should be the first field's key, or part + // of it, so put it back. + putBack(t); + missingCurly = true; + result = parseObject(false); + } + } + // Need to pull the children out of the resulting node so we can keep leading + // and trailing whitespace if this was a no-brace object. Otherwise, we need to add + // the result into the list of children. + if (result instanceof ConfigNodeObject && missingCurly) { + children.addAll(((ConfigNodeComplexValue) result).children()); + } else { + children.add(result); + } + t = nextTokenCollectingWhitespace(children); + if (t == Tokens.END) { + if (missingCurly) { + // If there were no braces, the entire document should be treated as a single object + return new ConfigNodeRoot(Collections.singletonList((AbstractConfigNode)new ConfigNodeObject(children)), baseOrigin); + } else { + return new ConfigNodeRoot(children, baseOrigin); + } + } else { + throw parseError("Document has trailing tokens after first object or array: " + + t); + } + } + + // Parse a given input stream into a single value node. Used when doing a replace inside a ConfigDocument. + AbstractConfigNodeValue parseSingleValue() { + Token t = nextToken(); + if (t == Tokens.START) { + // OK + } else { + throw new ConfigException.BugOrBroken( + "token stream did not begin with START, had " + t); + } + + t = nextToken(); + if (Tokens.isIgnoredWhitespace(t) || Tokens.isNewline(t) || isUnquotedWhitespace(t) || Tokens.isComment(t)) { + throw parseError("The value from withValueText cannot have leading or trailing newlines, whitespace, or comments"); + } + if (t == Tokens.END) { + throw parseError("Empty value"); + } + if (flavor == ConfigSyntax.JSON) { + AbstractConfigNodeValue node = parseValue(t); + t = nextToken(); + if (t == Tokens.END) { + return node; + } else { + throw parseError("Parsing JSON and the value set in withValueText was either a concatenation or " + + "had trailing whitespace, newlines, or comments"); + } + } else { + putBack(t); + ArrayList nodes = new ArrayList(); + AbstractConfigNodeValue node = consolidateValues(nodes); + t = nextToken(); + if (t == Tokens.END) { + return node; + } else { + throw parseError("The value from withValueText cannot have leading or trailing newlines, whitespace, or comments"); + } + } + } + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDouble.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDouble.java new file mode 100644 index 0000000..523106a --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigDouble.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.ObjectStreamException; +import java.io.Serializable; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +final class ConfigDouble extends ConfigNumber implements Serializable { + + private static final long serialVersionUID = 2L; + + final private double value; + + ConfigDouble(ConfigOrigin origin, double value, String originalText) { + super(origin, originalText); + this.value = value; + } + + @Override + public ConfigValueType valueType() { + return ConfigValueType.NUMBER; + } + + @Override + public Double unwrapped() { + return value; + } + + @Override + String transformToString() { + String s = super.transformToString(); + if (s == null) + return Double.toString(value); + else + return s; + } + + @Override + protected long longValue() { + return (long) value; + } + + @Override + protected double doubleValue() { + return value; + } + + @Override + protected ConfigDouble newCopy(ConfigOrigin origin) { + return new ConfigDouble(origin, value, originalText); + } + + // serialization all goes through SerializedConfigValue + private Object writeReplace() throws ObjectStreamException { + return new SerializedConfigValue(this); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigImpl.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigImpl.java new file mode 100644 index 0000000..291b476 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigImpl.java @@ -0,0 +1,477 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.net.URL; +import java.time.Duration; +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 com.drtshock.playervaults.lib.com.typesafe.config.Config; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigIncluder; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigMemorySize; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigObject; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigParseOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigParseable; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; +import com.drtshock.playervaults.lib.com.typesafe.config.impl.SimpleIncluder.NameSource; + +/** + * Internal implementation detail, not ABI stable, do not touch. + * For use only by the {@link com.typesafe.config} package. + */ +public class ConfigImpl { + + private static class LoaderCache { + private Config currentSystemProperties; + private WeakReference currentLoader; + private Map cache; + + LoaderCache() { + this.currentSystemProperties = null; + this.currentLoader = new WeakReference(null); + this.cache = new HashMap(); + } + + // 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 updater) { + if (loader != currentLoader.get()) { + // reset the cache if we start using a different loader + cache.clear(); + currentLoader = new WeakReference(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 config from cache updater"); + cache.put(key, config); + } + + return config; + } + } + + private static class LoaderCacheHolder { + static final LoaderCache cache = new LoaderCache(); + } + + public static Config computeCachedConfig(ClassLoader loader, String key, + Callable 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); + } + }; + + public static ConfigObject parseResourcesAnySyntax(Class klass, String resourceBasename, + ConfigParseOptions baseOptions) { + NameSource source = new ClasspathNameSourceWithClass(klass); + return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions); + } + + public static ConfigObject parseResourcesAnySyntax(String resourceBasename, + ConfigParseOptions baseOptions) { + NameSource source = new ClasspathNameSource(); + return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions); + } + + 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); + } + + 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. 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. emptyList()); + } + + private static AbstractConfigObject emptyObject(ConfigOrigin origin) { + // we want null origin to go to SimpleConfigObject.empty() to get the + // origin "empty config" 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); + } + + public static ConfigValue fromAnyRef(Object object, String originDescription) { + ConfigOrigin origin = valueOrigin(originDescription); + return fromAnyRef(object, origin, FromMapMode.KEYS_ARE_KEYS); + } + + public static ConfigObject fromPathMap( + Map 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 AbstractConfigValue) { + return (AbstractConfigValue) object; + } 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.Quoted(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 Duration) { + return new ConfigLong(origin, ((Duration) object).toMillis(), null); + } else if (object instanceof Map) { + if (((Map) object).isEmpty()) + return emptyObject(origin); + + if (mapMode == FromMapMode.KEYS_ARE_KEYS) { + Map values = new HashMap(); + 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 values = new ArrayList(); + while (i.hasNext()) { + AbstractConfigValue v = fromAnyRef(i.next(), origin, mapMode); + values.add(v); + } + + return new SimpleConfigList(origin, values); + } else if (object instanceof ConfigMemorySize) { + return new ConfigLong(origin, ((ConfigMemorySize) object).toBytes(), null); + } 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); + } + } + + public static Config systemPropertiesAsConfig() { + return systemPropertiesAsConfigObject().toConfig(); + } + + public static void reloadSystemPropertiesConfig() { + // ConfigFactory.invalidateCaches() relies on this having the side + // effect that it drops all caches + SystemPropertiesHolder.systemProperties = loadSystemProperties(); + } + + private static AbstractConfigObject loadEnvVariables() { + return PropertiesParser.fromStringMap(newSimpleOrigin("env variables"), System.getenv()); + } + + private static class EnvVariablesHolder { + static volatile AbstractConfigObject envVariables = loadEnvVariables(); + } + + static AbstractConfigObject envVariablesAsConfigObject() { + try { + return EnvVariablesHolder.envVariables; + } catch (ExceptionInInitializerError e) { + throw ConfigImplUtil.extractInitializerError(e); + } + } + + public static Config envVariablesAsConfig() { + 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() { + @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 String SUBSTITUTIONS = "substitutions"; + + private static Map loadDiagnostics() { + Map result = new HashMap(); + result.put(LOADS, false); + result.put(SUBSTITUTIONS, false); + + // People do -Dconfig.trace=foo,bar to enable tracing of different things + String s = System.getProperty("config.trace"); + if (s == null) { + return result; + } else { + String[] keys = s.split(","); + for (String k : keys) { + if (k.equals(LOADS)) { + result.put(LOADS, true); + } else if (k.equals(SUBSTITUTIONS)) { + result.put(SUBSTITUTIONS, true); + } else { + System.err.println("config.trace property contains unknown trace topic '" + + k + "'"); + } + } + return result; + } + } + + private static final Map diagnostics = loadDiagnostics(); + + private static final boolean traceLoadsEnabled = diagnostics.get(LOADS); + private static final boolean traceSubstitutionsEnabled = diagnostics.get(SUBSTITUTIONS); + + static boolean traceLoadsEnabled() { + return traceLoadsEnabled; + } + + static boolean traceSubstitutionsEnabled() { + return traceSubstitutionsEnabled; + } + } + + public static boolean traceLoadsEnabled() { + try { + return DebugHolder.traceLoadsEnabled(); + } catch (ExceptionInInitializerError e) { + throw ConfigImplUtil.extractInitializerError(e); + } + } + + public static boolean traceSubstitutionsEnabled() { + try { + return DebugHolder.traceSubstitutionsEnabled(); + } catch (ExceptionInInitializerError e) { + throw ConfigImplUtil.extractInitializerError(e); + } + } + + public static void trace(String message) { + System.err.println(message); + } + + public static void trace(int indentLevel, String message) { + while (indentLevel > 0) { + System.err.print(" "); + indentLevel -= 1; + } + 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); + } + + public static ConfigOrigin newSimpleOrigin(String description) { + if (description == null) { + return defaultValueOrigin; + } else { + return SimpleConfigOrigin.newSimple(description); + } + } + + public static ConfigOrigin newFileOrigin(String filename) { + return SimpleConfigOrigin.newFile(filename); + } + + public static ConfigOrigin newURLOrigin(URL url) { + return SimpleConfigOrigin.newURL(url); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigImplUtil.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigImplUtil.java new file mode 100644 index 0000000..139254b --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigImplUtil.java @@ -0,0 +1,236 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; + +/** + * Internal implementation detail, not ABI stable, do not touch. + * For use only by the {@link com.typesafe.config} package. + */ +final public class ConfigImplUtil { + static boolean equalsHandlingNull(Object a, Object b) { + if (a == null && b != null) + return false; + else if (a != null && b == null) + return false; + else if (a == b) // catches null == null plus optimizes identity case + return true; + else + return a.equals(b); + } + + static boolean isC0Control(int codepoint) { + return (codepoint >= 0x0000 && codepoint <= 0x001F); + } + + public static String renderJsonString(String s) { + StringBuilder sb = new StringBuilder(); + sb.append('"'); + for (int i = 0; i < s.length(); ++i) { + char c = s.charAt(i); + switch (c) { + case '"': + sb.append("\\\""); + break; + case '\\': + sb.append("\\\\"); + break; + case '\n': + sb.append("\\n"); + break; + case '\b': + sb.append("\\b"); + break; + case '\f': + sb.append("\\f"); + break; + case '\r': + sb.append("\\r"); + break; + case '\t': + sb.append("\\t"); + break; + default: + if (isC0Control(c)) + sb.append(String.format("\\u%04x", (int) c)); + else + sb.append(c); + } + } + sb.append('"'); + return sb.toString(); + } + + static String renderStringUnquotedIfPossible(String s) { + // this can quote unnecessarily as long as it never fails to quote when + // necessary + if (s.length() == 0) + return renderJsonString(s); + + // if it starts with a hyphen or number, we have to quote + // to ensure we end up with a string and not a number + int first = s.codePointAt(0); + if (Character.isDigit(first) || first == '-') + return renderJsonString(s); + + if (s.startsWith("include") || s.startsWith("true") || s.startsWith("false") + || s.startsWith("null") || s.contains("//")) + return renderJsonString(s); + + // only unquote if it's pure alphanumeric + for (int i = 0; i < s.length(); ++i) { + char c = s.charAt(i); + if (!(Character.isLetter(c) || Character.isDigit(c) || c == '-')) + return renderJsonString(s); + } + + return s; + } + + static boolean isWhitespace(int codepoint) { + switch (codepoint) { + // try to hit the most common ASCII ones first, then the nonbreaking + // spaces that Java brokenly leaves out of isWhitespace. + case ' ': + case '\n': + case '\u00A0': + case '\u2007': + case '\u202F': + // this one is the BOM, see + // http://www.unicode.org/faq/utf_bom.html#BOM + // we just accept it as a zero-width nonbreaking space. + case '\uFEFF': + return true; + default: + return Character.isWhitespace(codepoint); + } + } + + public static String unicodeTrim(String s) { + // this is dumb because it looks like there aren't any whitespace + // characters that need surrogate encoding. But, points for + // pedantic correctness! It's future-proof or something. + // String.trim() actually is broken, since there are plenty of + // non-ASCII whitespace characters. + final int length = s.length(); + if (length == 0) + return s; + + int start = 0; + while (start < length) { + char c = s.charAt(start); + if (c == ' ' || c == '\n') { + start += 1; + } else { + int cp = s.codePointAt(start); + if (isWhitespace(cp)) + start += Character.charCount(cp); + else + break; + } + } + + int end = length; + while (end > start) { + char c = s.charAt(end - 1); + if (c == ' ' || c == '\n') { + --end; + } else { + int cp; + int delta; + if (Character.isLowSurrogate(c)) { + cp = s.codePointAt(end - 2); + delta = 2; + } else { + cp = s.codePointAt(end - 1); + delta = 1; + } + if (isWhitespace(cp)) + end -= delta; + else + break; + } + } + return s.substring(start, end); + } + + + public static ConfigException extractInitializerError(ExceptionInInitializerError e) { + Throwable cause = e.getCause(); + if (cause != null && cause instanceof ConfigException) { + return (ConfigException) cause; + } else { + throw e; + } + } + + static File urlToFile(URL url) { + // this isn't really right, clearly, but not sure what to do. + try { + // this will properly handle hex escapes, etc. + return new File(url.toURI()); + } catch (URISyntaxException e) { + // this handles some stuff like file:///c:/Whatever/ + // apparently but mangles handling of hex escapes + return new File(url.getPath()); + } catch (IllegalArgumentException e) { + // file://foo with double slash causes + // IllegalArgumentException "url has an authority component" + return new File(url.getPath()); + } + } + + public static String joinPath(String... elements) { + return (new Path(elements)).render(); + } + + public static String joinPath(List elements) { + return joinPath(elements.toArray(new String[0])); + } + + public static List splitPath(String path) { + Path p = Path.newPath(path); + List elements = new ArrayList(); + while (p != null) { + elements.add(p.first()); + p = p.remainder(); + } + return elements; + } + + public static ConfigOrigin readOrigin(ObjectInputStream in) throws IOException { + return SerializedConfigValue.readOrigin(in, null); + } + + public static void writeOrigin(ObjectOutputStream out, ConfigOrigin origin) throws IOException { + SerializedConfigValue.writeOrigin(new DataOutputStream(out), (SimpleConfigOrigin) origin, + null); + } + + static String toCamelCase(String originalName) { + String[] words = originalName.split("-+"); + StringBuilder nameBuilder = new StringBuilder(originalName.length()); + for (String word : words) { + if (nameBuilder.length() == 0) { + nameBuilder.append(word); + } else { + nameBuilder.append(word.substring(0, 1).toUpperCase()); + nameBuilder.append(word.substring(1)); + } + } + return nameBuilder.toString(); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigIncludeKind.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigIncludeKind.java new file mode 100644 index 0000000..127d200 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigIncludeKind.java @@ -0,0 +1,5 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +enum ConfigIncludeKind { + URL, FILE, CLASSPATH, HEURISTIC +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigInt.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigInt.java new file mode 100644 index 0000000..ae3c69c --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigInt.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.ObjectStreamException; +import java.io.Serializable; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +final class ConfigInt extends ConfigNumber implements Serializable { + + private static final long serialVersionUID = 2L; + + final private int value; + + ConfigInt(ConfigOrigin origin, int value, String originalText) { + super(origin, originalText); + this.value = value; + } + + @Override + public ConfigValueType valueType() { + return ConfigValueType.NUMBER; + } + + @Override + public Integer unwrapped() { + return value; + } + + @Override + String transformToString() { + String s = super.transformToString(); + if (s == null) + return Integer.toString(value); + else + return s; + } + + @Override + protected long longValue() { + return value; + } + + @Override + protected double doubleValue() { + return value; + } + + @Override + protected ConfigInt newCopy(ConfigOrigin origin) { + return new ConfigInt(origin, value, originalText); + } + + // serialization all goes through SerializedConfigValue + private Object writeReplace() throws ObjectStreamException { + return new SerializedConfigValue(this); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigLong.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigLong.java new file mode 100644 index 0000000..40736c2 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigLong.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.ObjectStreamException; +import java.io.Serializable; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +final class ConfigLong extends ConfigNumber implements Serializable { + + private static final long serialVersionUID = 2L; + + final private long value; + + ConfigLong(ConfigOrigin origin, long value, String originalText) { + super(origin, originalText); + this.value = value; + } + + @Override + public ConfigValueType valueType() { + return ConfigValueType.NUMBER; + } + + @Override + public Long unwrapped() { + return value; + } + + @Override + String transformToString() { + String s = super.transformToString(); + if (s == null) + return Long.toString(value); + else + return s; + } + + @Override + protected long longValue() { + return value; + } + + @Override + protected double doubleValue() { + return value; + } + + @Override + protected ConfigLong newCopy(ConfigOrigin origin) { + return new ConfigLong(origin, value, originalText); + } + + // serialization all goes through SerializedConfigValue + private Object writeReplace() throws ObjectStreamException { + return new SerializedConfigValue(this); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeArray.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeArray.java new file mode 100644 index 0000000..ae7398b --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeArray.java @@ -0,0 +1,14 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.Collection; + +final class ConfigNodeArray extends ConfigNodeComplexValue { + ConfigNodeArray(Collection children) { + super(children); + } + + @Override + protected ConfigNodeArray newNode(Collection nodes) { + return new ConfigNodeArray(nodes); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeComment.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeComment.java new file mode 100644 index 0000000..76213fb --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeComment.java @@ -0,0 +1,17 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; + +final class ConfigNodeComment extends ConfigNodeSingleToken { + ConfigNodeComment(Token comment) { + super(comment); + if (!Tokens.isComment(super.token)) { + throw new ConfigException.BugOrBroken("Tried to create a ConfigNodeComment from a non-comment token"); + } + } + + protected String commentText() { + return Tokens.getCommentText(super.token); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeComplexValue.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeComplexValue.java new file mode 100644 index 0000000..7191333 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeComplexValue.java @@ -0,0 +1,52 @@ +/** + * Copyright (C) 2015 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.*; + +abstract class ConfigNodeComplexValue extends AbstractConfigNodeValue { + final protected ArrayList children; + + ConfigNodeComplexValue(Collection children) { + this.children = new ArrayList(children); + } + + final public Collection children() { + return children; + } + + @Override + protected Collection tokens() { + ArrayList tokens = new ArrayList(); + for (AbstractConfigNode child : children) { + tokens.addAll(child.tokens()); + } + return tokens; + } + + protected ConfigNodeComplexValue indentText(AbstractConfigNode indentation) { + ArrayList childrenCopy = new ArrayList(children); + for (int i = 0; i < childrenCopy.size(); i++) { + AbstractConfigNode child = childrenCopy.get(i); + if (child instanceof ConfigNodeSingleToken && + Tokens.isNewline(((ConfigNodeSingleToken) child).token())) { + childrenCopy.add(i + 1, indentation); + i++; + } else if (child instanceof ConfigNodeField) { + AbstractConfigNode value = ((ConfigNodeField) child).value(); + if (value instanceof ConfigNodeComplexValue) { + childrenCopy.set(i, ((ConfigNodeField) child).replaceValue(((ConfigNodeComplexValue) value).indentText(indentation))); + } + } else if (child instanceof ConfigNodeComplexValue) { + childrenCopy.set(i, ((ConfigNodeComplexValue) child).indentText(indentation)); + } + } + return newNode(childrenCopy); + } + + // This method will just call into the object's constructor, but it's needed + // for use in the indentText() method so we can avoid a gross if/else statement + // checking the type of this + abstract ConfigNodeComplexValue newNode(Collection nodes); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeConcatenation.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeConcatenation.java new file mode 100644 index 0000000..962ac9b --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeConcatenation.java @@ -0,0 +1,14 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.Collection; + +final class ConfigNodeConcatenation extends ConfigNodeComplexValue { + ConfigNodeConcatenation(Collection children) { + super(children); + } + + @Override + protected ConfigNodeConcatenation newNode(Collection nodes) { + return new ConfigNodeConcatenation(nodes); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeField.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeField.java new file mode 100644 index 0000000..4a4d7e3 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeField.java @@ -0,0 +1,78 @@ +/** + * Copyright (C) 2015 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +final class ConfigNodeField extends AbstractConfigNode { + final private ArrayList children; + + public ConfigNodeField(Collection children) { + this.children = new ArrayList(children); + } + + @Override + protected Collection tokens() { + ArrayList tokens = new ArrayList(); + for (AbstractConfigNode child : children) { + tokens.addAll(child.tokens()); + } + return tokens; + } + + public ConfigNodeField replaceValue(AbstractConfigNodeValue newValue) { + ArrayList childrenCopy = new ArrayList(children); + for (int i = 0; i < childrenCopy.size(); i++) { + if (childrenCopy.get(i) instanceof AbstractConfigNodeValue) { + childrenCopy.set(i, newValue); + return new ConfigNodeField(childrenCopy); + } + } + throw new ConfigException.BugOrBroken("Field node doesn't have a value"); + } + + public AbstractConfigNodeValue value() { + for (int i = 0; i < children.size(); i++) { + if (children.get(i) instanceof AbstractConfigNodeValue) { + return (AbstractConfigNodeValue)children.get(i); + } + } + throw new ConfigException.BugOrBroken("Field node doesn't have a value"); + } + + public ConfigNodePath path() { + for (int i = 0; i < children.size(); i++) { + if (children.get(i) instanceof ConfigNodePath) { + return (ConfigNodePath)children.get(i); + } + } + throw new ConfigException.BugOrBroken("Field node doesn't have a path"); + } + + protected Token separator() { + for (AbstractConfigNode child : children) { + if (child instanceof ConfigNodeSingleToken) { + Token t = ((ConfigNodeSingleToken) child).token(); + if (t == Tokens.PLUS_EQUALS || t == Tokens.COLON || t == Tokens.EQUALS) { + return t; + } + } + } + return null; + } + + protected List comments() { + List comments = new ArrayList(); + for (AbstractConfigNode child : children) { + if (child instanceof ConfigNodeComment) { + comments.add(((ConfigNodeComment) child).commentText()); + } + } + return comments; + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeInclude.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeInclude.java new file mode 100644 index 0000000..25712b3 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeInclude.java @@ -0,0 +1,46 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.ArrayList; +import java.util.Collection; + +final class ConfigNodeInclude extends AbstractConfigNode { + final private ArrayList children; + final private ConfigIncludeKind kind; + final private boolean isRequired; + + ConfigNodeInclude(Collection children, ConfigIncludeKind kind, boolean isRequired) { + this.children = new ArrayList(children); + this.kind = kind; + this.isRequired = isRequired; + } + + final public Collection children() { + return children; + } + + @Override + protected Collection tokens() { + ArrayList tokens = new ArrayList(); + for (AbstractConfigNode child : children) { + tokens.addAll(child.tokens()); + } + return tokens; + } + + protected ConfigIncludeKind kind() { + return kind; + } + + protected boolean isRequired() { + return isRequired; + } + + protected String name() { + for (AbstractConfigNode n : children) { + if (n instanceof ConfigNodeSimpleValue) { + return (String)Tokens.getValue(((ConfigNodeSimpleValue) n).token()).unwrapped(); + } + } + return null; + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeObject.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeObject.java new file mode 100644 index 0000000..3948950 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeObject.java @@ -0,0 +1,281 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigSyntax; + +import java.util.ArrayList; +import java.util.Collection; + +final class ConfigNodeObject extends ConfigNodeComplexValue { + ConfigNodeObject(Collection children) { + super(children); + } + + @Override + protected ConfigNodeObject newNode(Collection nodes) { + return new ConfigNodeObject(nodes); + } + + public boolean hasValue(Path desiredPath) { + for (AbstractConfigNode node : children) { + if (node instanceof ConfigNodeField) { + ConfigNodeField field = (ConfigNodeField) node; + Path key = field.path().value(); + if (key.equals(desiredPath) || key.startsWith(desiredPath)) { + return true; + } else if (desiredPath.startsWith(key)) { + if (field.value() instanceof ConfigNodeObject) { + ConfigNodeObject obj = (ConfigNodeObject) field.value(); + Path remainingPath = desiredPath.subPath(key.length()); + if (obj.hasValue(remainingPath)) { + return true; + } + } + } + } + } + return false; + } + + protected ConfigNodeObject changeValueOnPath(Path desiredPath, AbstractConfigNodeValue value, ConfigSyntax flavor) { + ArrayList childrenCopy = new ArrayList(super.children); + boolean seenNonMatching = false; + // Copy the value so we can change it to null but not modify the original parameter + AbstractConfigNodeValue valueCopy = value; + for (int i = childrenCopy.size() - 1; i >= 0; i--) { + if (childrenCopy.get(i) instanceof ConfigNodeSingleToken) { + Token t = ((ConfigNodeSingleToken) childrenCopy.get(i)).token(); + // Ensure that, when we are removing settings in JSON, we don't end up with a trailing comma + if (flavor == ConfigSyntax.JSON && !seenNonMatching && t == Tokens.COMMA) { + childrenCopy.remove(i); + } + continue; + } else if (!(childrenCopy.get(i) instanceof ConfigNodeField)) { + continue; + } + ConfigNodeField node = (ConfigNodeField) childrenCopy.get(i); + Path key = node.path().value(); + + // Delete all multi-element paths that start with the desired path, since technically they are duplicates + if ((valueCopy == null && key.equals(desiredPath))|| (key.startsWith(desiredPath) && !key.equals(desiredPath))) { + childrenCopy.remove(i); + // Remove any whitespace or commas after the deleted setting + for (int j = i; j < childrenCopy.size(); j++) { + if (childrenCopy.get(j) instanceof ConfigNodeSingleToken) { + Token t = ((ConfigNodeSingleToken) childrenCopy.get(j)).token(); + if (Tokens.isIgnoredWhitespace(t) || t == Tokens.COMMA) { + childrenCopy.remove(j); + j--; + } else { + break; + } + } else { + break; + } + } + } else if (key.equals(desiredPath)) { + seenNonMatching = true; + AbstractConfigNodeValue indentedValue; + AbstractConfigNode before = i - 1 > 0 ? childrenCopy.get(i - 1) : null; + if (value instanceof ConfigNodeComplexValue && + before instanceof ConfigNodeSingleToken && + Tokens.isIgnoredWhitespace(((ConfigNodeSingleToken) before).token())) + indentedValue = ((ConfigNodeComplexValue) value).indentText(before); + else + indentedValue = value; + childrenCopy.set(i, node.replaceValue(indentedValue)); + valueCopy = null; + } else if (desiredPath.startsWith(key)) { + seenNonMatching = true; + if (node.value() instanceof ConfigNodeObject) { + Path remainingPath = desiredPath.subPath(key.length()); + childrenCopy.set(i, node.replaceValue(((ConfigNodeObject) node.value()).changeValueOnPath(remainingPath, valueCopy, flavor))); + if (valueCopy != null && !node.equals(super.children.get(i))) + valueCopy = null; + } + } else { + seenNonMatching = true; + } + } + return new ConfigNodeObject(childrenCopy); + } + + public ConfigNodeObject setValueOnPath(String desiredPath, AbstractConfigNodeValue value) { + return setValueOnPath(desiredPath, value, ConfigSyntax.CONF); + } + + public ConfigNodeObject setValueOnPath(String desiredPath, AbstractConfigNodeValue value, ConfigSyntax flavor) { + ConfigNodePath path = PathParser.parsePathNode(desiredPath, flavor); + return setValueOnPath(path, value, flavor); + } + + private ConfigNodeObject setValueOnPath(ConfigNodePath desiredPath, AbstractConfigNodeValue value, ConfigSyntax flavor) { + ConfigNodeObject node = changeValueOnPath(desiredPath.value(), value, flavor); + + // If the desired Path did not exist, add it + if (!node.hasValue(desiredPath.value())) { + return node.addValueOnPath(desiredPath, value, flavor); + } + return node; + } + + private Collection indentation() { + boolean seenNewLine = false; + ArrayList indentation = new ArrayList(); + if (children.isEmpty()) { + return indentation; + } + for (int i = 0; i < children.size(); i++) { + if (!seenNewLine) { + if (children.get(i) instanceof ConfigNodeSingleToken && + Tokens.isNewline(((ConfigNodeSingleToken) children.get(i)).token())) { + seenNewLine = true; + indentation.add(new ConfigNodeSingleToken(Tokens.newLine(null))); + } + } else { + if (children.get(i) instanceof ConfigNodeSingleToken && + Tokens.isIgnoredWhitespace(((ConfigNodeSingleToken) children.get(i)).token()) && + i + 1 < children.size() && (children.get(i+1) instanceof ConfigNodeField || + children.get(i+1) instanceof ConfigNodeInclude)) { + // Return the indentation of the first setting on its own line + indentation.add(children.get(i)); + return indentation; + } + } + } + if (indentation.isEmpty()) { + indentation.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " "))); + } else { + // Calculate the indentation of the ending curly-brace to get the indentation of the root object + AbstractConfigNode last = children.get(children.size() - 1); + if (last instanceof ConfigNodeSingleToken && ((ConfigNodeSingleToken) last).token() == Tokens.CLOSE_CURLY) { + AbstractConfigNode beforeLast = children.get(children.size() - 2); + String indent = ""; + if (beforeLast instanceof ConfigNodeSingleToken && + Tokens.isIgnoredWhitespace(((ConfigNodeSingleToken) beforeLast).token())) + indent = ((ConfigNodeSingleToken) beforeLast).token().tokenText(); + indent += " "; + indentation.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, indent))); + return indentation; + } + } + + // The object has no curly braces and is at the root level, so don't indent + return indentation; + } + + protected ConfigNodeObject addValueOnPath(ConfigNodePath desiredPath, AbstractConfigNodeValue value, ConfigSyntax flavor) { + Path path = desiredPath.value(); + ArrayList childrenCopy = new ArrayList(super.children); + ArrayList indentation = new ArrayList(indentation()); + + // If the value we're inserting is a complex value, we'll need to indent it for insertion + AbstractConfigNodeValue indentedValue; + if (value instanceof ConfigNodeComplexValue && !indentation.isEmpty()) { + indentedValue = ((ConfigNodeComplexValue) value).indentText(indentation.get(indentation.size() - 1)); + } else { + indentedValue = value; + } + boolean sameLine = !(indentation.size() > 0 && indentation.get(0) instanceof ConfigNodeSingleToken && + Tokens.isNewline(((ConfigNodeSingleToken) indentation.get(0)).token())); + + // If the path is of length greater than one, see if the value needs to be added further down + if (path.length() > 1) { + for (int i = super.children.size() - 1; i >= 0; i--) { + if (!(super.children.get(i) instanceof ConfigNodeField)) { + continue; + } + ConfigNodeField node = (ConfigNodeField) super.children.get(i); + Path key = node.path().value(); + if (path.startsWith(key) && node.value() instanceof ConfigNodeObject) { + ConfigNodePath remainingPath = desiredPath.subPath(key.length()); + ConfigNodeObject newValue = (ConfigNodeObject) node.value(); + childrenCopy.set(i, node.replaceValue(newValue.addValueOnPath(remainingPath, value, flavor))); + return new ConfigNodeObject(childrenCopy); + } + } + } + + // Otherwise, construct the new setting + boolean startsWithBrace = !super.children.isEmpty() && super.children.get(0) instanceof ConfigNodeSingleToken && + ((ConfigNodeSingleToken) super.children.get(0)).token() == Tokens.OPEN_CURLY; + ArrayList newNodes = new ArrayList(); + newNodes.addAll(indentation); + newNodes.add(desiredPath.first()); + newNodes.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " "))); + newNodes.add(new ConfigNodeSingleToken(Tokens.COLON)); + newNodes.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " "))); + + if (path.length() == 1) { + newNodes.add(indentedValue); + } else { + // If the path is of length greater than one add the required new objects along the path + ArrayList newObjectNodes = new ArrayList(); + newObjectNodes.add(new ConfigNodeSingleToken(Tokens.OPEN_CURLY)); + if (indentation.isEmpty()) { + newObjectNodes.add(new ConfigNodeSingleToken(Tokens.newLine(null))); + } + newObjectNodes.addAll(indentation); + newObjectNodes.add(new ConfigNodeSingleToken(Tokens.CLOSE_CURLY)); + ConfigNodeObject newObject = new ConfigNodeObject(newObjectNodes); + newNodes.add(newObject.addValueOnPath(desiredPath.subPath(1), indentedValue, flavor)); + } + + // Combine these two cases so that we only have to iterate once + if (flavor == ConfigSyntax.JSON || startsWithBrace || sameLine) { + for (int i = childrenCopy.size() - 1; i >= 0; i--) { + + // If we are in JSON or are adding a setting on the same line, we need to add a comma to the + // last setting + if ((flavor == ConfigSyntax.JSON || sameLine) && childrenCopy.get(i) instanceof ConfigNodeField) { + if (i+1 >= childrenCopy.size() || + !(childrenCopy.get(i+1) instanceof ConfigNodeSingleToken + && ((ConfigNodeSingleToken) childrenCopy.get(i+1)).token() == Tokens.COMMA)) + childrenCopy.add(i+1, new ConfigNodeSingleToken(Tokens.COMMA)); + break; + } + + // Add the value into the copy of the children map, keeping any whitespace/newlines + // before the close curly brace + if (startsWithBrace && childrenCopy.get(i) instanceof ConfigNodeSingleToken && + ((ConfigNodeSingleToken) childrenCopy.get(i)).token == Tokens.CLOSE_CURLY) { + AbstractConfigNode previous = childrenCopy.get(i - 1); + if (previous instanceof ConfigNodeSingleToken && + Tokens.isNewline(((ConfigNodeSingleToken) previous).token())) { + childrenCopy.add(i - 1, new ConfigNodeField(newNodes)); + i--; + } else if (previous instanceof ConfigNodeSingleToken && + Tokens.isIgnoredWhitespace(((ConfigNodeSingleToken) previous).token())) { + AbstractConfigNode beforePrevious = childrenCopy.get(i - 2); + if (sameLine) { + childrenCopy.add(i - 1, new ConfigNodeField(newNodes)); + i--; + } + else if (beforePrevious instanceof ConfigNodeSingleToken && + Tokens.isNewline(((ConfigNodeSingleToken) beforePrevious).token())) { + childrenCopy.add(i - 2, new ConfigNodeField(newNodes)); + i -= 2; + } else { + childrenCopy.add(i, new ConfigNodeField(newNodes)); + } + + } + else + childrenCopy.add(i, new ConfigNodeField(newNodes)); + } + } + } + if (!startsWithBrace) { + if (!childrenCopy.isEmpty() && childrenCopy.get(childrenCopy.size() - 1) instanceof ConfigNodeSingleToken && + Tokens.isNewline(((ConfigNodeSingleToken) childrenCopy.get(childrenCopy.size() - 1)).token())) + childrenCopy.add(childrenCopy.size() - 1, new ConfigNodeField(newNodes)); + else + childrenCopy.add(new ConfigNodeField(newNodes)); + } + return new ConfigNodeObject(childrenCopy); + } + + public ConfigNodeObject removeValueOnPath(String desiredPath, ConfigSyntax flavor) { + Path path = PathParser.parsePathNode(desiredPath, flavor).value(); + return changeValueOnPath(path, null, flavor); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodePath.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodePath.java new file mode 100644 index 0000000..4489841 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodePath.java @@ -0,0 +1,52 @@ +/** + * Copyright (C) 2015 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; + +import java.util.ArrayList; +import java.util.Collection; + +final class ConfigNodePath extends AbstractConfigNode { + final private Path path; + final ArrayList tokens; + ConfigNodePath(Path path, Collection tokens) { + this.path = path; + this.tokens = new ArrayList(tokens); + } + + @Override + protected Collection tokens() { + return tokens; + } + + protected Path value() { + return path; + } + + protected ConfigNodePath subPath(int toRemove) { + int periodCount = 0; + ArrayList tokensCopy = new ArrayList(tokens); + for (int i = 0; i < tokensCopy.size(); i++) { + if (Tokens.isUnquotedText(tokensCopy.get(i)) && + tokensCopy.get(i).tokenText().equals(".")) + periodCount++; + + if (periodCount == toRemove) { + return new ConfigNodePath(path.subPath(toRemove), tokensCopy.subList(i + 1, tokensCopy.size())); + } + } + throw new ConfigException.BugOrBroken("Tried to remove too many elements from a Path node"); + } + + protected ConfigNodePath first() { + ArrayList tokensCopy = new ArrayList(tokens); + for (int i = 0; i < tokensCopy.size(); i++) { + if (Tokens.isUnquotedText(tokensCopy.get(i)) && + tokensCopy.get(i).tokenText().equals(".")) + return new ConfigNodePath(path.subPath(0, 1), tokensCopy.subList(0, i)); + } + return this; + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeRoot.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeRoot.java new file mode 100644 index 0000000..926c0f6 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeRoot.java @@ -0,0 +1,67 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigSyntax; + +import java.util.ArrayList; +import java.util.Collection; + +final class ConfigNodeRoot extends ConfigNodeComplexValue { + final private ConfigOrigin origin; + + ConfigNodeRoot(Collection children, ConfigOrigin origin) { + super(children); + this.origin = origin; + } + + @Override + protected ConfigNodeRoot newNode(Collection nodes) { + throw new ConfigException.BugOrBroken("Tried to indent the root object"); + } + + protected ConfigNodeComplexValue value() { + for (AbstractConfigNode node : children) { + if (node instanceof ConfigNodeComplexValue) { + return (ConfigNodeComplexValue)node; + } + } + throw new ConfigException.BugOrBroken("ConfigNodeRoot did not contain a value"); + } + + protected ConfigNodeRoot setValue(String desiredPath, AbstractConfigNodeValue value, ConfigSyntax flavor) { + ArrayList childrenCopy = new ArrayList(children); + for (int i = 0; i < childrenCopy.size(); i++) { + AbstractConfigNode node = childrenCopy.get(i); + if (node instanceof ConfigNodeComplexValue) { + if (node instanceof ConfigNodeArray) { + throw new ConfigException.WrongType(origin, "The ConfigDocument had an array at the root level, and values cannot be modified inside an array."); + } else if (node instanceof ConfigNodeObject) { + if (value == null) { + childrenCopy.set(i, ((ConfigNodeObject)node).removeValueOnPath(desiredPath, flavor)); + } else { + childrenCopy.set(i, ((ConfigNodeObject) node).setValueOnPath(desiredPath, value, flavor)); + } + return new ConfigNodeRoot(childrenCopy, origin); + } + } + } + throw new ConfigException.BugOrBroken("ConfigNodeRoot did not contain a value"); + } + + protected boolean hasValue(String desiredPath) { + Path path = PathParser.parsePath(desiredPath); + ArrayList childrenCopy = new ArrayList(children); + for (int i = 0; i < childrenCopy.size(); i++) { + AbstractConfigNode node = childrenCopy.get(i); + if (node instanceof ConfigNodeComplexValue) { + if (node instanceof ConfigNodeArray) { + throw new ConfigException.WrongType(origin, "The ConfigDocument had an array at the root level, and values cannot be modified inside an array."); + } else if (node instanceof ConfigNodeObject) { + return ((ConfigNodeObject) node).hasValue(path); + } + } + } + throw new ConfigException.BugOrBroken("ConfigNodeRoot did not contain a value"); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeSimpleValue.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeSimpleValue.java new file mode 100644 index 0000000..5998e84 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeSimpleValue.java @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2015 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +final class ConfigNodeSimpleValue extends AbstractConfigNodeValue { + final Token token; + ConfigNodeSimpleValue(Token value) { + token = value; + } + + @Override + protected Collection tokens() { + return Collections.singletonList(token); + } + + protected Token token() { return token; } + + protected AbstractConfigValue value() { + if (Tokens.isValue(token)) + return Tokens.getValue(token); + else if (Tokens.isUnquotedText(token)) + return new ConfigString.Unquoted(token.origin(), Tokens.getUnquotedText(token)); + else if (Tokens.isSubstitution(token)) { + List expression = Tokens.getSubstitutionPathExpression(token); + Path path = PathParser.parsePathExpression(expression.iterator(), token.origin()); + boolean optional = Tokens.getSubstitutionOptional(token); + + return new ConfigReference(token.origin(), new SubstitutionExpression(path, optional)); + } + throw new ConfigException.BugOrBroken("ConfigNodeSimpleValue did not contain a valid value token"); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeSingleToken.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeSingleToken.java new file mode 100644 index 0000000..fccd06c --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNodeSingleToken.java @@ -0,0 +1,21 @@ +/** + * Copyright (C) 2015 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.Collection; +import java.util.Collections; + +class ConfigNodeSingleToken extends AbstractConfigNode { + final Token token; + ConfigNodeSingleToken(Token t) { + token = t; + } + + @Override + protected Collection tokens() { + return Collections.singletonList(token); + } + + protected Token token() { return token; } +} \ No newline at end of file diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNull.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNull.java new file mode 100644 index 0000000..50842df --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNull.java @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.ObjectStreamException; +import java.io.Serializable; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigRenderOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +/** + * This exists because sometimes null is not the same as missing. Specifically, + * if a value is set to null we can give a better error message (indicating + * where it was set to null) in case someone asks for the value. Also, null + * overrides values set "earlier" in the search path, while missing values do + * not. + * + */ +final class ConfigNull extends AbstractConfigValue implements Serializable { + + private static final long serialVersionUID = 2L; + + ConfigNull(ConfigOrigin origin) { + super(origin); + } + + @Override + public ConfigValueType valueType() { + return ConfigValueType.NULL; + } + + @Override + public Object unwrapped() { + return null; + } + + @Override + String transformToString() { + return "null"; + } + + @Override + protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { + sb.append("null"); + } + + @Override + protected ConfigNull newCopy(ConfigOrigin origin) { + return new ConfigNull(origin); + } + + // serialization all goes through SerializedConfigValue + private Object writeReplace() throws ObjectStreamException { + return new SerializedConfigValue(this); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNumber.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNumber.java new file mode 100644 index 0000000..1c5c380 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigNumber.java @@ -0,0 +1,110 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.ObjectStreamException; +import java.io.Serializable; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; + +abstract class ConfigNumber extends AbstractConfigValue implements Serializable { + + private static final long serialVersionUID = 2L; + + // This is so when we concatenate a number into a string (say it appears in + // a sentence) we always have it exactly as the person typed it into the + // config file. It's purely cosmetic; equals/hashCode don't consider this + // for example. + final protected String originalText; + + protected ConfigNumber(ConfigOrigin origin, String originalText) { + super(origin); + this.originalText = originalText; + } + + @Override + public abstract Number unwrapped(); + + @Override + String transformToString() { + return originalText; + } + + int intValueRangeChecked(String path) { + long l = longValue(); + if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) { + throw new ConfigException.WrongType(origin(), path, "32-bit integer", + "out-of-range value " + l); + } + return (int) l; + } + + protected abstract long longValue(); + + protected abstract double doubleValue(); + + private boolean isWhole() { + long asLong = longValue(); + return asLong == doubleValue(); + } + + @Override + protected boolean canEqual(Object other) { + return other instanceof ConfigNumber; + } + + @Override + public boolean equals(Object other) { + // note that "origin" is deliberately NOT part of equality + if (other instanceof ConfigNumber && canEqual(other)) { + ConfigNumber n = (ConfigNumber) other; + if (isWhole()) { + return n.isWhole() && this.longValue() == n.longValue(); + } else { + return (!n.isWhole()) && this.doubleValue() == n.doubleValue(); + } + } else { + return false; + } + } + + @Override + public int hashCode() { + // note that "origin" is deliberately NOT part of equality + + // this matches what standard Long.hashCode and Double.hashCode + // do, though I don't think it really matters. + long asLong; + if (isWhole()) { + asLong = longValue(); + } else { + asLong = Double.doubleToLongBits(doubleValue()); + } + return (int) (asLong ^ (asLong >>> 32)); + } + + static ConfigNumber newNumber(ConfigOrigin origin, long number, + String originalText) { + if (number <= Integer.MAX_VALUE && number >= Integer.MIN_VALUE) + return new ConfigInt(origin, (int) number, originalText); + else + return new ConfigLong(origin, number, originalText); + } + + static ConfigNumber newNumber(ConfigOrigin origin, double number, + String originalText) { + long asLong = (long) number; + if (asLong == number) { + return newNumber(origin, asLong, originalText); + } else { + return new ConfigDouble(origin, number, originalText); + } + } + + // serialization all goes through SerializedConfigValue + private Object writeReplace() throws ObjectStreamException { + return new SerializedConfigValue(this); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigParser.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigParser.java new file mode 100644 index 0000000..19a4bc0 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigParser.java @@ -0,0 +1,426 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigIncludeContext; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigParseOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigSyntax; + +final class ConfigParser { + static AbstractConfigValue parse(ConfigNodeRoot document, + ConfigOrigin origin, ConfigParseOptions options, + ConfigIncludeContext includeContext) { + ParseContext context = new ParseContext(options.getSyntax(), origin, document, + SimpleIncluder.makeFull(options.getIncluder()), includeContext); + return context.parse(); + } + + static private final class ParseContext { + private int lineNumber; + final private ConfigNodeRoot document; + final private FullIncluder includer; + final private ConfigIncludeContext includeContext; + final private ConfigSyntax flavor; + final private ConfigOrigin baseOrigin; + final private LinkedList pathStack; + + // the number of lists we are inside; this is used to detect the "cannot + // generate a reference to a list element" problem, and once we fix that + // problem we should be able to get rid of this variable. + int arrayCount; + + ParseContext(ConfigSyntax flavor, ConfigOrigin origin, ConfigNodeRoot document, + FullIncluder includer, ConfigIncludeContext includeContext) { + lineNumber = 1; + this.document = document; + this.flavor = flavor; + this.baseOrigin = origin; + this.includer = includer; + this.includeContext = includeContext; + this.pathStack = new LinkedList(); + this.arrayCount = 0; + } + + // merge a bunch of adjacent values into one + // value; change unquoted text into a string + // value. + private AbstractConfigValue parseConcatenation(ConfigNodeConcatenation n) { + // this trick is not done in JSON + if (flavor == ConfigSyntax.JSON) + throw new ConfigException.BugOrBroken("Found a concatenation node in JSON"); + + List values = new ArrayList(); + + for (AbstractConfigNode node : n.children()) { + AbstractConfigValue v = null; + if (node instanceof AbstractConfigNodeValue) { + v = parseValue((AbstractConfigNodeValue)node, null); + values.add(v); + } + } + + return ConfigConcatenation.concatenate(values); + } + + private SimpleConfigOrigin lineOrigin() { + return ((SimpleConfigOrigin) baseOrigin).withLineNumber(lineNumber); + } + + private ConfigException parseError(String message) { + return parseError(message, null); + } + + private ConfigException parseError(String message, Throwable cause) { + return new ConfigException.Parse(lineOrigin(), message, cause); + } + + private Path fullCurrentPath() { + // pathStack has top of stack at front + if (pathStack.isEmpty()) + throw new ConfigException.BugOrBroken("Bug in parser; tried to get current path when at root"); + else + return new Path(pathStack.descendingIterator()); + } + + private AbstractConfigValue parseValue(AbstractConfigNodeValue n, List comments) { + AbstractConfigValue v; + + int startingArrayCount = arrayCount; + + if (n instanceof ConfigNodeSimpleValue) { + v = ((ConfigNodeSimpleValue) n).value(); + } else if (n instanceof ConfigNodeObject) { + v = parseObject((ConfigNodeObject)n); + } else if (n instanceof ConfigNodeArray) { + v = parseArray((ConfigNodeArray)n); + } else if (n instanceof ConfigNodeConcatenation) { + v = parseConcatenation((ConfigNodeConcatenation)n); + } else { + throw parseError("Expecting a value but got wrong node type: " + n.getClass()); + } + + if (comments != null && !comments.isEmpty()) { + v = v.withOrigin(v.origin().prependComments(new ArrayList(comments))); + comments.clear(); + } + + if (arrayCount != startingArrayCount) + throw new ConfigException.BugOrBroken("Bug in config parser: unbalanced array count"); + + return v; + } + + private static AbstractConfigObject createValueUnderPath(Path path, + AbstractConfigValue value) { + // for path foo.bar, we are creating + // { "foo" : { "bar" : value } } + List keys = new ArrayList(); + + String key = path.first(); + Path remaining = path.remainder(); + while (key != null) { + keys.add(key); + if (remaining == null) { + break; + } else { + key = remaining.first(); + remaining = remaining.remainder(); + } + } + + // the withComments(null) is to ensure comments are only + // on the exact leaf node they apply to. + // a comment before "foo.bar" applies to the full setting + // "foo.bar" not also to "foo" + ListIterator i = keys.listIterator(keys.size()); + String deepest = i.previous(); + AbstractConfigObject o = new SimpleConfigObject(value.origin().withComments(null), + Collections. singletonMap( + deepest, value)); + while (i.hasPrevious()) { + Map m = Collections. singletonMap( + i.previous(), o); + o = new SimpleConfigObject(value.origin().withComments(null), m); + } + + return o; + } + + private void parseInclude(Map values, ConfigNodeInclude n) { + boolean isRequired = n.isRequired(); + ConfigIncludeContext cic = includeContext.setParseOptions(includeContext.parseOptions().setAllowMissing(!isRequired)); + + AbstractConfigObject obj; + switch (n.kind()) { + case URL: + URL url; + try { + url = new URL(n.name()); + } catch (MalformedURLException e) { + throw parseError("include url() specifies an invalid URL: " + n.name(), e); + } + obj = (AbstractConfigObject) includer.includeURL(cic, url); + break; + + case FILE: + obj = (AbstractConfigObject) includer.includeFile(cic, + new File(n.name())); + break; + + case CLASSPATH: + obj = (AbstractConfigObject) includer.includeResources(cic, n.name()); + break; + + case HEURISTIC: + obj = (AbstractConfigObject) includer + .include(cic, n.name()); + break; + + default: + throw new ConfigException.BugOrBroken("should not be reached"); + } + + // we really should make this work, but for now throwing an + // exception is better than producing an incorrect result. + // See https://github.com/lightbend/config/issues/160 + if (arrayCount > 0 && obj.resolveStatus() != ResolveStatus.RESOLVED) + throw parseError("Due to current limitations of the config parser, when an include statement is nested inside a list value, " + + "${} substitutions inside the included file cannot be resolved correctly. Either move the include outside of the list value or " + + "remove the ${} statements from the included file."); + + if (!pathStack.isEmpty()) { + Path prefix = fullCurrentPath(); + obj = obj.relativized(prefix); + } + + for (String key : obj.keySet()) { + AbstractConfigValue v = obj.get(key); + AbstractConfigValue existing = values.get(key); + if (existing != null) { + values.put(key, v.withFallback(existing)); + } else { + values.put(key, v); + } + } + } + + private AbstractConfigObject parseObject(ConfigNodeObject n) { + Map values = new HashMap(); + SimpleConfigOrigin objectOrigin = lineOrigin(); + boolean lastWasNewline = false; + + ArrayList nodes = new ArrayList(n.children()); + List comments = new ArrayList(); + for (int i = 0; i < nodes.size(); i++) { + AbstractConfigNode node = nodes.get(i); + if (node instanceof ConfigNodeComment) { + lastWasNewline = false; + comments.add(((ConfigNodeComment) node).commentText()); + } else if (node instanceof ConfigNodeSingleToken && Tokens.isNewline(((ConfigNodeSingleToken) node).token())) { + lineNumber++; + if (lastWasNewline) { + // Drop all comments if there was a blank line and start a new comment block + comments.clear(); + } + lastWasNewline = true; + } else if (flavor != ConfigSyntax.JSON && node instanceof ConfigNodeInclude) { + parseInclude(values, (ConfigNodeInclude)node); + lastWasNewline = false; + } else if (node instanceof ConfigNodeField) { + lastWasNewline = false; + Path path = ((ConfigNodeField) node).path().value(); + comments.addAll(((ConfigNodeField) node).comments()); + + // path must be on-stack while we parse the value + pathStack.push(path); + if (((ConfigNodeField) node).separator() == Tokens.PLUS_EQUALS) { + // we really should make this work, but for now throwing + // an exception is better than producing an incorrect + // result. See + // https://github.com/lightbend/config/issues/160 + if (arrayCount > 0) + throw parseError("Due to current limitations of the config parser, += does not work nested inside a list. " + + "+= expands to a ${} substitution and the path in ${} cannot currently refer to list elements. " + + "You might be able to move the += outside of the list and then refer to it from inside the list with ${}."); + + // because we will put it in an array after the fact so + // we want this to be incremented during the parseValue + // below in order to throw the above exception. + arrayCount += 1; + } + + AbstractConfigNodeValue valueNode; + AbstractConfigValue newValue; + + valueNode = ((ConfigNodeField) node).value(); + + // comments from the key token go to the value token + newValue = parseValue(valueNode, comments); + + if (((ConfigNodeField) node).separator() == Tokens.PLUS_EQUALS) { + arrayCount -= 1; + + List concat = new ArrayList(2); + AbstractConfigValue previousRef = new ConfigReference(newValue.origin(), + new SubstitutionExpression(fullCurrentPath(), true /* optional */)); + AbstractConfigValue list = new SimpleConfigList(newValue.origin(), + Collections.singletonList(newValue)); + concat.add(previousRef); + concat.add(list); + newValue = ConfigConcatenation.concatenate(concat); + } + + // Grab any trailing comments on the same line + if (i < nodes.size() - 1) { + i++; + while (i < nodes.size()) { + if (nodes.get(i) instanceof ConfigNodeComment) { + ConfigNodeComment comment = (ConfigNodeComment) nodes.get(i); + newValue = newValue.withOrigin(newValue.origin().appendComments( + Collections.singletonList(comment.commentText()))); + break; + } else if (nodes.get(i) instanceof ConfigNodeSingleToken) { + ConfigNodeSingleToken curr = (ConfigNodeSingleToken) nodes.get(i); + if (curr.token() == Tokens.COMMA || Tokens.isIgnoredWhitespace(curr.token())) { + // keep searching, as there could still be a comment + } else { + i--; + break; + } + } else { + i--; + break; + } + i++; + } + } + + pathStack.pop(); + + String key = path.first(); + Path remaining = path.remainder(); + + if (remaining == null) { + AbstractConfigValue existing = values.get(key); + if (existing != null) { + // In strict JSON, dups should be an error; while in + // our custom config language, they should be merged + // if the value is an object (or substitution that + // could become an object). + + if (flavor == ConfigSyntax.JSON) { + throw parseError("JSON does not allow duplicate fields: '" + + key + + "' was already seen at " + + existing.origin().description()); + } else { + newValue = newValue.withFallback(existing); + } + } + values.put(key, newValue); + } else { + if (flavor == ConfigSyntax.JSON) { + throw new ConfigException.BugOrBroken( + "somehow got multi-element path in JSON mode"); + } + + AbstractConfigObject obj = createValueUnderPath( + remaining, newValue); + AbstractConfigValue existing = values.get(key); + if (existing != null) { + obj = obj.withFallback(existing); + } + values.put(key, obj); + } + } + } + + return new SimpleConfigObject(objectOrigin, values); + } + + private SimpleConfigList parseArray(ConfigNodeArray n) { + arrayCount += 1; + + SimpleConfigOrigin arrayOrigin = lineOrigin(); + List values = new ArrayList(); + + boolean lastWasNewLine = false; + List comments = new ArrayList(); + + AbstractConfigValue v = null; + + for (AbstractConfigNode node : n.children()) { + if (node instanceof ConfigNodeComment) { + comments.add(((ConfigNodeComment) node).commentText()); + lastWasNewLine = false; + } else if (node instanceof ConfigNodeSingleToken && Tokens.isNewline(((ConfigNodeSingleToken) node).token())) { + lineNumber++; + if (lastWasNewLine && v == null) { + comments.clear(); + } else if (v != null) { + values.add(v.withOrigin(v.origin().appendComments(new ArrayList(comments)))); + comments.clear(); + v = null; + } + lastWasNewLine = true; + } else if (node instanceof AbstractConfigNodeValue) { + lastWasNewLine = false; + if (v != null) { + values.add(v.withOrigin(v.origin().appendComments(new ArrayList(comments)))); + comments.clear(); + } + v = parseValue((AbstractConfigNodeValue)node, comments); + } + } + // There shouldn't be any comments at this point, but add them just in case + if (v != null) { + values.add(v.withOrigin(v.origin().appendComments(new ArrayList(comments)))); + } + arrayCount -= 1; + return new SimpleConfigList(arrayOrigin, values); + } + + AbstractConfigValue parse() { + AbstractConfigValue result = null; + ArrayList comments = new ArrayList(); + boolean lastWasNewLine = false; + for (AbstractConfigNode node : document.children()) { + if (node instanceof ConfigNodeComment) { + comments.add(((ConfigNodeComment) node).commentText()); + lastWasNewLine = false; + } else if (node instanceof ConfigNodeSingleToken) { + Token t = ((ConfigNodeSingleToken) node).token(); + if (Tokens.isNewline(t)) { + lineNumber++; + if (lastWasNewLine && result == null) { + comments.clear(); + } else if (result != null) { + result = result.withOrigin(result.origin().appendComments(new ArrayList(comments))); + comments.clear(); + break; + } + lastWasNewLine = true; + } + } else if (node instanceof ConfigNodeComplexValue) { + result = parseValue((ConfigNodeComplexValue)node, comments); + lastWasNewLine = false; + } + } + return result; + } + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigReference.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigReference.java new file mode 100644 index 0000000..762264c --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigReference.java @@ -0,0 +1,161 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.Collection; +import java.util.Collections; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigRenderOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +/** + * ConfigReference replaces ConfigReference (the older class kept for back + * compat) and represents the ${} substitution syntax. It can resolve to any + * kind of value. + */ +final class ConfigReference extends AbstractConfigValue implements Unmergeable { + + final private SubstitutionExpression expr; + // the length of any prefixes added with relativized() + final private int prefixLength; + + ConfigReference(ConfigOrigin origin, SubstitutionExpression expr) { + this(origin, expr, 0); + } + + private ConfigReference(ConfigOrigin origin, SubstitutionExpression expr, int prefixLength) { + super(origin); + this.expr = expr; + this.prefixLength = prefixLength; + } + + private ConfigException.NotResolved notResolved() { + return new ConfigException.NotResolved( + "need to Config#resolve(), see the API docs for Config#resolve(); substitution not resolved: " + + this); + } + + @Override + public ConfigValueType valueType() { + throw notResolved(); + } + + @Override + public Object unwrapped() { + throw notResolved(); + } + + @Override + protected ConfigReference newCopy(ConfigOrigin newOrigin) { + return new ConfigReference(newOrigin, expr, prefixLength); + } + + @Override + protected boolean ignoresFallbacks() { + return false; + } + + @Override + public Collection unmergedValues() { + return Collections.singleton(this); + } + + // ConfigReference should be a firewall against NotPossibleToResolve going + // further up the stack; it should convert everything to ConfigException. + // This way it's impossible for NotPossibleToResolve to "escape" since + // any failure to resolve has to start with a ConfigReference. + @Override + ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) { + ResolveContext newContext = context.addCycleMarker(this); + AbstractConfigValue v; + try { + ResolveSource.ResultWithPath resultWithPath = source.lookupSubst(newContext, expr, prefixLength); + newContext = resultWithPath.result.context; + + if (resultWithPath.result.value != null) { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(newContext.depth(), "recursively resolving " + resultWithPath + + " which was the resolution of " + expr + " against " + source); + + ResolveSource recursiveResolveSource = (new ResolveSource( + (AbstractConfigObject) resultWithPath.pathFromRoot.last(), resultWithPath.pathFromRoot)); + + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(newContext.depth(), "will recursively resolve against " + recursiveResolveSource); + + ResolveResult result = newContext.resolve(resultWithPath.result.value, + recursiveResolveSource); + v = result.value; + newContext = result.context; + } else { + ConfigValue fallback = context.options().getResolver().lookup(expr.path().render()); + v = (AbstractConfigValue) fallback; + } + } catch (NotPossibleToResolve e) { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(newContext.depth(), + "not possible to resolve " + expr + ", cycle involved: " + e.traceString()); + if (expr.optional()) + v = null; + else + throw new ConfigException.UnresolvedSubstitution(origin(), expr + + " was part of a cycle of substitutions involving " + e.traceString(), e); + } + + if (v == null && !expr.optional()) { + if (newContext.options().getAllowUnresolved()) + return ResolveResult.make(newContext.removeCycleMarker(this), this); + else + throw new ConfigException.UnresolvedSubstitution(origin(), expr.toString()); + } else { + return ResolveResult.make(newContext.removeCycleMarker(this), v); + } + } + + @Override + ResolveStatus resolveStatus() { + return ResolveStatus.UNRESOLVED; + } + + // when you graft a substitution into another object, + // you have to prefix it with the location in that object + // where you grafted it; but save prefixLength so + // system property and env variable lookups don't get + // broken. + @Override + ConfigReference relativized(Path prefix) { + SubstitutionExpression newExpr = expr.changePath(expr.path().prepend(prefix)); + return new ConfigReference(origin(), newExpr, prefixLength + prefix.length()); + } + + @Override + protected boolean canEqual(Object other) { + return other instanceof ConfigReference; + } + + @Override + public boolean equals(Object other) { + // note that "origin" is deliberately NOT part of equality + if (other instanceof ConfigReference) { + return canEqual(other) && this.expr.equals(((ConfigReference) other).expr); + } else { + return false; + } + } + + @Override + public int hashCode() { + // note that "origin" is deliberately NOT part of equality + return expr.hashCode(); + } + + @Override + protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { + sb.append(expr.toString()); + } + + SubstitutionExpression expression() { + return expr; + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigString.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigString.java new file mode 100644 index 0000000..4a7242a --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ConfigString.java @@ -0,0 +1,90 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.ObjectStreamException; +import java.io.Serializable; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigRenderOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +abstract class ConfigString extends AbstractConfigValue implements Serializable { + + private static final long serialVersionUID = 2L; + + final protected String value; + + protected ConfigString(ConfigOrigin origin, String value) { + super(origin); + this.value = value; + } + + + final static class Quoted extends ConfigString { + Quoted(ConfigOrigin origin, String value) { + super(origin, value); + } + @Override + protected Quoted newCopy(ConfigOrigin origin) { + return new Quoted(origin, value); + } + // serialization all goes through SerializedConfigValue + private Object writeReplace() throws ObjectStreamException { + return new SerializedConfigValue(this); + } + } + + // this is sort of a hack; we want to preserve whether whitespace + // was quoted until we process substitutions, so we can ignore + // unquoted whitespace when concatenating lists or objects. + // We dump this distinction when serializing and deserializing, + // but that's OK because it isn't in equals/hashCode, and we + // don't allow serializing unresolved objects which is where + // quoted-ness matters. If we later make ConfigOrigin point + // to the original token range, we could use that to implement + // wasQuoted() + final static class Unquoted extends ConfigString { + Unquoted(ConfigOrigin origin, String value) { + super(origin, value); + } + @Override + protected Unquoted newCopy(ConfigOrigin origin) { + return new Unquoted(origin, value); + } + // serialization all goes through SerializedConfigValue + private Object writeReplace() throws ObjectStreamException { + return new SerializedConfigValue(this); + } + } + + boolean wasQuoted() { + return (this instanceof Quoted); + } + + @Override + public ConfigValueType valueType() { + return ConfigValueType.STRING; + } + + @Override + public String unwrapped() { + return value; + } + + @Override + String transformToString() { + return value; + } + + @Override + protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { + String rendered; + if (options.getJson()) + rendered = ConfigImplUtil.renderJsonString(value); + else + rendered = ConfigImplUtil.renderStringUnquotedIfPossible(value); + sb.append(rendered); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Container.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Container.java new file mode 100644 index 0000000..fd9d9c4 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Container.java @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2014 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; + +/** + * An AbstractConfigValue which contains other values. Java has no way to + * express "this has to be an AbstractConfigValue also" other than making + * AbstractConfigValue an interface which would be aggravating. But we can say + * we are a ConfigValue. + */ +interface Container extends ConfigValue { + /** + * Replace a child of this value. CAUTION if replacement is null, delete the + * child, which may also delete the parent, or make the parent into a + * non-container. + */ + AbstractConfigValue replaceChild(AbstractConfigValue child, AbstractConfigValue replacement); + + /** + * Super-expensive full traversal to see if descendant is anywhere + * underneath this container. + */ + boolean hasDescendant(AbstractConfigValue descendant); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/DefaultTransformer.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/DefaultTransformer.java new file mode 100644 index 0000000..f9987bc --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/DefaultTransformer.java @@ -0,0 +1,128 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +/** + * Default automatic type transformations. + */ +final class DefaultTransformer { + + static AbstractConfigValue transform(AbstractConfigValue value, + ConfigValueType requested) { + if (value.valueType() == ConfigValueType.STRING) { + String s = (String) value.unwrapped(); + switch (requested) { + case NUMBER: + try { + Long v = Long.parseLong(s); + return new ConfigLong(value.origin(), v, s); + } catch (NumberFormatException e) { + // try Double + } + try { + Double v = Double.parseDouble(s); + return new ConfigDouble(value.origin(), v, s); + } catch (NumberFormatException e) { + // oh well. + } + break; + case NULL: + if (s.equals("null")) + return new ConfigNull(value.origin()); + break; + case BOOLEAN: + if (s.equals("true") || s.equals("yes") || s.equals("on")) { + return new ConfigBoolean(value.origin(), true); + } else if (s.equals("false") || s.equals("no") + || s.equals("off")) { + return new ConfigBoolean(value.origin(), false); + } + break; + case LIST: + // can't go STRING to LIST automatically + break; + case OBJECT: + // can't go STRING to OBJECT automatically + break; + case STRING: + // no-op STRING to STRING + break; + } + } else if (requested == ConfigValueType.STRING) { + // if we converted null to string here, then you wouldn't properly + // get a missing-value error if you tried to get a null value + // as a string. + switch (value.valueType()) { + case NUMBER: // FALL THROUGH + case BOOLEAN: + return new ConfigString.Quoted(value.origin(), + value.transformToString()); + case NULL: + // want to be sure this throws instead of returning "null" as a + // string + break; + case OBJECT: + // no OBJECT to STRING automatically + break; + case LIST: + // no LIST to STRING automatically + break; + case STRING: + // no-op STRING to STRING + break; + } + } else if (requested == ConfigValueType.LIST && value.valueType() == ConfigValueType.OBJECT) { + // attempt to convert an array-like (numeric indices) object to a + // list. This would be used with .properties syntax for example: + // -Dfoo.0=bar -Dfoo.1=baz + // To ensure we still throw type errors for objects treated + // as lists in most cases, we'll refuse to convert if the object + // does not contain any numeric keys. This means we don't allow + // empty objects here though :-/ + AbstractConfigObject o = (AbstractConfigObject) value; + Map values = new HashMap(); + for (String key : o.keySet()) { + int i; + try { + i = Integer.parseInt(key, 10); + if (i < 0) + continue; + values.put(i, o.get(key)); + } catch (NumberFormatException e) { + continue; + } + } + if (!values.isEmpty()) { + ArrayList> entryList = new ArrayList>( + values.entrySet()); + // sort by numeric index + Collections.sort(entryList, + new Comparator>() { + @Override + public int compare(Map.Entry a, + Map.Entry b) { + return Integer.compare(a.getKey(), b.getKey()); + } + }); + // drop the indices (we allow gaps in the indices, for better or + // worse) + ArrayList list = new ArrayList(); + for (Map.Entry entry : entryList) { + list.add(entry.getValue()); + } + return new SimpleConfigList(value.origin(), list); + } + } + + return value; + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/FromMapMode.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/FromMapMode.java new file mode 100644 index 0000000..d464897 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/FromMapMode.java @@ -0,0 +1,8 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +enum FromMapMode { + KEYS_ARE_PATHS, KEYS_ARE_KEYS +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/FullIncluder.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/FullIncluder.java new file mode 100644 index 0000000..51c8b67 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/FullIncluder.java @@ -0,0 +1,14 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigIncluder; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigIncluderClasspath; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigIncluderFile; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigIncluderURL; + +interface FullIncluder extends ConfigIncluder, ConfigIncluderFile, ConfigIncluderURL, + ConfigIncluderClasspath { + +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/MemoKey.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/MemoKey.java new file mode 100644 index 0000000..8593abb --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/MemoKey.java @@ -0,0 +1,44 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +/** The key used to memoize already-traversed nodes when resolving substitutions */ +final class MemoKey { + MemoKey(AbstractConfigValue value, Path restrictToChildOrNull) { + this.value = value; + this.restrictToChildOrNull = restrictToChildOrNull; + } + + final private AbstractConfigValue value; + final private Path restrictToChildOrNull; + + @Override + public final int hashCode() { + int h = System.identityHashCode(value); + if (restrictToChildOrNull != null) { + return h + 41 * (41 + restrictToChildOrNull.hashCode()); + } else { + return h; + } + } + + @Override + public final boolean equals(Object other) { + if (other instanceof MemoKey) { + MemoKey o = (MemoKey) other; + if (o.value != this.value) + return false; + else if (o.restrictToChildOrNull == this.restrictToChildOrNull) + return true; + else if (o.restrictToChildOrNull == null || this.restrictToChildOrNull == null) + return false; + else + return o.restrictToChildOrNull.equals(this.restrictToChildOrNull); + } else { + return false; + } + } + + @Override + public final String toString() { + return "MemoKey(" + value + "@" + System.identityHashCode(value) + "," + restrictToChildOrNull + ")"; + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/MergeableValue.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/MergeableValue.java new file mode 100644 index 0000000..a7e0441 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/MergeableValue.java @@ -0,0 +1,9 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigMergeable; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; + +interface MergeableValue extends ConfigMergeable { + // converts a Config to its root object and a ConfigValue to itself + ConfigValue toFallbackValue(); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/OriginType.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/OriginType.java new file mode 100644 index 0000000..c43b307 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/OriginType.java @@ -0,0 +1,9 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +// caution: ordinals used in serialization +enum OriginType { + GENERIC, + FILE, + URL, + RESOURCE +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Parseable.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Parseable.java new file mode 100644 index 0000000..4ab4a4b --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Parseable.java @@ -0,0 +1,891 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FilterReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.util.*; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigIncludeContext; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigObject; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigParseOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigParseable; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigSyntax; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; +import com.drtshock.playervaults.lib.com.typesafe.config.parser.ConfigDocument; + +/** + * Internal implementation detail, not ABI stable, do not touch. + * For use only by the {@link com.typesafe.config} package. + * The point of this class is to avoid "propagating" each + * overload on "thing which can be parsed" through multiple + * interfaces. Most interfaces can have just one overload that + * takes a Parseable. Also it's used as an abstract "resource + * handle" in the ConfigIncluder interface. + */ +public abstract class Parseable implements ConfigParseable { + private ConfigIncludeContext includeContext; + private ConfigParseOptions initialOptions; + private ConfigOrigin initialOrigin; + + /** + * Internal implementation detail, not ABI stable, do not touch. + */ + protected interface Relativizer { + ConfigParseable relativeTo(String filename); + } + + private static final ThreadLocal> parseStack = new ThreadLocal>() { + @Override + protected LinkedList initialValue() { + return new LinkedList(); + } + }; + + private static final int MAX_INCLUDE_DEPTH = 50; + + protected Parseable() { + } + + private ConfigParseOptions fixupOptions(ConfigParseOptions baseOptions) { + ConfigSyntax syntax = baseOptions.getSyntax(); + if (syntax == null) { + syntax = guessSyntax(); + } + if (syntax == null) { + syntax = ConfigSyntax.CONF; + } + ConfigParseOptions modified = baseOptions.setSyntax(syntax); + + // make sure the app-provided includer falls back to default + modified = modified.appendIncluder(ConfigImpl.defaultIncluder()); + // make sure the app-provided includer is complete + modified = modified.setIncluder(SimpleIncluder.makeFull(modified.getIncluder())); + + return modified; + } + + protected void postConstruct(ConfigParseOptions baseOptions) { + this.initialOptions = fixupOptions(baseOptions); + + this.includeContext = new SimpleIncludeContext(this); + + if (initialOptions.getOriginDescription() != null) + initialOrigin = SimpleConfigOrigin.newSimple(initialOptions.getOriginDescription()); + else + initialOrigin = createOrigin(); + } + + // the general idea is that any work should be in here, not in the + // constructor, so that exceptions are thrown from the public parse() + // function and not from the creation of the Parseable. + // Essentially this is a lazy field. The parser should close the + // reader when it's done with it. + // ALSO, IMPORTANT: if the file or URL is not found, this must throw. + // to support the "allow missing" feature. + protected abstract Reader reader() throws IOException; + + protected Reader reader(ConfigParseOptions options) throws IOException { + return reader(); + } + + protected static void trace(String message) { + if (ConfigImpl.traceLoadsEnabled()) { + ConfigImpl.trace(message); + } + } + + ConfigSyntax guessSyntax() { + return null; + } + + ConfigSyntax contentType() { + return null; + } + + ConfigParseable relativeTo(String filename) { + // fall back to classpath; we treat the "filename" as absolute + // (don't add a package name in front), + // if it starts with "/" then remove the "/", for consistency + // with ParseableResources.relativeTo + String resource = filename; + if (filename.startsWith("/")) + resource = filename.substring(1); + return newResources(resource, options().setOriginDescription(null)); + } + + ConfigIncludeContext includeContext() { + return includeContext; + } + + static AbstractConfigObject forceParsedToObject(ConfigValue value) { + if (value instanceof AbstractConfigObject) { + return (AbstractConfigObject) value; + } else { + throw new ConfigException.WrongType(value.origin(), "", "object at file root", value + .valueType().name()); + } + } + + @Override + public ConfigObject parse(ConfigParseOptions baseOptions) { + + LinkedList stack = parseStack.get(); + if (stack.size() >= MAX_INCLUDE_DEPTH) { + throw new ConfigException.Parse(initialOrigin, "include statements nested more than " + + MAX_INCLUDE_DEPTH + + " times, you probably have a cycle in your includes. Trace: " + stack); + } + + stack.addFirst(this); + try { + return forceParsedToObject(parseValue(baseOptions)); + } finally { + stack.removeFirst(); + if (stack.isEmpty()) { + parseStack.remove(); + } + } + } + + final AbstractConfigValue parseValue(ConfigParseOptions baseOptions) { + // note that we are NOT using our "initialOptions", + // but using the ones from the passed-in options. The idea is that + // callers can get our original options and then parse with different + // ones if they want. + ConfigParseOptions options = fixupOptions(baseOptions); + + // passed-in options can override origin + ConfigOrigin origin; + if (options.getOriginDescription() != null) + origin = SimpleConfigOrigin.newSimple(options.getOriginDescription()); + else + origin = initialOrigin; + return parseValue(origin, options); + } + + final private AbstractConfigValue parseValue(ConfigOrigin origin, + ConfigParseOptions finalOptions) { + try { + return rawParseValue(origin, finalOptions); + } catch (IOException e) { + if (finalOptions.getAllowMissing()) { + trace(e.getMessage() + ". Allowing Missing File, this can be turned off by setting" + + " ConfigParseOptions.allowMissing = false"); + return SimpleConfigObject.emptyMissing(origin); + } else { + trace("exception loading " + origin.description() + ": " + e.getClass().getName() + + ": " + e.getMessage()); + throw new ConfigException.IO(origin, + e.getClass().getName() + ": " + e.getMessage(), e); + } + } + } + + final ConfigDocument parseDocument(ConfigParseOptions baseOptions) { + // note that we are NOT using our "initialOptions", + // but using the ones from the passed-in options. The idea is that + // callers can get our original options and then parse with different + // ones if they want. + ConfigParseOptions options = fixupOptions(baseOptions); + + // passed-in options can override origin + ConfigOrigin origin; + if (options.getOriginDescription() != null) + origin = SimpleConfigOrigin.newSimple(options.getOriginDescription()); + else + origin = initialOrigin; + return parseDocument(origin, options); + } + + final private ConfigDocument parseDocument(ConfigOrigin origin, + ConfigParseOptions finalOptions) { + try { + return rawParseDocument(origin, finalOptions); + } catch (IOException e) { + if (finalOptions.getAllowMissing()) { + ArrayList children = new ArrayList(); + children.add(new ConfigNodeObject(new ArrayList())); + return new SimpleConfigDocument(new ConfigNodeRoot(children, origin), finalOptions); + } else { + trace("exception loading " + origin.description() + ": " + e.getClass().getName() + + ": " + e.getMessage()); + throw new ConfigException.IO(origin, + e.getClass().getName() + ": " + e.getMessage(), e); + } + } + } + + // this is parseValue without post-processing the IOException or handling + // options.getAllowMissing() + protected AbstractConfigValue rawParseValue(ConfigOrigin origin, ConfigParseOptions finalOptions) + throws IOException { + Reader reader = reader(finalOptions); + + // after reader() we will have loaded the Content-Type. + ConfigSyntax contentType = contentType(); + + ConfigParseOptions optionsWithContentType; + if (contentType != null) { + if (ConfigImpl.traceLoadsEnabled() && finalOptions.getSyntax() != null) + trace("Overriding syntax " + finalOptions.getSyntax() + + " with Content-Type which specified " + contentType); + + optionsWithContentType = finalOptions.setSyntax(contentType); + } else { + optionsWithContentType = finalOptions; + } + + try { + return rawParseValue(reader, origin, optionsWithContentType); + } finally { + reader.close(); + } + } + + private AbstractConfigValue rawParseValue(Reader reader, ConfigOrigin origin, + ConfigParseOptions finalOptions) throws IOException { + if (finalOptions.getSyntax() == ConfigSyntax.PROPERTIES) { + return PropertiesParser.parse(reader, origin); + } else { + Iterator tokens = Tokenizer.tokenize(origin, reader, finalOptions.getSyntax()); + ConfigNodeRoot document = ConfigDocumentParser.parse(tokens, origin, finalOptions); + return ConfigParser.parse(document, origin, finalOptions, includeContext()); + } + } + + // this is parseDocument without post-processing the IOException or handling + // options.getAllowMissing() + protected ConfigDocument rawParseDocument(ConfigOrigin origin, ConfigParseOptions finalOptions) + throws IOException { + Reader reader = reader(finalOptions); + + // after reader() we will have loaded the Content-Type. + ConfigSyntax contentType = contentType(); + + ConfigParseOptions optionsWithContentType; + if (contentType != null) { + if (ConfigImpl.traceLoadsEnabled() && finalOptions.getSyntax() != null) + trace("Overriding syntax " + finalOptions.getSyntax() + + " with Content-Type which specified " + contentType); + + optionsWithContentType = finalOptions.setSyntax(contentType); + } else { + optionsWithContentType = finalOptions; + } + + try { + return rawParseDocument(reader, origin, optionsWithContentType); + } finally { + reader.close(); + } + } + + private ConfigDocument rawParseDocument(Reader reader, ConfigOrigin origin, + ConfigParseOptions finalOptions) throws IOException { + Iterator tokens = Tokenizer.tokenize(origin, reader, finalOptions.getSyntax()); + return new SimpleConfigDocument(ConfigDocumentParser.parse(tokens, origin, finalOptions), finalOptions); + } + + public ConfigObject parse() { + return forceParsedToObject(parseValue(options())); + } + + public ConfigDocument parseConfigDocument() { + return parseDocument(options()); + } + + AbstractConfigValue parseValue() { + return parseValue(options()); + } + + @Override + public final ConfigOrigin origin() { + return initialOrigin; + } + + protected abstract ConfigOrigin createOrigin(); + + @Override + public ConfigParseOptions options() { + return initialOptions; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + private static ConfigSyntax syntaxFromExtension(String name) { + if (name.endsWith(".json")) + return ConfigSyntax.JSON; + else if (name.endsWith(".conf")) + return ConfigSyntax.CONF; + else if (name.endsWith(".properties")) + return ConfigSyntax.PROPERTIES; + else + return null; + } + + private static Reader readerFromStream(InputStream input) { + return readerFromStream(input, "UTF-8"); + } + + private static Reader readerFromStream(InputStream input, String encoding) { + try { + // well, this is messed up. If we aren't going to close + // the passed-in InputStream then we have no way to + // close these readers. So maybe we should not have an + // InputStream version, only a Reader version. + Reader reader = new InputStreamReader(input, encoding); + return new BufferedReader(reader); + } catch (UnsupportedEncodingException e) { + throw new ConfigException.BugOrBroken("Java runtime does not support UTF-8", e); + } + } + + private static Reader doNotClose(Reader input) { + return new FilterReader(input) { + @Override + public void close() { + // NOTHING. + } + }; + } + + static URL relativeTo(URL url, String filename) { + // I'm guessing this completely fails on Windows, help wanted + if (new File(filename).isAbsolute()) + return null; + + try { + URI siblingURI = url.toURI(); + URI relative = new URI(filename); + + // this seems wrong, but it's documented that the last + // element of the path in siblingURI gets stripped out, + // so to get something in the same directory as + // siblingURI we just call resolve(). + URL resolved = siblingURI.resolve(relative).toURL(); + + return resolved; + } catch (MalformedURLException e) { + return null; + } catch (URISyntaxException e) { + return null; + } catch (IllegalArgumentException e) { + return null; + } + } + + static File relativeTo(File file, String filename) { + File child = new File(filename); + + if (child.isAbsolute()) + return null; + + File parent = file.getParentFile(); + + if (parent == null) + return null; + else + return new File(parent, filename); + } + + // this is a parseable that doesn't exist and just throws when you try to + // parse it + private final static class ParseableNotFound extends Parseable { + final private String what; + final private String message; + + ParseableNotFound(String what, String message, ConfigParseOptions options) { + this.what = what; + this.message = message; + postConstruct(options); + } + + @Override + protected Reader reader() throws IOException { + throw new FileNotFoundException(message); + } + + @Override + protected ConfigOrigin createOrigin() { + return SimpleConfigOrigin.newSimple(what); + } + } + + public static Parseable newNotFound(String whatNotFound, String message, + ConfigParseOptions options) { + return new ParseableNotFound(whatNotFound, message, options); + } + + private final static class ParseableReader extends Parseable { + final private Reader reader; + + ParseableReader(Reader reader, ConfigParseOptions options) { + this.reader = reader; + postConstruct(options); + } + + @Override + protected Reader reader() { + if (ConfigImpl.traceLoadsEnabled()) + trace("Loading config from reader " + reader); + return reader; + } + + @Override + protected ConfigOrigin createOrigin() { + return SimpleConfigOrigin.newSimple("Reader"); + } + } + + // note that we will never close this reader; you have to do it when parsing + // is complete. + public static Parseable newReader(Reader reader, ConfigParseOptions options) { + + return new ParseableReader(doNotClose(reader), options); + } + + private final static class ParseableString extends Parseable { + final private String input; + + ParseableString(String input, ConfigParseOptions options) { + this.input = input; + postConstruct(options); + } + + @Override + protected Reader reader() { + if (ConfigImpl.traceLoadsEnabled()) + trace("Loading config from a String " + input); + return new StringReader(input); + } + + @Override + protected ConfigOrigin createOrigin() { + return SimpleConfigOrigin.newSimple("String"); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + input + ")"; + } + } + + public static Parseable newString(String input, ConfigParseOptions options) { + return new ParseableString(input, options); + } + + private static final String jsonContentType = "application/json"; + private static final String propertiesContentType = "text/x-java-properties"; + private static final String hoconContentType = "application/hocon"; + + private static class ParseableURL extends Parseable { + final protected URL input; + private String contentType = null; + + protected ParseableURL(URL input) { + this.input = input; + // does not postConstruct (subclass does it) + } + + ParseableURL(URL input, ConfigParseOptions options) { + this(input); + postConstruct(options); + } + + @Override + protected Reader reader() throws IOException { + throw new ConfigException.BugOrBroken("reader() without options should not be called on ParseableURL"); + } + + private static String acceptContentType(ConfigParseOptions options) { + if (options.getSyntax() == null) + return null; + + switch (options.getSyntax()) { + case JSON: + return jsonContentType; + case CONF: + return hoconContentType; + case PROPERTIES: + return propertiesContentType; + } + + // not sure this is reachable but javac thinks it is + return null; + } + + @Override + protected Reader reader(ConfigParseOptions options) throws IOException { + try { + if (ConfigImpl.traceLoadsEnabled()) + trace("Loading config from a URL: " + input.toExternalForm()); + URLConnection connection = input.openConnection(); + + // allow server to serve multiple types from one URL + String acceptContent = acceptContentType(options); + if (acceptContent != null) { + connection.setRequestProperty("Accept", acceptContent); + } + + connection.connect(); + + // save content type for later + contentType = connection.getContentType(); + if (contentType != null) { + if (ConfigImpl.traceLoadsEnabled()) + trace("URL sets Content-Type: '" + contentType + "'"); + contentType = contentType.trim(); + int semi = contentType.indexOf(';'); + if (semi >= 0) + contentType = contentType.substring(0, semi); + } + + InputStream stream = connection.getInputStream(); + + return readerFromStream(stream); + } catch (FileNotFoundException fnf) { + // If the resource is not found (HTTP response + // code 404 or something alike), then it's fine to + // treat it according to the allowMissing setting + // and "include" spec. But if we have something + // like HTTP 503 it seems to be better to fail + // early, because this may be a sign of broken + // environment. Java throws FileNotFoundException + // if it sees 404 or 410. + throw fnf; + } catch (IOException e) { + throw new ConfigException.BugOrBroken("Cannot load config from URL: " + input.toExternalForm(), e); + } + } + + @Override + ConfigSyntax guessSyntax() { + return syntaxFromExtension(input.getPath()); + } + + @Override + ConfigSyntax contentType() { + if (contentType != null) { + if (contentType.equals(jsonContentType)) + return ConfigSyntax.JSON; + else if (contentType.equals(propertiesContentType)) + return ConfigSyntax.PROPERTIES; + else if (contentType.equals(hoconContentType)) + return ConfigSyntax.CONF; + else { + if (ConfigImpl.traceLoadsEnabled()) + trace("'" + contentType + "' isn't a known content type"); + return null; + } + } else { + return null; + } + } + + @Override + ConfigParseable relativeTo(String filename) { + URL url = relativeTo(input, filename); + if (url == null) + return null; + return newURL(url, options().setOriginDescription(null)); + } + + @Override + protected ConfigOrigin createOrigin() { + return SimpleConfigOrigin.newURL(input); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + input.toExternalForm() + ")"; + } + } + + public static Parseable newURL(URL input, ConfigParseOptions options) { + // we want file: URLs and files to always behave the same, so switch + // to a file if it's a file: URL + if (input.getProtocol().equals("file")) { + return newFile(ConfigImplUtil.urlToFile(input), options); + } else { + return new ParseableURL(input, options); + } + } + + private final static class ParseableFile extends Parseable { + final private File input; + + ParseableFile(File input, ConfigParseOptions options) { + this.input = input; + postConstruct(options); + } + + @Override + protected Reader reader() throws IOException { + if (ConfigImpl.traceLoadsEnabled()) + trace("Loading config from a file: " + input); + InputStream stream = new FileInputStream(input); + return readerFromStream(stream); + } + + @Override + ConfigSyntax guessSyntax() { + return syntaxFromExtension(input.getName()); + } + + @Override + ConfigParseable relativeTo(String filename) { + File sibling; + if ((new File(filename)).isAbsolute()) { + sibling = new File(filename); + } else { + // this may return null + sibling = relativeTo(input, filename); + } + if (sibling == null) + return null; + if (sibling.exists()) { + trace(sibling + " exists, so loading it as a file"); + return newFile(sibling, options().setOriginDescription(null)); + } else { + trace(sibling + " does not exist, so trying it as a classpath resource"); + return super.relativeTo(filename); + } + } + + @Override + protected ConfigOrigin createOrigin() { + return SimpleConfigOrigin.newFile(input.getPath()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + input.getPath() + ")"; + } + } + + public static Parseable newFile(File input, ConfigParseOptions options) { + return new ParseableFile(input, options); + } + + + private final static class ParseableResourceURL extends ParseableURL { + + private final Relativizer relativizer; + private final String resource; + + ParseableResourceURL(URL input, ConfigParseOptions options, String resource, Relativizer relativizer) { + super(input); + this.relativizer = relativizer; + this.resource = resource; + postConstruct(options); + } + + @Override + protected ConfigOrigin createOrigin() { + return SimpleConfigOrigin.newResource(resource, input); + } + + @Override + ConfigParseable relativeTo(String filename) { + return relativizer.relativeTo(filename); + } + } + + private static Parseable newResourceURL(URL input, ConfigParseOptions options, String resource, Relativizer relativizer) { + return new ParseableResourceURL(input, options, resource, relativizer); + } + + private final static class ParseableResources extends Parseable implements Relativizer { + final private String resource; + + ParseableResources(String resource, ConfigParseOptions options) { + this.resource = resource; + postConstruct(options); + } + + @Override + protected Reader reader() throws IOException { + throw new ConfigException.BugOrBroken("reader() should not be called on resources"); + } + + @Override + protected AbstractConfigObject rawParseValue(ConfigOrigin origin, + ConfigParseOptions finalOptions) throws IOException { + ClassLoader loader = finalOptions.getClassLoader(); + if (loader == null) + throw new ConfigException.BugOrBroken( + "null class loader; pass in a class loader or use Thread.currentThread().setContextClassLoader()"); + Enumeration e = loader.getResources(resource); + if (!e.hasMoreElements()) { + if (ConfigImpl.traceLoadsEnabled()) + trace("Loading config from class loader " + loader + + " but there were no resources called " + resource); + throw new IOException("resource not found on classpath: " + resource); + } + AbstractConfigObject merged = SimpleConfigObject.empty(origin); + while (e.hasMoreElements()) { + URL url = e.nextElement(); + + if (ConfigImpl.traceLoadsEnabled()) + trace("Loading config from resource '" + resource + "' URL " + url.toExternalForm() + " from class loader " + + loader); + + Parseable element = newResourceURL(url, finalOptions, resource, this); + + AbstractConfigValue v = element.parseValue(); + + merged = merged.withFallback(v); + } + + return merged; + } + + @Override + ConfigSyntax guessSyntax() { + return syntaxFromExtension(resource); + } + + static String parent(String resource) { + // the "resource" is not supposed to begin with a "/" + // because it's supposed to be the raw resource + // (ClassLoader#getResource), not the + // resource "syntax" (Class#getResource) + int i = resource.lastIndexOf('/'); + if (i < 0) { + return null; + } else { + return resource.substring(0, i); + } + } + + @Override + public ConfigParseable relativeTo(String sibling) { + if (sibling.startsWith("/")) { + // if it starts with "/" then don't make it relative to + // the including resource + return newResources(sibling.substring(1), options().setOriginDescription(null)); + } else { + // here we want to build a new resource name and let + // the class loader have it, rather than getting the + // url with getResource() and relativizing to that url. + // This is needed in case the class loader is going to + // search a classpath. + String parent = parent(resource); + if (parent == null) + return newResources(sibling, options().setOriginDescription(null)); + else + return newResources(parent + "/" + sibling, options() + .setOriginDescription(null)); + } + } + + @Override + protected ConfigOrigin createOrigin() { + return SimpleConfigOrigin.newResource(resource); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + resource + ")"; + } + } + + public static Parseable newResources(Class klass, String resource, ConfigParseOptions options) { + return newResources(convertResourceName(klass, resource), + options.setClassLoader(klass.getClassLoader())); + } + + // this function is supposed to emulate the difference + // between Class.getResource and ClassLoader.getResource + // (unfortunately there doesn't seem to be public API for it). + // We're using it because the Class API is more limited, + // for example it lacks getResources(). So we want to be able to + // use ClassLoader directly. + private static String convertResourceName(Class klass, String resource) { + if (resource.startsWith("/")) { + // "absolute" resource, chop the slash + return resource.substring(1); + } else { + String className = klass.getName(); + int i = className.lastIndexOf('.'); + if (i < 0) { + // no package + return resource; + } else { + // need to be relative to the package + String packageName = className.substring(0, i); + String packagePath = packageName.replace('.', '/'); + return packagePath + "/" + resource; + } + } + } + + public static Parseable newResources(String resource, ConfigParseOptions options) { + if (options.getClassLoader() == null) + throw new ConfigException.BugOrBroken( + "null class loader; pass in a class loader or use Thread.currentThread().setContextClassLoader()"); + return new ParseableResources(resource, options); + } + + private final static class ParseableProperties extends Parseable { + final private Properties props; + + ParseableProperties(Properties props, ConfigParseOptions options) { + this.props = props; + postConstruct(options); + } + + @Override + protected Reader reader() throws IOException { + throw new ConfigException.BugOrBroken("reader() should not be called on props"); + } + + @Override + protected AbstractConfigObject rawParseValue(ConfigOrigin origin, + ConfigParseOptions finalOptions) { + if (ConfigImpl.traceLoadsEnabled()) + trace("Loading config from properties " + props); + return PropertiesParser.fromProperties(origin, props); + } + + @Override + ConfigSyntax guessSyntax() { + return ConfigSyntax.PROPERTIES; + } + + @Override + protected ConfigOrigin createOrigin() { + return SimpleConfigOrigin.newSimple("properties"); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + props.size() + " props)"; + } + } + + public static Parseable newProperties(Properties properties, ConfigParseOptions options) { + return new ParseableProperties(properties, options); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Path.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Path.java new file mode 100644 index 0000000..d833f09 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Path.java @@ -0,0 +1,232 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.*; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; + +final class Path { + + final private String first; + final private Path remainder; + + Path(String first, Path remainder) { + this.first = first; + this.remainder = remainder; + } + + Path(String... elements) { + if (elements.length == 0) + throw new ConfigException.BugOrBroken("empty path"); + this.first = elements[0]; + if (elements.length > 1) { + PathBuilder pb = new PathBuilder(); + for (int i = 1; i < elements.length; ++i) { + pb.appendKey(elements[i]); + } + this.remainder = pb.result(); + } else { + this.remainder = null; + } + } + + // append all the paths in the list together into one path + Path(List pathsToConcat) { + this(pathsToConcat.iterator()); + } + + // append all the paths in the iterator together into one path + Path(Iterator i) { + if (!i.hasNext()) + throw new ConfigException.BugOrBroken("empty path"); + + Path firstPath = i.next(); + this.first = firstPath.first; + + PathBuilder pb = new PathBuilder(); + if (firstPath.remainder != null) { + pb.appendPath(firstPath.remainder); + } + while (i.hasNext()) { + pb.appendPath(i.next()); + } + this.remainder = pb.result(); + } + + String first() { + return first; + } + + /** + * + * @return path minus the first element or null if no more elements + */ + Path remainder() { + return remainder; + } + + /** + * + * @return path minus the last element or null if we have just one element + */ + Path parent() { + if (remainder == null) + return null; + + PathBuilder pb = new PathBuilder(); + Path p = this; + while (p.remainder != null) { + pb.appendKey(p.first); + p = p.remainder; + } + return pb.result(); + } + + /** + * + * @return last element in the path + */ + String last() { + Path p = this; + while (p.remainder != null) { + p = p.remainder; + } + return p.first; + } + + Path prepend(Path toPrepend) { + PathBuilder pb = new PathBuilder(); + pb.appendPath(toPrepend); + pb.appendPath(this); + return pb.result(); + } + + int length() { + int count = 1; + Path p = remainder; + while (p != null) { + count += 1; + p = p.remainder; + } + return count; + } + + Path subPath(int removeFromFront) { + int count = removeFromFront; + Path p = this; + while (p != null && count > 0) { + count -= 1; + p = p.remainder; + } + return p; + } + + Path subPath(int firstIndex, int lastIndex) { + if (lastIndex < firstIndex) + throw new ConfigException.BugOrBroken("bad call to subPath"); + + Path from = subPath(firstIndex); + PathBuilder pb = new PathBuilder(); + int count = lastIndex - firstIndex; + while (count > 0) { + count -= 1; + pb.appendKey(from.first()); + from = from.remainder(); + if (from == null) + throw new ConfigException.BugOrBroken("subPath lastIndex out of range " + lastIndex); + } + return pb.result(); + } + + boolean startsWith(Path other) { + Path myRemainder = this; + Path otherRemainder = other; + if (otherRemainder.length() <= myRemainder.length()) { + while(otherRemainder != null) { + if (!otherRemainder.first().equals(myRemainder.first())) + return false; + myRemainder = myRemainder.remainder(); + otherRemainder = otherRemainder.remainder(); + } + return true; + } + return false; + } + + @Override + public boolean equals(Object other) { + if (other instanceof Path) { + Path that = (Path) other; + return this.first.equals(that.first) + && ConfigImplUtil.equalsHandlingNull(this.remainder, + that.remainder); + } else { + return false; + } + } + + @Override + public int hashCode() { + return 41 * (41 + first.hashCode()) + + (remainder == null ? 0 : remainder.hashCode()); + } + + // this doesn't have a very precise meaning, just to reduce + // noise from quotes in the rendered path for average cases + static boolean hasFunkyChars(String s) { + int length = s.length(); + + if (length == 0) + return false; + + for (int i = 0; i < length; ++i) { + char c = s.charAt(i); + + if (Character.isLetterOrDigit(c) || c == '-' || c == '_') + continue; + else + return true; + } + return false; + } + + private void appendToStringBuilder(StringBuilder sb) { + if (hasFunkyChars(first) || first.isEmpty()) + sb.append(ConfigImplUtil.renderJsonString(first)); + else + sb.append(first); + if (remainder != null) { + sb.append("."); + remainder.appendToStringBuilder(sb); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Path("); + appendToStringBuilder(sb); + sb.append(")"); + return sb.toString(); + } + + /** + * toString() is a debugging-oriented version while this is an + * error-message-oriented human-readable one. + */ + String render() { + StringBuilder sb = new StringBuilder(); + appendToStringBuilder(sb); + return sb.toString(); + } + + static Path newKey(String key) { + return new Path(key, null); + } + + static Path newPath(String path) { + return PathParser.parsePath(path); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/PathBuilder.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/PathBuilder.java new file mode 100644 index 0000000..7a33f8b --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/PathBuilder.java @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.Stack; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; + +final class PathBuilder { + // the keys are kept "backward" (top of stack is end of path) + final private Stack keys; + private Path result; + + PathBuilder() { + keys = new Stack(); + } + + private void checkCanAppend() { + if (result != null) + throw new ConfigException.BugOrBroken( + "Adding to PathBuilder after getting result"); + } + + void appendKey(String key) { + checkCanAppend(); + + keys.push(key); + } + + void appendPath(Path path) { + checkCanAppend(); + + String first = path.first(); + Path remainder = path.remainder(); + while (true) { + keys.push(first); + if (remainder != null) { + first = remainder.first(); + remainder = remainder.remainder(); + } else { + break; + } + } + } + + Path result() { + // note: if keys is empty, we want to return null, which is a valid + // empty path + if (result == null) { + Path remainder = null; + while (!keys.isEmpty()) { + String key = keys.pop(); + remainder = new Path(key, remainder); + } + result = remainder; + } + return result; + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/PathParser.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/PathParser.java new file mode 100644 index 0000000..51915ba --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/PathParser.java @@ -0,0 +1,281 @@ +/** + * Copyright (C) 2015 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigSyntax; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +import java.io.StringReader; +import java.util.*; + +final class PathParser { + static class Element { + StringBuilder sb; + // an element can be empty if it has a quoted empty string "" in it + boolean canBeEmpty; + + Element(String initial, boolean canBeEmpty) { + this.canBeEmpty = canBeEmpty; + this.sb = new StringBuilder(initial); + } + + @Override + public String toString() { + return "Element(" + sb.toString() + "," + canBeEmpty + ")"; + } + } + + static ConfigOrigin apiOrigin = SimpleConfigOrigin.newSimple("path parameter"); + + static ConfigNodePath parsePathNode(String path) { + return parsePathNode(path, ConfigSyntax.CONF); + } + + static ConfigNodePath parsePathNode(String path, ConfigSyntax flavor) { + StringReader reader = new StringReader(path); + + try { + Iterator tokens = Tokenizer.tokenize(apiOrigin, reader, + flavor); + tokens.next(); // drop START + return parsePathNodeExpression(tokens, apiOrigin, path, flavor); + } finally { + reader.close(); + } + } + + static Path parsePath(String path) { + Path speculated = speculativeFastParsePath(path); + if (speculated != null) + return speculated; + + StringReader reader = new StringReader(path); + + try { + Iterator tokens = Tokenizer.tokenize(apiOrigin, reader, + ConfigSyntax.CONF); + tokens.next(); // drop START + return parsePathExpression(tokens, apiOrigin, path); + } finally { + reader.close(); + } + } + + protected static Path parsePathExpression(Iterator expression, + ConfigOrigin origin) { + return parsePathExpression(expression, origin, null, null, ConfigSyntax.CONF); + } + + protected static Path parsePathExpression(Iterator expression, + ConfigOrigin origin, String originalText) { + return parsePathExpression(expression, origin, originalText, null, ConfigSyntax.CONF); + } + + protected static ConfigNodePath parsePathNodeExpression(Iterator expression, + ConfigOrigin origin) { + return parsePathNodeExpression(expression, origin, null, ConfigSyntax.CONF); + } + + protected static ConfigNodePath parsePathNodeExpression(Iterator expression, + ConfigOrigin origin, String originalText, ConfigSyntax flavor) { + ArrayList pathTokens = new ArrayList(); + Path path = parsePathExpression(expression, origin, originalText, pathTokens, flavor); + return new ConfigNodePath(path, pathTokens); + } + + // originalText may be null if not available + protected static Path parsePathExpression(Iterator expression, + ConfigOrigin origin, String originalText, + ArrayList pathTokens, + ConfigSyntax flavor) { + // each builder in "buf" is an element in the path. + List buf = new ArrayList(); + buf.add(new Element("", false)); + + if (!expression.hasNext()) { + throw new ConfigException.BadPath(origin, originalText, + "Expecting a field name or path here, but got nothing"); + } + + while (expression.hasNext()) { + Token t = expression.next(); + + if (pathTokens != null) + pathTokens.add(t); + + // Ignore all IgnoredWhitespace tokens + if (Tokens.isIgnoredWhitespace(t)) + continue; + + if (Tokens.isValueWithType(t, ConfigValueType.STRING)) { + AbstractConfigValue v = Tokens.getValue(t); + // this is a quoted string; so any periods + // in here don't count as path separators + String s = v.transformToString(); + + addPathText(buf, true, s); + } else if (t == Tokens.END) { + // ignore this; when parsing a file, it should not happen + // since we're parsing a token list rather than the main + // token iterator, and when parsing a path expression from the + // API, it's expected to have an END. + } else { + // any periods outside of a quoted string count as + // separators + String text; + if (Tokens.isValue(t)) { + // appending a number here may add + // a period, but we _do_ count those as path + // separators, because we basically want + // "foo 3.0bar" to parse as a string even + // though there's a number in it. The fact that + // we tokenize non-string values is largely an + // implementation detail. + AbstractConfigValue v = Tokens.getValue(t); + + // We need to split the tokens on a . so that we can get sub-paths but still preserve + // the original path text when doing an insertion + if (pathTokens != null) { + pathTokens.remove(pathTokens.size() - 1); + pathTokens.addAll(splitTokenOnPeriod(t, flavor)); + } + text = v.transformToString(); + } else if (Tokens.isUnquotedText(t)) { + // We need to split the tokens on a . so that we can get sub-paths but still preserve + // the original path text when doing an insertion on ConfigNodeObjects + if (pathTokens != null) { + pathTokens.remove(pathTokens.size() - 1); + pathTokens.addAll(splitTokenOnPeriod(t, flavor)); + } + text = Tokens.getUnquotedText(t); + } else { + throw new ConfigException.BadPath( + origin, + originalText, + "Token not allowed in path expression: " + + t + + " (you can double-quote this token if you really want it here)"); + } + + addPathText(buf, false, text); + } + } + + PathBuilder pb = new PathBuilder(); + for (Element e : buf) { + if (e.sb.length() == 0 && !e.canBeEmpty) { + throw new ConfigException.BadPath( + origin, + originalText, + "path has a leading, trailing, or two adjacent period '.' (use quoted \"\" empty string if you want an empty element)"); + } else { + pb.appendKey(e.sb.toString()); + } + } + + return pb.result(); + } + + private static Collection splitTokenOnPeriod(Token t, ConfigSyntax flavor) { + String tokenText = t.tokenText(); + if (tokenText.equals(".")) { + return Collections.singletonList(t); + } + String[] splitToken = tokenText.split("\\."); + ArrayList splitTokens = new ArrayList(); + for (String s : splitToken) { + if (flavor == ConfigSyntax.CONF) + splitTokens.add(Tokens.newUnquotedText(t.origin(), s)); + else + splitTokens.add(Tokens.newString(t.origin(), s, "\"" + s + "\"")); + splitTokens.add(Tokens.newUnquotedText(t.origin(), ".")); + } + if (tokenText.charAt(tokenText.length() - 1) != '.') + splitTokens.remove(splitTokens.size() - 1); + return splitTokens; + } + + private static void addPathText(List buf, boolean wasQuoted, + String newText) { + int i = wasQuoted ? -1 : newText.indexOf('.'); + Element current = buf.get(buf.size() - 1); + if (i < 0) { + // add to current path element + current.sb.append(newText); + // any empty quoted string means this element can + // now be empty. + if (wasQuoted && current.sb.length() == 0) + current.canBeEmpty = true; + } else { + // "buf" plus up to the period is an element + current.sb.append(newText.substring(0, i)); + // then start a new element + buf.add(new Element("", false)); + // recurse to consume remainder of newText + addPathText(buf, false, newText.substring(i + 1)); + } + } + + // the idea is to see if the string has any chars or features + // that might require the full parser to deal with. + private static boolean looksUnsafeForFastParser(String s) { + boolean lastWasDot = true; // start of path is also a "dot" + int len = s.length(); + if (s.isEmpty()) + return true; + if (s.charAt(0) == '.') + return true; + if (s.charAt(len - 1) == '.') + return true; + + for (int i = 0; i < len; ++i) { + char c = s.charAt(i); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') { + lastWasDot = false; + continue; + } else if (c == '.') { + if (lastWasDot) + return true; // ".." means we need to throw an error + lastWasDot = true; + } else if (c == '-') { + if (lastWasDot) + return true; + continue; + } else { + return true; + } + } + + if (lastWasDot) + return true; + + return false; + } + + private static Path fastPathBuild(Path tail, String s, int end) { + // lastIndexOf takes last index it should look at, end - 1 not end + int splitAt = s.lastIndexOf('.', end - 1); + ArrayList tokens = new ArrayList(); + tokens.add(Tokens.newUnquotedText(null, s)); + // this works even if splitAt is -1; then we start the substring at 0 + Path withOneMoreElement = new Path(s.substring(splitAt + 1, end), tail); + if (splitAt < 0) { + return withOneMoreElement; + } else { + return fastPathBuild(withOneMoreElement, s, splitAt); + } + } + + // do something much faster than the full parser if + // we just have something like "foo" or "foo.bar" + private static Path speculativeFastParsePath(String path) { + String s = ConfigImplUtil.unicodeTrim(path); + if (looksUnsafeForFastParser(s)) + return null; + + return fastPathBuild(null, s, s.length()); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/PropertiesParser.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/PropertiesParser.java new file mode 100644 index 0000000..50e31a3 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/PropertiesParser.java @@ -0,0 +1,210 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; + +final class PropertiesParser { + static AbstractConfigObject parse(Reader reader, + ConfigOrigin origin) throws IOException { + Properties props = new Properties(); + props.load(reader); + return fromProperties(origin, props); + } + + static String lastElement(String path) { + int i = path.lastIndexOf('.'); + if (i < 0) + return path; + else + return path.substring(i + 1); + } + + static String exceptLastElement(String path) { + int i = path.lastIndexOf('.'); + if (i < 0) + return null; + else + return path.substring(0, i); + } + + static Path pathFromPropertyKey(String key) { + String last = lastElement(key); + String exceptLast = exceptLastElement(key); + Path path = new Path(last, null); + while (exceptLast != null) { + last = lastElement(exceptLast); + exceptLast = exceptLastElement(exceptLast); + path = new Path(last, path); + } + return path; + } + + static AbstractConfigObject fromProperties(ConfigOrigin origin, + Properties props) { + return fromEntrySet(origin, props.entrySet()); + } + + private static AbstractConfigObject fromEntrySet(ConfigOrigin origin, Set> entries) { + final Map pathMap = getPathMap(entries); + return fromPathMap(origin, pathMap, true /* from properties */); + } + + private static Map getPathMap(Set> entries) { + Map pathMap = new HashMap(); + for (Map.Entry entry : entries) { + Object key = entry.getKey(); + if (key instanceof String) { + Path path = pathFromPropertyKey((String) key); + pathMap.put(path, entry.getValue()); + } + } + return pathMap; + } + + static AbstractConfigObject fromStringMap(ConfigOrigin origin, Map stringMap) { + return fromEntrySet(origin, stringMap.entrySet()); + } + + static AbstractConfigObject fromPathMap(ConfigOrigin origin, + Map pathExpressionMap) { + Map pathMap = new HashMap(); + for (Map.Entry entry : pathExpressionMap.entrySet()) { + Object keyObj = entry.getKey(); + if (!(keyObj instanceof String)) { + throw new ConfigException.BugOrBroken( + "Map has a non-string as a key, expecting a path expression as a String"); + } + Path path = Path.newPath((String) keyObj); + pathMap.put(path, entry.getValue()); + } + return fromPathMap(origin, pathMap, false /* from properties */); + } + + private static AbstractConfigObject fromPathMap(ConfigOrigin origin, + Map pathMap, boolean convertedFromProperties) { + /* + * First, build a list of paths that will have values, either string or + * object values. + */ + Set scopePaths = new HashSet(); + Set valuePaths = new HashSet(); + for (Path path : pathMap.keySet()) { + // add value's path + valuePaths.add(path); + + // all parent paths are objects + Path next = path.parent(); + while (next != null) { + scopePaths.add(next); + next = next.parent(); + } + } + + if (convertedFromProperties) { + /* + * If any string values are also objects containing other values, + * drop those string values - objects "win". + */ + valuePaths.removeAll(scopePaths); + } else { + /* If we didn't start out as properties, then this is an error. */ + for (Path path : valuePaths) { + if (scopePaths.contains(path)) { + throw new ConfigException.BugOrBroken( + "In the map, path '" + + path.render() + + "' occurs as both the parent object of a value and as a value. " + + "Because Map has no defined ordering, this is a broken situation."); + } + } + } + + /* + * Create maps for the object-valued values. + */ + Map root = new HashMap(); + Map> scopes = new HashMap>(); + + for (Path path : scopePaths) { + Map scope = new HashMap(); + scopes.put(path, scope); + } + + /* Store string values in the associated scope maps */ + for (Path path : valuePaths) { + Path parentPath = path.parent(); + Map parent = parentPath != null ? scopes + .get(parentPath) : root; + + String last = path.last(); + Object rawValue = pathMap.get(path); + AbstractConfigValue value; + if (convertedFromProperties) { + if (rawValue instanceof String) { + value = new ConfigString.Quoted(origin, (String) rawValue); + } else { + // silently ignore non-string values in Properties + value = null; + } + } else { + value = ConfigImpl.fromAnyRef(pathMap.get(path), origin, + FromMapMode.KEYS_ARE_PATHS); + } + if (value != null) + parent.put(last, value); + } + + /* + * Make a list of scope paths from longest to shortest, so children go + * before parents. + */ + List sortedScopePaths = new ArrayList(); + sortedScopePaths.addAll(scopePaths); + // sort descending by length + Collections.sort(sortedScopePaths, new Comparator() { + @Override + public int compare(Path a, Path b) { + // Path.length() is O(n) so in theory this sucks + // but in practice we can make Path precompute length + // if it ever matters. + return b.length() - a.length(); + } + }); + + /* + * Create ConfigObject for each scope map, working from children to + * parents to avoid modifying any already-created ConfigObject. This is + * where we need the sorted list. + */ + for (Path scopePath : sortedScopePaths) { + Map scope = scopes.get(scopePath); + + Path parentPath = scopePath.parent(); + Map parent = parentPath != null ? scopes + .get(parentPath) : root; + + AbstractConfigObject o = new SimpleConfigObject(origin, scope, + ResolveStatus.RESOLVED, false /* ignoresFallbacks */); + parent.put(scopePath.last(), o); + } + + // return root config object + return new SimpleConfigObject(origin, root, ResolveStatus.RESOLVED, + false /* ignoresFallbacks */); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ReplaceableMergeStack.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ReplaceableMergeStack.java new file mode 100644 index 0000000..bde6e41 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ReplaceableMergeStack.java @@ -0,0 +1,14 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +/** + * Implemented by a merge stack (ConfigDelayedMerge, ConfigDelayedMergeObject) + * that replaces itself during substitution resolution in order to implement + * "look backwards only" semantics. + */ +interface ReplaceableMergeStack extends Container { + /** + * Make a replacement for this object skipping the given number of elements + * which are lower in merge priority. + */ + AbstractConfigValue makeReplacement(ResolveContext context, int skipping); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveContext.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveContext.java new file mode 100644 index 0000000..4f1ef72 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveContext.java @@ -0,0 +1,237 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.ArrayList; +import java.util.Set; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigResolveOptions; + +final class ResolveContext { + final private ResolveMemos memos; + + final private ConfigResolveOptions options; + // the current path restriction, used to ensure lazy + // resolution and avoid gratuitous cycles. without this, + // any sibling of an object we're traversing could + // cause a cycle "by side effect" + // CAN BE NULL for a full resolve. + final private Path restrictToChild; + + // This is used for tracing and debugging and nice error messages; + // contains every node as we call resolve on it. + final private List resolveStack; + + final private Set cycleMarkers; + + ResolveContext(ResolveMemos memos, ConfigResolveOptions options, Path restrictToChild, + List resolveStack, Set cycleMarkers) { + this.memos = memos; + this.options = options; + this.restrictToChild = restrictToChild; + this.resolveStack = Collections.unmodifiableList(resolveStack); + this.cycleMarkers = Collections.unmodifiableSet(cycleMarkers); + } + + private static Set newCycleMarkers() { + return Collections.newSetFromMap(new IdentityHashMap()); + } + + ResolveContext(ConfigResolveOptions options, Path restrictToChild) { + // LinkedHashSet keeps the traversal order which is at least useful + // in error messages if nothing else + this(new ResolveMemos(), options, restrictToChild, new ArrayList(), newCycleMarkers()); + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(depth(), "ResolveContext restrict to child " + restrictToChild); + } + + ResolveContext addCycleMarker(AbstractConfigValue value) { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(depth(), "++ Cycle marker " + value + "@" + System.identityHashCode(value)); + if (cycleMarkers.contains(value)) + throw new ConfigException.BugOrBroken("Added cycle marker twice " + value); + Set copy = newCycleMarkers(); + copy.addAll(cycleMarkers); + copy.add(value); + return new ResolveContext(memos, options, restrictToChild, resolveStack, copy); + } + + ResolveContext removeCycleMarker(AbstractConfigValue value) { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(depth(), "-- Cycle marker " + value + "@" + System.identityHashCode(value)); + + Set copy = newCycleMarkers(); + copy.addAll(cycleMarkers); + copy.remove(value); + return new ResolveContext(memos, options, restrictToChild, resolveStack, copy); + } + + private ResolveContext memoize(MemoKey key, AbstractConfigValue value) { + ResolveMemos changed = memos.put(key, value); + return new ResolveContext(changed, options, restrictToChild, resolveStack, cycleMarkers); + } + + ConfigResolveOptions options() { + return options; + } + + boolean isRestrictedToChild() { + return restrictToChild != null; + } + + Path restrictToChild() { + return restrictToChild; + } + + // restrictTo may be null to unrestrict + ResolveContext restrict(Path restrictTo) { + if (restrictTo == restrictToChild) + return this; + else + return new ResolveContext(memos, options, restrictTo, resolveStack, cycleMarkers); + } + + ResolveContext unrestricted() { + return restrict(null); + } + + String traceString() { + String separator = ", "; + StringBuilder sb = new StringBuilder(); + for (AbstractConfigValue value : resolveStack) { + if (value instanceof ConfigReference) { + sb.append(((ConfigReference) value).expression().toString()); + sb.append(separator); + } + } + if (sb.length() > 0) + sb.setLength(sb.length() - separator.length()); + return sb.toString(); + } + + private ResolveContext pushTrace(AbstractConfigValue value) { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(depth(), "pushing trace " + value); + List copy = new ArrayList(resolveStack); + copy.add(value); + return new ResolveContext(memos, options, restrictToChild, copy, cycleMarkers); + } + + ResolveContext popTrace() { + List copy = new ArrayList(resolveStack); + AbstractConfigValue old = copy.remove(resolveStack.size() - 1); + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(depth() - 1, "popped trace " + old); + return new ResolveContext(memos, options, restrictToChild, copy, cycleMarkers); + } + + int depth() { + if (resolveStack.size() > 30) + throw new ConfigException.BugOrBroken("resolve getting too deep"); + return resolveStack.size(); + } + + ResolveResult resolve(AbstractConfigValue original, ResolveSource source) + throws AbstractConfigValue.NotPossibleToResolve { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl + .trace(depth(), "resolving " + original + " restrictToChild=" + restrictToChild + " in " + source); + return pushTrace(original).realResolve(original, source).popTrace(); + } + + private ResolveResult realResolve(AbstractConfigValue original, ResolveSource source) + throws AbstractConfigValue.NotPossibleToResolve { + // a fully-resolved (no restrictToChild) object can satisfy a + // request for a restricted object, so always check that first. + final MemoKey fullKey = new MemoKey(original, null); + MemoKey restrictedKey = null; + + AbstractConfigValue cached = memos.get(fullKey); + + // but if there was no fully-resolved object cached, we'll only + // compute the restrictToChild object so use a more limited + // memo key + if (cached == null && isRestrictedToChild()) { + restrictedKey = new MemoKey(original, restrictToChild()); + cached = memos.get(restrictedKey); + } + + if (cached != null) { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(depth(), "using cached resolution " + cached + " for " + original + + " restrictToChild " + restrictToChild()); + return ResolveResult.make(this, cached); + } else { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(depth(), + "not found in cache, resolving " + original + "@" + System.identityHashCode(original)); + + if (cycleMarkers.contains(original)) { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(depth(), + "Cycle detected, can't resolve; " + original + "@" + System.identityHashCode(original)); + throw new AbstractConfigValue.NotPossibleToResolve(this); + } + + ResolveResult result = original.resolveSubstitutions(this, source); + AbstractConfigValue resolved = result.value; + + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(depth(), "resolved to " + resolved + "@" + System.identityHashCode(resolved) + + " from " + original + "@" + System.identityHashCode(resolved)); + + ResolveContext withMemo = result.context; + + if (resolved == null || resolved.resolveStatus() == ResolveStatus.RESOLVED) { + // if the resolved object is fully resolved by resolving + // only the restrictToChildOrNull, then it can be cached + // under fullKey since the child we were restricted to + // turned out to be the only unresolved thing. + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(depth(), "caching " + fullKey + " result " + resolved); + + withMemo = withMemo.memoize(fullKey, resolved); + } else { + // if we have an unresolved object then either we did a + // partial resolve restricted to a certain child, or we are + // allowing incomplete resolution, or it's a bug. + if (isRestrictedToChild()) { + if (restrictedKey == null) { + throw new ConfigException.BugOrBroken( + "restrictedKey should not be null here"); + } + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(depth(), "caching " + restrictedKey + " result " + resolved); + + withMemo = withMemo.memoize(restrictedKey, resolved); + } else if (options().getAllowUnresolved()) { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(depth(), "caching " + fullKey + " result " + resolved); + + withMemo = withMemo.memoize(fullKey, resolved); + } else { + throw new ConfigException.BugOrBroken( + "resolveSubstitutions() did not give us a resolved object"); + } + } + + return ResolveResult.make(withMemo, resolved); + } + } + + static AbstractConfigValue resolve(AbstractConfigValue value, AbstractConfigObject root, + ConfigResolveOptions options) { + ResolveSource source = new ResolveSource(root); + ResolveContext context = new ResolveContext(options, null /* restrictToChild */); + + try { + return context.resolve(value, source).value; + } catch (AbstractConfigValue.NotPossibleToResolve e) { + // ConfigReference was supposed to catch NotPossibleToResolve + throw new ConfigException.BugOrBroken( + "NotPossibleToResolve was thrown from an outermost resolve", e); + } + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveMemos.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveMemos.java new file mode 100644 index 0000000..6f5049b --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveMemos.java @@ -0,0 +1,35 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.HashMap; +import java.util.Map; + +/** + * This exists because we have to memoize resolved substitutions as we go + * through the config tree; otherwise we could end up creating multiple copies + * of values or whole trees of values as we follow chains of substitutions. + */ +final class ResolveMemos { + // note that we can resolve things to undefined (represented as Java null, + // rather than ConfigNull) so this map can have null values. + final private Map memos; + + private ResolveMemos(Map memos) { + this.memos = memos; + } + + ResolveMemos() { + this(new HashMap()); + } + + AbstractConfigValue get(MemoKey key) { + return memos.get(key); + } + + ResolveMemos put(MemoKey key, AbstractConfigValue value) { + // completely inefficient, but so far nobody cares about resolve() + // performance, we can clean it up someday... + Map copy = new HashMap(memos); + copy.put(key, value); + return new ResolveMemos(copy); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveResult.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveResult.java new file mode 100644 index 0000000..5dd63e0 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveResult.java @@ -0,0 +1,43 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; + +// value is allowed to be null +final class ResolveResult { + public final ResolveContext context; + public final V value; + + private ResolveResult(ResolveContext context, V value) { + this.context = context; + this.value = value; + } + + static ResolveResult make(ResolveContext context, V value) { + return new ResolveResult(context, value); + } + + // better option? we don't have variance + @SuppressWarnings("unchecked") + ResolveResult asObjectResult() { + if (!(value instanceof AbstractConfigObject)) + throw new ConfigException.BugOrBroken("Expecting a resolve result to be an object, but it was " + value); + Object o = this; + return (ResolveResult) o; + } + + // better option? we don't have variance + @SuppressWarnings("unchecked") + ResolveResult asValueResult() { + Object o = this; + return (ResolveResult) o; + } + + ResolveResult popTrace() { + return make(context.popTrace(), value); + } + + @Override + public String toString() { + return "ResolveResult(" + value + ")"; + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveSource.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveSource.java new file mode 100644 index 0000000..1f81c8b --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveSource.java @@ -0,0 +1,349 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; + +/** + * This class is the source for values for a substitution like ${foo}. + */ +final class ResolveSource { + + final AbstractConfigObject root; + // This is used for knowing the chain of parents we used to get here. + // null if we should assume we are not a descendant of the root. + // the root itself should be a node in this if non-null. + final Node pathFromRoot; + + ResolveSource(AbstractConfigObject root, Node pathFromRoot) { + this.root = root; + this.pathFromRoot = pathFromRoot; + } + + ResolveSource(AbstractConfigObject root) { + this.root = root; + this.pathFromRoot = null; + } + + // if we replace the root with a non-object, use an empty + // object with nothing in it instead. + private AbstractConfigObject rootMustBeObj(Container value) { + if (value instanceof AbstractConfigObject) { + return (AbstractConfigObject) value; + } else { + return SimpleConfigObject.empty(); + } + } + + // as a side effect, findInObject() will have to resolve all parents of the + // child being peeked, but NOT the child itself. Caller has to resolve + // the child itself if needed. ValueWithPath.value can be null but + // the ValueWithPath instance itself should not be. + static private ResultWithPath findInObject(AbstractConfigObject obj, ResolveContext context, Path path) + throws AbstractConfigValue.NotPossibleToResolve { + // resolve ONLY portions of the object which are along our path + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace("*** finding '" + path + "' in " + obj); + Path restriction = context.restrictToChild(); + ResolveResult partiallyResolved = context.restrict(path).resolve(obj, + new ResolveSource(obj)); + ResolveContext newContext = partiallyResolved.context.restrict(restriction); + if (partiallyResolved.value instanceof AbstractConfigObject) { + ValueWithPath pair = findInObject((AbstractConfigObject) partiallyResolved.value, path); + return new ResultWithPath(ResolveResult.make(newContext, pair.value), pair.pathFromRoot); + } else { + throw new ConfigException.BugOrBroken("resolved object to non-object " + obj + " to " + partiallyResolved); + } + } + + static private ValueWithPath findInObject(AbstractConfigObject obj, Path path) { + try { + // we'll fail if anything along the path can't + // be looked at without resolving. + return findInObject(obj, path, null); + } catch (ConfigException.NotResolved e) { + throw ConfigImpl.improveNotResolved(path, e); + } + } + + static private ValueWithPath findInObject(AbstractConfigObject obj, Path path, Node parents) { + String key = path.first(); + Path next = path.remainder(); + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace("*** looking up '" + key + "' in " + obj); + AbstractConfigValue v = obj.attemptPeekWithPartialResolve(key); + Node newParents = parents == null ? new Node(obj) : parents.prepend(obj); + + if (next == null) { + return new ValueWithPath(v, newParents); + } else { + if (v instanceof AbstractConfigObject) { + return findInObject((AbstractConfigObject) v, next, newParents); + } else { + return new ValueWithPath(null, newParents); + } + } + } + + ResultWithPath lookupSubst(ResolveContext context, SubstitutionExpression subst, + int prefixLength) + throws AbstractConfigValue.NotPossibleToResolve { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(context.depth(), "searching for " + subst); + + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(context.depth(), subst + " - looking up relative to file it occurred in"); + // First we look up the full path, which means relative to the + // included file if we were not a root file + ResultWithPath result = findInObject(root, context, subst.path()); + + if (result.result.value == null) { + // Then we want to check relative to the root file. We don't + // want the prefix we were included at to be used when looking + // up env variables either. + Path unprefixed = subst.path().subPath(prefixLength); + + if (prefixLength > 0) { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(result.result.context.depth(), unprefixed + + " - looking up relative to parent file"); + result = findInObject(root, result.result.context, unprefixed); + } + + if (result.result.value == null && result.result.context.options().getUseSystemEnvironment()) { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(result.result.context.depth(), unprefixed + " - looking up in system environment"); + result = findInObject(ConfigImpl.envVariablesAsConfigObject(), context, unprefixed); + } + } + + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace(result.result.context.depth(), "resolved to " + result); + + return result; + } + + ResolveSource pushParent(Container parent) { + if (parent == null) + throw new ConfigException.BugOrBroken("can't push null parent"); + + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace("pushing parent " + parent + " ==root " + (parent == root) + " onto " + this); + + if (pathFromRoot == null) { + if (parent == root) { + return new ResolveSource(root, new Node(parent)); + } else { + if (ConfigImpl.traceSubstitutionsEnabled()) { + // this hasDescendant check is super-expensive so it's a + // trace message rather than an assertion + if (root.hasDescendant((AbstractConfigValue) parent)) + ConfigImpl.trace("***** BUG ***** tried to push parent " + parent + + " without having a path to it in " + this); + } + // ignore parents if we aren't proceeding from the + // root + return this; + } + } else { + Container parentParent = pathFromRoot.head(); + if (ConfigImpl.traceSubstitutionsEnabled()) { + // this hasDescendant check is super-expensive so it's a + // trace message rather than an assertion + if (parentParent != null && !parentParent.hasDescendant((AbstractConfigValue) parent)) + ConfigImpl.trace("***** BUG ***** trying to push non-child of " + parentParent + ", non-child was " + + parent); + } + + return new ResolveSource(root, pathFromRoot.prepend(parent)); + } + } + + ResolveSource resetParents() { + if (pathFromRoot == null) + return this; + else + return new ResolveSource(root); + } + + // returns null if the replacement results in deleting all the nodes. + private static Node replace(Node list, Container old, AbstractConfigValue replacement) { + Container child = list.head(); + if (child != old) + throw new ConfigException.BugOrBroken("Can only replace() the top node we're resolving; had " + child + + " on top and tried to replace " + old + " overall list was " + list); + Container parent = list.tail() == null ? null : list.tail().head(); + if (replacement == null || !(replacement instanceof Container)) { + if (parent == null) { + return null; + } else { + /* + * we are deleting the child from the stack of containers + * because it's either going away or not a container + */ + AbstractConfigValue newParent = parent.replaceChild((AbstractConfigValue) old, null); + + return replace(list.tail(), parent, newParent); + } + } else { + /* we replaced the container with another container */ + if (parent == null) { + return new Node((Container) replacement); + } else { + AbstractConfigValue newParent = parent.replaceChild((AbstractConfigValue) old, replacement); + Node newTail = replace(list.tail(), parent, newParent); + if (newTail != null) + return newTail.prepend((Container) replacement); + else + return new Node((Container) replacement); + } + } + } + + ResolveSource replaceCurrentParent(Container old, Container replacement) { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace("replaceCurrentParent old " + old + "@" + System.identityHashCode(old) + " replacement " + + replacement + "@" + System.identityHashCode(old) + " in " + this); + if (old == replacement) { + return this; + } else if (pathFromRoot != null) { + Node newPath = replace(pathFromRoot, old, (AbstractConfigValue) replacement); + if (ConfigImpl.traceSubstitutionsEnabled()) { + ConfigImpl.trace("replaced " + old + " with " + replacement + " in " + this); + ConfigImpl.trace("path was: " + pathFromRoot + " is now " + newPath); + } + // if we end up nuking the root object itself, we replace it with an + // empty root + if (newPath != null) + return new ResolveSource((AbstractConfigObject) newPath.last(), newPath); + else + return new ResolveSource(SimpleConfigObject.empty()); + } else { + if (old == root) { + return new ResolveSource(rootMustBeObj(replacement)); + } else { + throw new ConfigException.BugOrBroken("attempt to replace root " + root + " with " + replacement); + // return this; + } + } + } + + // replacement may be null to delete + ResolveSource replaceWithinCurrentParent(AbstractConfigValue old, AbstractConfigValue replacement) { + if (ConfigImpl.traceSubstitutionsEnabled()) + ConfigImpl.trace("replaceWithinCurrentParent old " + old + "@" + System.identityHashCode(old) + + " replacement " + replacement + "@" + System.identityHashCode(old) + " in " + this); + if (old == replacement) { + return this; + } else if (pathFromRoot != null) { + Container parent = pathFromRoot.head(); + AbstractConfigValue newParent = parent.replaceChild(old, replacement); + return replaceCurrentParent(parent, (newParent instanceof Container) ? (Container) newParent : null); + } else { + if (old == root && replacement instanceof Container) { + return new ResolveSource(rootMustBeObj((Container) replacement)); + } else { + throw new ConfigException.BugOrBroken("replace in parent not possible " + old + " with " + replacement + + " in " + this); + // return this; + } + } + } + + @Override + public String toString() { + return "ResolveSource(root=" + root + ", pathFromRoot=" + pathFromRoot + ")"; + } + + // a persistent list + static final class Node { + final T value; + final Node next; + + Node(T value, Node next) { + this.value = value; + this.next = next; + } + + Node(T value) { + this(value, null); + } + + Node prepend(T value) { + return new Node(value, this); + } + + T head() { + return value; + } + + Node tail() { + return next; + } + + T last() { + Node i = this; + while (i.next != null) + i = i.next; + return i.value; + } + + Node reverse() { + if (next == null) { + return this; + } else { + Node reversed = new Node(value); + Node i = next; + while (i != null) { + reversed = reversed.prepend(i.value); + i = i.next; + } + return reversed; + } + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("["); + Node toAppendValue = this.reverse(); + while (toAppendValue != null) { + sb.append(toAppendValue.value.toString()); + if (toAppendValue.next != null) + sb.append(" <= "); + toAppendValue = toAppendValue.next; + } + sb.append("]"); + return sb.toString(); + } + } + + // value is allowed to be null + static final class ValueWithPath { + final AbstractConfigValue value; + final Node pathFromRoot; + + ValueWithPath(AbstractConfigValue value, Node pathFromRoot) { + this.value = value; + this.pathFromRoot = pathFromRoot; + } + + @Override + public String toString() { + return "ValueWithPath(value=" + value + ", pathFromRoot=" + pathFromRoot + ")"; + } + } + + static final class ResultWithPath { + final ResolveResult result; + final Node pathFromRoot; + + ResultWithPath(ResolveResult result, Node pathFromRoot) { + this.result = result; + this.pathFromRoot = pathFromRoot; + } + + @Override + public String toString() { + return "ResultWithPath(result=" + result + ", pathFromRoot=" + pathFromRoot + ")"; + } + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveStatus.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveStatus.java new file mode 100644 index 0000000..27eef3b --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/ResolveStatus.java @@ -0,0 +1,26 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.Collection; + +/** + * Status of substitution resolution. + */ +enum ResolveStatus { + UNRESOLVED, RESOLVED; + + final static ResolveStatus fromValues( + Collection values) { + for (AbstractConfigValue v : values) { + if (v.resolveStatus() == ResolveStatus.UNRESOLVED) + return ResolveStatus.UNRESOLVED; + } + return ResolveStatus.RESOLVED; + } + + final static ResolveStatus fromBoolean(boolean resolved) { + return resolved ? ResolveStatus.RESOLVED : ResolveStatus.UNRESOLVED; + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SerializedConfigValue.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SerializedConfigValue.java new file mode 100644 index 0000000..f159f7f --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SerializedConfigValue.java @@ -0,0 +1,534 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.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; +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.ObjectStreamException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.drtshock.playervaults.lib.com.typesafe.config.Config; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigList; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigObject; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +/** + * Deliberately shoving all the serialization code into this class instead of + * doing it OO-style with each subclass. Seems better to have it all in one + * place. This class implements a lame serialization format that supports + * skipping unknown fields, so it's moderately more extensible than the default + * Java serialization format. + */ +class SerializedConfigValue extends AbstractConfigValue implements Externalizable { + + // this is the version used by Java serialization, if it increments it's + // essentially an ABI break and bad + private static final long serialVersionUID = 1L; + + // this is how we try to be extensible + static enum SerializedField { + // represents a field code we didn't recognize + UNKNOWN, + + // end of a list of fields + END_MARKER, + + // Fields at the root + ROOT_VALUE, + ROOT_WAS_CONFIG, + + // Fields that make up a value + VALUE_DATA, + VALUE_ORIGIN, + + // Fields that make up an origin + ORIGIN_DESCRIPTION, + ORIGIN_LINE_NUMBER, + ORIGIN_END_LINE_NUMBER, + ORIGIN_TYPE, + ORIGIN_URL, + ORIGIN_COMMENTS, + ORIGIN_NULL_URL, + ORIGIN_NULL_COMMENTS, + ORIGIN_RESOURCE, + ORIGIN_NULL_RESOURCE; + + static SerializedField forInt(int b) { + if (b < values().length) + return values()[b]; + else + return UNKNOWN; + } + }; + + private static enum SerializedValueType { + // the ordinals here are in the wire format, caution + NULL(ConfigValueType.NULL), + BOOLEAN(ConfigValueType.BOOLEAN), + INT(ConfigValueType.NUMBER), + LONG(ConfigValueType.NUMBER), + DOUBLE(ConfigValueType.NUMBER), + STRING(ConfigValueType.STRING), + LIST(ConfigValueType.LIST), + OBJECT(ConfigValueType.OBJECT); + + ConfigValueType configType; + + SerializedValueType(ConfigValueType configType) { + this.configType = configType; + } + + static SerializedValueType forInt(int b) { + if (b < values().length) + return values()[b]; + else + return null; + } + + static SerializedValueType forValue(ConfigValue value) { + ConfigValueType t = value.valueType(); + if (t == ConfigValueType.NUMBER) { + if (value instanceof ConfigInt) + return INT; + else if (value instanceof ConfigLong) + return LONG; + else if (value instanceof ConfigDouble) + return DOUBLE; + } else { + for (SerializedValueType st : values()) { + if (st.configType == t) + return st; + } + } + + throw new ConfigException.BugOrBroken("don't know how to serialize " + value); + } + }; + + private ConfigValue value; + private boolean wasConfig; + + // this has to be public for the Java deserializer + public SerializedConfigValue() { + super(null); + } + + SerializedConfigValue(ConfigValue value) { + this(); + this.value = value; + this.wasConfig = false; + } + + SerializedConfigValue(Config conf) { + this(conf.root()); + this.wasConfig = true; + } + + // when Java deserializer reads this object, return the contained + // object instead. + private Object readResolve() throws ObjectStreamException { + if (wasConfig) + return ((ConfigObject) value).toConfig(); + else + return value; + } + + private static class FieldOut { + final SerializedField code; + final ByteArrayOutputStream bytes; + final DataOutput data; + + FieldOut(SerializedField code) { + this.code = code; + this.bytes = new ByteArrayOutputStream(); + this.data = new DataOutputStream(bytes); + } + } + + // this is a separate function to prevent bugs writing to the + // outer stream instead of field.data + private static void writeOriginField(DataOutput out, SerializedField code, Object v) + throws IOException { + switch (code) { + case ORIGIN_DESCRIPTION: + out.writeUTF((String) v); + break; + case ORIGIN_LINE_NUMBER: + out.writeInt((Integer) v); + break; + case ORIGIN_END_LINE_NUMBER: + out.writeInt((Integer) v); + break; + case ORIGIN_TYPE: + out.writeByte((Integer) v); + break; + case ORIGIN_URL: + out.writeUTF((String) v); + break; + case ORIGIN_RESOURCE: + out.writeUTF((String) v); + break; + case ORIGIN_COMMENTS: + @SuppressWarnings("unchecked") + List list = (List) v; + int size = list.size(); + out.writeInt(size); + for (String s : list) { + out.writeUTF(s); + } + break; + case ORIGIN_NULL_URL: // FALL THRU + case ORIGIN_NULL_RESOURCE: // FALL THRU + case ORIGIN_NULL_COMMENTS: + // nothing to write out besides code and length + break; + default: + throw new IOException("Unhandled field from origin: " + code); + } + } + + // not private because we use it to serialize ConfigException + static void writeOrigin(DataOutput out, SimpleConfigOrigin origin, + SimpleConfigOrigin baseOrigin) throws IOException { + Map m; + // to serialize a null origin, we write out no fields at all + if (origin != null) + m = origin.toFieldsDelta(baseOrigin); + else + m = Collections.emptyMap(); + for (Map.Entry e : m.entrySet()) { + FieldOut field = new FieldOut(e.getKey()); + Object v = e.getValue(); + writeOriginField(field.data, field.code, v); + writeField(out, field); + } + writeEndMarker(out); + } + + // not private because we use it to deserialize ConfigException + static SimpleConfigOrigin readOrigin(DataInput in, SimpleConfigOrigin baseOrigin) + throws IOException { + Map m = new EnumMap(SerializedField.class); + while (true) { + Object v = null; + SerializedField field = readCode(in); + switch (field) { + case END_MARKER: + return SimpleConfigOrigin.fromBase(baseOrigin, m); + case ORIGIN_DESCRIPTION: + in.readInt(); // discard length + v = in.readUTF(); + break; + case ORIGIN_LINE_NUMBER: + in.readInt(); // discard length + v = in.readInt(); + break; + case ORIGIN_END_LINE_NUMBER: + in.readInt(); // discard length + v = in.readInt(); + break; + case ORIGIN_TYPE: + in.readInt(); // discard length + v = in.readUnsignedByte(); + break; + case ORIGIN_URL: + in.readInt(); // discard length + v = in.readUTF(); + break; + case ORIGIN_RESOURCE: + in.readInt(); // discard length + v = in.readUTF(); + break; + case ORIGIN_COMMENTS: + in.readInt(); // discard length + int size = in.readInt(); + List list = new ArrayList(size); + for (int i = 0; i < size; ++i) { + list.add(in.readUTF()); + } + v = list; + break; + case ORIGIN_NULL_URL: // FALL THRU + case ORIGIN_NULL_RESOURCE: // FALL THRU + case ORIGIN_NULL_COMMENTS: + // nothing to read besides code and length + in.readInt(); // discard length + v = ""; // just something non-null to put in the map + break; + case ROOT_VALUE: + case ROOT_WAS_CONFIG: + case VALUE_DATA: + case VALUE_ORIGIN: + throw new IOException("Not expecting this field here: " + field); + case UNKNOWN: + // skip unknown field + skipField(in); + break; + } + if (v != null) + m.put(field, v); + } + } + + private static void writeValueData(DataOutput out, ConfigValue value) throws IOException { + SerializedValueType st = SerializedValueType.forValue(value); + out.writeByte(st.ordinal()); + switch (st) { + case BOOLEAN: + out.writeBoolean(((ConfigBoolean) value).unwrapped()); + break; + case NULL: + break; + case INT: + // saving numbers as both string and binary is redundant but easy + out.writeInt(((ConfigInt) value).unwrapped()); + out.writeUTF(((ConfigNumber) value).transformToString()); + break; + case LONG: + out.writeLong(((ConfigLong) value).unwrapped()); + out.writeUTF(((ConfigNumber) value).transformToString()); + break; + case DOUBLE: + out.writeDouble(((ConfigDouble) value).unwrapped()); + out.writeUTF(((ConfigNumber) value).transformToString()); + break; + case STRING: + out.writeUTF(((ConfigString) value).unwrapped()); + break; + case LIST: + ConfigList list = (ConfigList) value; + out.writeInt(list.size()); + for (ConfigValue v : list) { + writeValue(out, v, (SimpleConfigOrigin) list.origin()); + } + break; + case OBJECT: + ConfigObject obj = (ConfigObject) value; + out.writeInt(obj.size()); + for (Map.Entry e : obj.entrySet()) { + out.writeUTF(e.getKey()); + writeValue(out, e.getValue(), (SimpleConfigOrigin) obj.origin()); + } + break; + } + } + + private static AbstractConfigValue readValueData(DataInput in, SimpleConfigOrigin origin) + throws IOException { + int stb = in.readUnsignedByte(); + SerializedValueType st = SerializedValueType.forInt(stb); + if (st == null) + throw new IOException("Unknown serialized value type: " + stb); + switch (st) { + case BOOLEAN: + return new ConfigBoolean(origin, in.readBoolean()); + case NULL: + return new ConfigNull(origin); + case INT: + int vi = in.readInt(); + String si = in.readUTF(); + return new ConfigInt(origin, vi, si); + case LONG: + long vl = in.readLong(); + String sl = in.readUTF(); + return new ConfigLong(origin, vl, sl); + case DOUBLE: + double vd = in.readDouble(); + String sd = in.readUTF(); + return new ConfigDouble(origin, vd, sd); + case STRING: + return new ConfigString.Quoted(origin, in.readUTF()); + case LIST: + int listSize = in.readInt(); + List list = new ArrayList(listSize); + for (int i = 0; i < listSize; ++i) { + AbstractConfigValue v = readValue(in, origin); + list.add(v); + } + return new SimpleConfigList(origin, list); + case OBJECT: + int mapSize = in.readInt(); + Map map = new HashMap(mapSize); + for (int i = 0; i < mapSize; ++i) { + String key = in.readUTF(); + AbstractConfigValue v = readValue(in, origin); + map.put(key, v); + } + return new SimpleConfigObject(origin, map); + } + throw new IOException("Unhandled serialized value type: " + st); + } + + private static void writeValue(DataOutput out, ConfigValue value, SimpleConfigOrigin baseOrigin) + throws IOException { + FieldOut origin = new FieldOut(SerializedField.VALUE_ORIGIN); + writeOrigin(origin.data, (SimpleConfigOrigin) value.origin(), + baseOrigin); + writeField(out, origin); + + FieldOut data = new FieldOut(SerializedField.VALUE_DATA); + writeValueData(data.data, value); + writeField(out, data); + + writeEndMarker(out); + } + + private static AbstractConfigValue readValue(DataInput in, SimpleConfigOrigin baseOrigin) + throws IOException { + AbstractConfigValue value = null; + SimpleConfigOrigin origin = null; + while (true) { + SerializedField code = readCode(in); + if (code == SerializedField.END_MARKER) { + if (value == null) + throw new IOException("No value data found in serialization of value"); + return value; + } else if (code == SerializedField.VALUE_DATA) { + if (origin == null) + throw new IOException("Origin must be stored before value data"); + in.readInt(); // discard length + value = readValueData(in, origin); + } else if (code == SerializedField.VALUE_ORIGIN) { + in.readInt(); // discard length + origin = readOrigin(in, baseOrigin); + } else { + // ignore unknown field + skipField(in); + } + } + } + + private static void writeField(DataOutput out, FieldOut field) throws IOException { + byte[] bytes = field.bytes.toByteArray(); + out.writeByte(field.code.ordinal()); + out.writeInt(bytes.length); + out.write(bytes); + } + + private static void writeEndMarker(DataOutput out) throws IOException { + out.writeByte(SerializedField.END_MARKER.ordinal()); + } + + private static SerializedField readCode(DataInput in) throws IOException { + int c = in.readUnsignedByte(); + if (c == SerializedField.UNKNOWN.ordinal()) + throw new IOException("field code " + c + " is not supposed to be on the wire"); + return SerializedField.forInt(c); + } + + private static void skipField(DataInput in) throws IOException { + int len = in.readInt(); + // skipBytes doesn't have to block + int skipped = in.skipBytes(len); + if (skipped < len) { + // wastefully use readFully() if skipBytes didn't work + byte[] bytes = new byte[(len - skipped)]; + in.readFully(bytes); + } + } + + @Override + public void writeExternal(ObjectOutput out) throws IOException { + if (((AbstractConfigValue) value).resolveStatus() != ResolveStatus.RESOLVED) + throw new NotSerializableException( + "tried to serialize a value with unresolved substitutions, need to Config#resolve() first, see API docs"); + FieldOut field = new FieldOut(SerializedField.ROOT_VALUE); + writeValue(field.data, value, null /* baseOrigin */); + writeField(out, field); + + field = new FieldOut(SerializedField.ROOT_WAS_CONFIG); + field.data.writeBoolean(wasConfig); + writeField(out, field); + + writeEndMarker(out); + } + + @Override + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + while (true) { + SerializedField code = readCode(in); + if (code == SerializedField.END_MARKER) { + return; + } + + DataInput input = fieldIn(in); + if (code == SerializedField.ROOT_VALUE) { + this.value = readValue(input, null /* baseOrigin */); + } else if (code == SerializedField.ROOT_WAS_CONFIG) { + 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"); + } + + @Override + public ConfigValueType valueType() { + throw shouldNotBeUsed(); + } + + @Override + public Object unwrapped() { + throw shouldNotBeUsed(); + } + + @Override + protected SerializedConfigValue newCopy(ConfigOrigin origin) { + throw shouldNotBeUsed(); + } + + @Override + public final String toString() { + return getClass().getSimpleName() + "(value=" + value + ",wasConfig=" + wasConfig + ")"; + } + + @Override + public boolean equals(Object other) { + // there's no reason we will ever call this equals(), but + // the one in AbstractConfigValue would explode due to + // calling unwrapped() above, so we just give some + // safe-to-call implementation to avoid breaking the + // contract of java.lang.Object + if (other instanceof SerializedConfigValue) { + return canEqual(other) + && (this.wasConfig == ((SerializedConfigValue) other).wasConfig) + && (this.value.equals(((SerializedConfigValue) other).value)); + } else { + return false; + } + } + + @Override + public int hashCode() { + int h = 41 * (41 + value.hashCode()); + h = 41 * (h + (wasConfig ? 1 : 0)); + return h; + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfig.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfig.java new file mode 100644 index 0000000..b8c897f --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfig.java @@ -0,0 +1,1168 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +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; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigList; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigMemorySize; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigMergeable; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigObject; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigResolveOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; +import com.drtshock.playervaults.lib.com.typesafe.config.Config; + +/** + * One thing to keep in mind in the future: as Collection-like APIs are added + * here, including iterators or size() or anything, they should be consistent + * with a one-level java.util.Map from paths to non-null values. Null values are + * not "in" the map. + */ +final class SimpleConfig implements Config, MergeableValue, Serializable { + + private static final long serialVersionUID = 1L; + + final private AbstractConfigObject object; + + SimpleConfig(AbstractConfigObject object) { + this.object = object; + } + + @Override + public AbstractConfigObject root() { + return object; + } + + @Override + public ConfigOrigin origin() { + return object.origin(); + } + + @Override + public SimpleConfig resolve() { + return resolve(ConfigResolveOptions.defaults()); + } + + @Override + public SimpleConfig resolve(ConfigResolveOptions options) { + return resolveWith(this, options); + } + + @Override + public SimpleConfig resolveWith(Config source) { + return resolveWith(source, ConfigResolveOptions.defaults()); + } + + @Override + public SimpleConfig resolveWith(Config source, ConfigResolveOptions options) { + AbstractConfigValue resolved = ResolveContext.resolve(object, ((SimpleConfig) source).object, options); + + if (resolved == object) + return this; + else + return new SimpleConfig((AbstractConfigObject) resolved); + } + + private ConfigValue hasPathPeek(String pathExpression) { + Path path = Path.newPath(pathExpression); + ConfigValue peeked; + try { + peeked = object.peekPath(path); + } catch (ConfigException.NotResolved e) { + throw ConfigImpl.improveNotResolved(path, e); + } + return peeked; + } + + @Override + public boolean hasPath(String pathExpression) { + ConfigValue peeked = hasPathPeek(pathExpression); + return peeked != null && peeked.valueType() != ConfigValueType.NULL; + } + + @Override + public boolean hasPathOrNull(String path) { + ConfigValue peeked = hasPathPeek(path); + return peeked != null; + } + + @Override + public boolean isEmpty() { + return object.isEmpty(); + } + + private static void findPaths(Set> entries, Path parent, + AbstractConfigObject obj) { + for (Map.Entry entry : obj.entrySet()) { + String elem = entry.getKey(); + ConfigValue v = entry.getValue(); + Path path = Path.newKey(elem); + if (parent != null) + path = path.prepend(parent); + if (v instanceof AbstractConfigObject) { + findPaths(entries, path, (AbstractConfigObject) v); + } else if (v instanceof ConfigNull) { + // nothing; nulls are conceptually not in a Config + } else { + entries.add(new AbstractMap.SimpleImmutableEntry(path.render(), v)); + } + } + } + + @Override + public Set> entrySet() { + Set> entries = new HashSet>(); + findPaths(entries, null, object); + return entries; + } + + static private AbstractConfigValue throwIfNull(AbstractConfigValue v, ConfigValueType expected, Path originalPath) { + if (v.valueType() == ConfigValueType.NULL) + throw new ConfigException.Null(v.origin(), originalPath.render(), + expected != null ? expected.name() : null); + else + return v; + } + + static private AbstractConfigValue findKey(AbstractConfigObject self, String key, + ConfigValueType expected, Path originalPath) { + return throwIfNull(findKeyOrNull(self, key, expected, originalPath), expected, originalPath); + } + + static private AbstractConfigValue findKeyOrNull(AbstractConfigObject self, String key, + ConfigValueType expected, Path originalPath) { + AbstractConfigValue v = self.peekAssumingResolved(key, originalPath); + if (v == null) + throw new ConfigException.Missing(originalPath.render()); + + if (expected != null) + v = DefaultTransformer.transform(v, expected); + + if (expected != null && (v.valueType() != expected && v.valueType() != ConfigValueType.NULL)) + throw new ConfigException.WrongType(v.origin(), originalPath.render(), expected.name(), + v.valueType().name()); + else + return v; + } + + static private AbstractConfigValue findOrNull(AbstractConfigObject self, Path path, + ConfigValueType expected, Path originalPath) { + try { + String key = path.first(); + Path next = path.remainder(); + if (next == null) { + return findKeyOrNull(self, key, expected, originalPath); + } else { + AbstractConfigObject o = (AbstractConfigObject) findKey(self, key, + ConfigValueType.OBJECT, + originalPath.subPath(0, originalPath.length() - next.length())); + assert (o != null); // missing was supposed to throw + return findOrNull(o, next, expected, originalPath); + } + } catch (ConfigException.NotResolved e) { + throw ConfigImpl.improveNotResolved(path, e); + } + } + + AbstractConfigValue find(Path pathExpression, ConfigValueType expected, Path originalPath) { + return throwIfNull(findOrNull(object, pathExpression, expected, originalPath), expected, originalPath); + } + + AbstractConfigValue find(String pathExpression, ConfigValueType expected) { + Path path = Path.newPath(pathExpression); + return find(path, expected, path); + } + + private AbstractConfigValue findOrNull(Path pathExpression, ConfigValueType expected, Path originalPath) { + return findOrNull(object, pathExpression, expected, originalPath); + } + + private AbstractConfigValue findOrNull(String pathExpression, ConfigValueType expected) { + Path path = Path.newPath(pathExpression); + return findOrNull(path, expected, path); + } + + @Override + public AbstractConfigValue getValue(String path) { + return find(path, null); + } + + @Override + public boolean getIsNull(String path) { + AbstractConfigValue v = findOrNull(path, null); + return (v.valueType() == ConfigValueType.NULL); + } + + @Override + public boolean getBoolean(String path) { + ConfigValue v = find(path, ConfigValueType.BOOLEAN); + return (Boolean) v.unwrapped(); + } + + private ConfigNumber getConfigNumber(String path) { + ConfigValue v = find(path, ConfigValueType.NUMBER); + return (ConfigNumber) v; + } + + @Override + public Number getNumber(String path) { + return getConfigNumber(path).unwrapped(); + } + + @Override + public int getInt(String path) { + ConfigNumber n = getConfigNumber(path); + return n.intValueRangeChecked(path); + } + + @Override + public long getLong(String path) { + return getNumber(path).longValue(); + } + + @Override + public double getDouble(String path) { + return getNumber(path).doubleValue(); + } + + @Override + public String getString(String path) { + ConfigValue v = find(path, ConfigValueType.STRING); + return (String) v.unwrapped(); + } + + @Override + public > T getEnum(Class enumClass, String path) { + ConfigValue v = find(path, ConfigValueType.STRING); + return getEnumValue(path, enumClass, v); + } + + @Override + public ConfigList getList(String path) { + AbstractConfigValue v = find(path, ConfigValueType.LIST); + return (ConfigList) v; + } + + @Override + public AbstractConfigObject getObject(String path) { + AbstractConfigObject obj = (AbstractConfigObject) find(path, ConfigValueType.OBJECT); + return obj; + } + + @Override + public SimpleConfig getConfig(String path) { + return getObject(path).toConfig(); + } + + @Override + public Object getAnyRef(String path) { + ConfigValue v = find(path, null); + return v.unwrapped(); + } + + @Override + public Long getBytes(String path) { + Long size = null; + try { + size = getLong(path); + } catch (ConfigException.WrongType e) { + ConfigValue v = find(path, ConfigValueType.STRING); + size = parseBytes((String) v.unwrapped(), + v.origin(), path); + } + return size; + } + + @Override + public ConfigMemorySize getMemorySize(String path) { + return ConfigMemorySize.ofBytes(getBytes(path)); + } + + @Deprecated + @Override + public Long getMilliseconds(String path) { + return getDuration(path, TimeUnit.MILLISECONDS); + } + + @Deprecated + @Override + public Long getNanoseconds(String path) { + return getDuration(path, TimeUnit.NANOSECONDS); + } + + @Override + public long getDuration(String path, TimeUnit unit) { + ConfigValue v = find(path, ConfigValueType.STRING); + long result = unit.convert( + parseDuration((String) v.unwrapped(), v.origin(), path), + TimeUnit.NANOSECONDS); + return result; + } + + @Override + public Duration getDuration(String path) { + ConfigValue v = find(path, ConfigValueType.STRING); + long nanos = parseDuration((String) v.unwrapped(), v.origin(), path); + 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 List getHomogeneousUnwrappedList(String path, + ConfigValueType expected) { + List l = new ArrayList(); + List list = getList(path); + for (ConfigValue cv : list) { + // variance would be nice, but stupid cast will do + AbstractConfigValue v = (AbstractConfigValue) cv; + if (expected != null) { + v = DefaultTransformer.transform(v, expected); + } + if (v.valueType() != expected) + throw new ConfigException.WrongType(v.origin(), path, + "list of " + expected.name(), "list of " + + v.valueType().name()); + l.add((T) v.unwrapped()); + } + return l; + } + + @Override + public List getBooleanList(String path) { + return getHomogeneousUnwrappedList(path, ConfigValueType.BOOLEAN); + } + + @Override + public List getNumberList(String path) { + return getHomogeneousUnwrappedList(path, ConfigValueType.NUMBER); + } + + @Override + public List getIntList(String path) { + List l = new ArrayList(); + List numbers = getHomogeneousWrappedList(path, ConfigValueType.NUMBER); + for (AbstractConfigValue v : numbers) { + l.add(((ConfigNumber) v).intValueRangeChecked(path)); + } + return l; + } + + @Override + public List getLongList(String path) { + List l = new ArrayList(); + List numbers = getNumberList(path); + for (Number n : numbers) { + l.add(n.longValue()); + } + return l; + } + + @Override + public List getDoubleList(String path) { + List l = new ArrayList(); + List numbers = getNumberList(path); + for (Number n : numbers) { + l.add(n.doubleValue()); + } + return l; + } + + @Override + public List getStringList(String path) { + return getHomogeneousUnwrappedList(path, ConfigValueType.STRING); + } + + @Override + public > List getEnumList(Class enumClass, String path) { + List enumNames = getHomogeneousWrappedList(path, ConfigValueType.STRING); + List enumList = new ArrayList(); + for (ConfigString enumName : enumNames) { + enumList.add(getEnumValue(path, enumClass, enumName)); + } + return enumList; + } + + private > T getEnumValue(String path, Class enumClass, ConfigValue enumConfigValue) { + String enumName = (String) enumConfigValue.unwrapped(); + try { + return Enum.valueOf(enumClass, enumName); + } catch (IllegalArgumentException e) { + List enumNames = new ArrayList(); + Enum[] enumConstants = enumClass.getEnumConstants(); + if (enumConstants != null) { + for (Enum enumConstant : enumConstants) { + enumNames.add(enumConstant.name()); + } + } + throw new ConfigException.BadValue( + enumConfigValue.origin(), path, + String.format("The enum class %s has no constant of the name '%s' (should be one of %s.)", + enumClass.getSimpleName(), enumName, enumNames)); + } + } + + @SuppressWarnings("unchecked") + private List getHomogeneousWrappedList( + String path, ConfigValueType expected) { + List l = new ArrayList(); + List list = getList(path); + for (ConfigValue cv : list) { + // variance would be nice, but stupid cast will do + AbstractConfigValue v = (AbstractConfigValue) cv; + if (expected != null) { + v = DefaultTransformer.transform(v, expected); + } + if (v.valueType() != expected) + throw new ConfigException.WrongType(v.origin(), path, + "list of " + expected.name(), "list of " + + v.valueType().name()); + l.add((T) v); + } + return l; + } + + @Override + public List getObjectList(String path) { + return getHomogeneousWrappedList(path, ConfigValueType.OBJECT); + } + + @Override + public List getConfigList(String path) { + List objects = getObjectList(path); + List l = new ArrayList(); + for (ConfigObject o : objects) { + l.add(o.toConfig()); + } + return l; + } + + @Override + public List getAnyRefList(String path) { + List l = new ArrayList(); + List list = getList(path); + for (ConfigValue v : list) { + l.add(v.unwrapped()); + } + return l; + } + + @Override + public List getBytesList(String path) { + List l = new ArrayList(); + List list = getList(path); + for (ConfigValue v : list) { + if (v.valueType() == ConfigValueType.NUMBER) { + l.add(((Number) v.unwrapped()).longValue()); + } else if (v.valueType() == ConfigValueType.STRING) { + String s = (String) v.unwrapped(); + Long n = parseBytes(s, v.origin(), path); + l.add(n); + } else { + throw new ConfigException.WrongType(v.origin(), path, + "memory size string or number of bytes", v.valueType() + .name()); + } + } + return l; + } + + @Override + public List getMemorySizeList(String path) { + List list = getBytesList(path); + List builder = new ArrayList(); + for (Long v : list) { + builder.add(ConfigMemorySize.ofBytes(v)); + } + return builder; + } + + @Override + public List getDurationList(String path, TimeUnit unit) { + List l = new ArrayList(); + List list = getList(path); + for (ConfigValue v : list) { + if (v.valueType() == ConfigValueType.NUMBER) { + Long n = unit.convert( + ((Number) v.unwrapped()).longValue(), + TimeUnit.MILLISECONDS); + l.add(n); + } else if (v.valueType() == ConfigValueType.STRING) { + String s = (String) v.unwrapped(); + Long n = unit.convert( + parseDuration(s, v.origin(), path), + TimeUnit.NANOSECONDS); + l.add(n); + } else { + throw new ConfigException.WrongType(v.origin(), path, + "duration string or number of milliseconds", + v.valueType().name()); + } + } + return l; + } + + @Override + public List getDurationList(String path) { + List l = getDurationList(path, TimeUnit.NANOSECONDS); + List builder = new ArrayList(l.size()); + for (Long value : l) { + builder.add(Duration.ofNanos(value)); + } + return builder; + } + + @Deprecated + @Override + public List getMillisecondsList(String path) { + return getDurationList(path, TimeUnit.MILLISECONDS); + } + + @Deprecated + @Override + public List getNanosecondsList(String path) { + return getDurationList(path, TimeUnit.NANOSECONDS); + } + + @Override + public AbstractConfigObject toFallbackValue() { + return object; + } + + @Override + public SimpleConfig withFallback(ConfigMergeable other) { + // this can return "this" if the withFallback doesn't need a new + // ConfigObject + return object.withFallback(other).toConfig(); + } + + @Override + public final boolean equals(Object other) { + if (other instanceof SimpleConfig) { + return object.equals(((SimpleConfig) other).object); + } else { + return false; + } + } + + @Override + public final int hashCode() { + // we do the "41*" just so our hash code won't match that of the + // underlying object. there's no real reason it can't match, but + // making it not match might catch some kinds of bug. + return 41 * object.hashCode(); + } + + @Override + public String toString() { + return "Config(" + object.toString() + ")"; + } + + private static String getUnits(String s) { + int i = s.length() - 1; + while (i >= 0) { + char c = s.charAt(i); + if (!Character.isLetter(c)) + break; + i -= 1; + } + 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. + * The purpose of this function is to implement the duration-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 nanoseconds + * @throws ConfigException + * if string is invalid + */ + public static long parseDuration(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())); + TimeUnit units = null; + + // 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 duration value '" + input + + "'"); + + if (unitString.length() > 2 && !unitString.endsWith("s")) + unitString = unitString + "s"; + + // note that this is deliberately case-sensitive + if (unitString.equals("") || unitString.equals("ms") || unitString.equals("millis") + || unitString.equals("milliseconds")) { + units = TimeUnit.MILLISECONDS; + } else if (unitString.equals("us") || unitString.equals("micros") || unitString.equals("microseconds")) { + units = TimeUnit.MICROSECONDS; + } else if (unitString.equals("ns") || unitString.equals("nanos") || unitString.equals("nanoseconds")) { + units = TimeUnit.NANOSECONDS; + } else if (unitString.equals("d") || unitString.equals("days")) { + units = TimeUnit.DAYS; + } else if (unitString.equals("h") || unitString.equals("hours")) { + units = TimeUnit.HOURS; + } else if (unitString.equals("s") || unitString.equals("seconds")) { + units = TimeUnit.SECONDS; + } else if (unitString.equals("m") || unitString.equals("minutes")) { + units = TimeUnit.MINUTES; + } else { + throw new ConfigException.BadValue(originForException, + pathForException, "Could not parse time unit '" + + originalUnitString + + "' (try ns, us, ms, s, m, h, d)"); + } + + try { + // if the string is purely digits, parse as an integer to avoid + // possible precision loss; + // otherwise as a double. + if (numberString.matches("[+-]?[0-9]+")) { + return units.toNanos(Long.parseLong(numberString)); + } else { + long nanosInUnit = units.toNanos(1); + return (long) (Double.parseDouble(numberString) * nanosInUnit); + } + } catch (NumberFormatException e) { + throw new ConfigException.BadValue(originForException, + pathForException, "Could not parse duration number '" + + numberString + "'"); + } + } + + private static enum MemoryUnit { + BYTES("", 1024, 0), + + KILOBYTES("kilo", 1000, 1), + MEGABYTES("mega", 1000, 2), + GIGABYTES("giga", 1000, 3), + TERABYTES("tera", 1000, 4), + PETABYTES("peta", 1000, 5), + EXABYTES("exa", 1000, 6), + ZETTABYTES("zetta", 1000, 7), + YOTTABYTES("yotta", 1000, 8), + + KIBIBYTES("kibi", 1024, 1), + MEBIBYTES("mebi", 1024, 2), + GIBIBYTES("gibi", 1024, 3), + TEBIBYTES("tebi", 1024, 4), + PEBIBYTES("pebi", 1024, 5), + EXBIBYTES("exbi", 1024, 6), + ZEBIBYTES("zebi", 1024, 7), + YOBIBYTES("yobi", 1024, 8); + + final String prefix; + final int powerOf; + final int power; + final BigInteger bytes; + + MemoryUnit(String prefix, int powerOf, int power) { + this.prefix = prefix; + this.powerOf = powerOf; + this.power = power; + this.bytes = BigInteger.valueOf(powerOf).pow(power); + } + + private static Map makeUnitsMap() { + Map map = new HashMap(); + for (MemoryUnit unit : MemoryUnit.values()) { + map.put(unit.prefix + "byte", unit); + map.put(unit.prefix + "bytes", unit); + if (unit.prefix.length() == 0) { + map.put("b", unit); + map.put("B", unit); + map.put("", unit); // no unit specified means bytes + } else { + String first = unit.prefix.substring(0, 1); + String firstUpper = first.toUpperCase(); + if (unit.powerOf == 1024) { + map.put(first, unit); // 512m + map.put(firstUpper, unit); // 512M + map.put(firstUpper + "i", unit); // 512Mi + map.put(firstUpper + "iB", unit); // 512MiB + } else if (unit.powerOf == 1000) { + if (unit.power == 1) { + map.put(first + "B", unit); // 512kB + } else { + map.put(firstUpper + "B", unit); // 512MB + } + } else { + throw new RuntimeException("broken MemoryUnit enum"); + } + } + } + return map; + } + + private static Map unitsMap = makeUnitsMap(); + + static MemoryUnit parseUnit(String unit) { + return unitsMap.get(unit); + } + } + + /** + * Parses a size-in-bytes string. If no units are specified in the string, + * it is assumed to be in bytes. The returned value is in bytes. The purpose + * of this function is to implement the size-in-bytes-related methods in the + * Config interface. + * + * @param input + * the string to parse + * @param originForException + * origin of the value being parsed + * @param pathForException + * path to include in exceptions + * @return size in bytes + * @throws ConfigException + * if string is invalid + */ + public static long parseBytes(String input, ConfigOrigin originForException, + String pathForException) { + String s = ConfigImplUtil.unicodeTrim(input); + String unitString = getUnits(s); + String numberString = ConfigImplUtil.unicodeTrim(s.substring(0, + s.length() - unitString.length())); + + // 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 size-in-bytes value '" + + input + "'"); + + MemoryUnit units = MemoryUnit.parseUnit(unitString); + + if (units == null) { + throw new ConfigException.BadValue(originForException, pathForException, + "Could not parse size-in-bytes unit '" + unitString + + "' (try k, K, kB, KiB, kilobytes, kibibytes)"); + } + + try { + BigInteger result; + // if the string is purely digits, parse as an integer to avoid + // possible precision loss; otherwise as a double. + if (numberString.matches("[0-9]+")) { + result = units.bytes.multiply(new BigInteger(numberString)); + } else { + BigDecimal resultDecimal = (new BigDecimal(units.bytes)).multiply(new BigDecimal(numberString)); + result = resultDecimal.toBigInteger(); + } + if (result.bitLength() < 64) + return result.longValue(); + else + throw new ConfigException.BadValue(originForException, pathForException, + "size-in-bytes value is out of range for a 64-bit long: '" + input + "'"); + } catch (NumberFormatException e) { + throw new ConfigException.BadValue(originForException, pathForException, + "Could not parse size-in-bytes number '" + numberString + "'"); + } + } + + private AbstractConfigValue peekPath(Path path) { + return root().peekPath(path); + } + + private static void addProblem(List accumulator, Path path, + ConfigOrigin origin, String problem) { + accumulator.add(new ConfigException.ValidationProblem(path.render(), origin, problem)); + } + + private static String getDesc(ConfigValueType type) { + return type.name().toLowerCase(); + } + + private static String getDesc(ConfigValue refValue) { + if (refValue instanceof AbstractConfigObject) { + AbstractConfigObject obj = (AbstractConfigObject) refValue; + if (!obj.isEmpty()) + return "object with keys " + obj.keySet(); + else + return getDesc(refValue.valueType()); + } else { + return getDesc(refValue.valueType()); + } + } + + private static void addMissing(List accumulator, + String refDesc, Path path, ConfigOrigin origin) { + addProblem(accumulator, path, origin, "No setting at '" + path.render() + "', expecting: " + + refDesc); + } + + private static void addMissing(List accumulator, + ConfigValue refValue, Path path, ConfigOrigin origin) { + addMissing(accumulator, getDesc(refValue), path, origin); + } + + // JavaBean stuff uses this + static void addMissing(List accumulator, + ConfigValueType refType, Path path, ConfigOrigin origin) { + addMissing(accumulator, getDesc(refType), path, origin); + } + + private static void addWrongType(List accumulator, + String refDesc, AbstractConfigValue actual, Path path) { + addProblem(accumulator, path, actual.origin(), "Wrong value type at '" + path.render() + + "', expecting: " + refDesc + " but got: " + + getDesc(actual)); + } + + private static void addWrongType(List accumulator, + ConfigValue refValue, AbstractConfigValue actual, Path path) { + addWrongType(accumulator, getDesc(refValue), actual, path); + } + + private static void addWrongType(List accumulator, + ConfigValueType refType, AbstractConfigValue actual, Path path) { + addWrongType(accumulator, getDesc(refType), actual, path); + } + + private static boolean couldBeNull(AbstractConfigValue v) { + return DefaultTransformer.transform(v, ConfigValueType.NULL) + .valueType() == ConfigValueType.NULL; + } + + private static boolean haveCompatibleTypes(ConfigValue reference, AbstractConfigValue value) { + if (couldBeNull((AbstractConfigValue) reference)) { + // we allow any setting to be null + return true; + } else { + return haveCompatibleTypes(reference.valueType(), value); + } + } + + private static boolean haveCompatibleTypes(ConfigValueType referenceType, AbstractConfigValue value) { + if (referenceType == ConfigValueType.NULL || couldBeNull(value)) { + // we allow any setting to be null + return true; + } else if (referenceType == ConfigValueType.OBJECT) { + if (value instanceof AbstractConfigObject) { + return true; + } else { + return false; + } + } else if (referenceType == ConfigValueType.LIST) { + // objects may be convertible to lists if they have numeric keys + if (value instanceof SimpleConfigList || value instanceof SimpleConfigObject) { + return true; + } else { + return false; + } + } else if (referenceType == ConfigValueType.STRING) { + // assume a string could be gotten as any non-collection type; + // allows things like getMilliseconds including domain-specific + // interpretations of strings + return true; + } else if (value instanceof ConfigString) { + // assume a string could be gotten as any non-collection type + return true; + } else { + if (referenceType == value.valueType()) { + return true; + } else { + return false; + } + } + } + + // path is null if we're at the root + private static void checkValidObject(Path path, AbstractConfigObject reference, + AbstractConfigObject value, + List accumulator) { + for (Map.Entry entry : reference.entrySet()) { + String key = entry.getKey(); + + Path childPath; + if (path != null) + childPath = Path.newKey(key).prepend(path); + else + childPath = Path.newKey(key); + + AbstractConfigValue v = value.get(key); + if (v == null) { + addMissing(accumulator, entry.getValue(), childPath, value.origin()); + } else { + checkValid(childPath, entry.getValue(), v, accumulator); + } + } + } + + private static void checkListCompatibility(Path path, SimpleConfigList listRef, + SimpleConfigList listValue, List accumulator) { + if (listRef.isEmpty() || listValue.isEmpty()) { + // can't verify type, leave alone + } else { + AbstractConfigValue refElement = listRef.get(0); + for (ConfigValue elem : listValue) { + AbstractConfigValue e = (AbstractConfigValue) elem; + if (!haveCompatibleTypes(refElement, e)) { + addProblem(accumulator, path, e.origin(), "List at '" + path.render() + + "' contains wrong value type, expecting list of " + + getDesc(refElement) + " but got element of type " + getDesc(e)); + // don't add a problem for every last array element + break; + } + } + } + } + + // Used by the JavaBean-based validator + static void checkValid(Path path, ConfigValueType referenceType, AbstractConfigValue value, + List accumulator) { + if (haveCompatibleTypes(referenceType, value)) { + if (referenceType == ConfigValueType.LIST && value instanceof SimpleConfigObject) { + // attempt conversion of indexed object to list + AbstractConfigValue listValue = DefaultTransformer.transform(value, + ConfigValueType.LIST); + if (!(listValue instanceof SimpleConfigList)) + addWrongType(accumulator, referenceType, value, path); + } + } else { + addWrongType(accumulator, referenceType, value, path); + } + } + + private static void checkValid(Path path, ConfigValue reference, AbstractConfigValue value, + List accumulator) { + // Unmergeable is supposed to be impossible to encounter in here + // because we check for resolve status up front. + + if (haveCompatibleTypes(reference, value)) { + if (reference instanceof AbstractConfigObject && value instanceof AbstractConfigObject) { + checkValidObject(path, (AbstractConfigObject) reference, + (AbstractConfigObject) value, accumulator); + } else if (reference instanceof SimpleConfigList && value instanceof SimpleConfigList) { + SimpleConfigList listRef = (SimpleConfigList) reference; + SimpleConfigList listValue = (SimpleConfigList) value; + checkListCompatibility(path, listRef, listValue, accumulator); + } else if (reference instanceof SimpleConfigList && value instanceof SimpleConfigObject) { + // attempt conversion of indexed object to list + SimpleConfigList listRef = (SimpleConfigList) reference; + AbstractConfigValue listValue = DefaultTransformer.transform(value, + ConfigValueType.LIST); + if (listValue instanceof SimpleConfigList) + checkListCompatibility(path, listRef, (SimpleConfigList) listValue, accumulator); + else + addWrongType(accumulator, reference, value, path); + } + } else { + addWrongType(accumulator, reference, value, path); + } + } + + @Override + public boolean isResolved() { + return root().resolveStatus() == ResolveStatus.RESOLVED; + } + + @Override + public void checkValid(Config reference, String... restrictToPaths) { + SimpleConfig ref = (SimpleConfig) reference; + + // unresolved reference config is a bug in the caller of checkValid + if (ref.root().resolveStatus() != ResolveStatus.RESOLVED) + throw new ConfigException.BugOrBroken( + "do not call checkValid() with an unresolved reference config, call Config#resolve(), see Config#resolve() API docs"); + + // unresolved config under validation is a bug in something, + // NotResolved is a more specific subclass of BugOrBroken + if (root().resolveStatus() != ResolveStatus.RESOLVED) + throw new ConfigException.NotResolved( + "need to Config#resolve() each config before using it, see the API docs for Config#resolve()"); + + // Now we know that both reference and this config are resolved + + List problems = new ArrayList(); + + if (restrictToPaths.length == 0) { + checkValidObject(null, ref.root(), root(), problems); + } else { + for (String p : restrictToPaths) { + Path path = Path.newPath(p); + AbstractConfigValue refValue = ref.peekPath(path); + if (refValue != null) { + AbstractConfigValue child = peekPath(path); + if (child != null) { + checkValid(path, refValue, child, problems); + } else { + addMissing(problems, refValue, path, origin()); + } + } + } + } + + if (!problems.isEmpty()) { + throw new ConfigException.ValidationFailed(problems); + } + } + + @Override + public SimpleConfig withOnlyPath(String pathExpression) { + Path path = Path.newPath(pathExpression); + return new SimpleConfig(root().withOnlyPath(path)); + } + + @Override + public SimpleConfig withoutPath(String pathExpression) { + Path path = Path.newPath(pathExpression); + return new SimpleConfig(root().withoutPath(path)); + } + + @Override + public SimpleConfig withValue(String pathExpression, ConfigValue v) { + Path path = Path.newPath(pathExpression); + return new SimpleConfig(root().withValue(path, v)); + } + + SimpleConfig atKey(ConfigOrigin origin, String key) { + return root().atKey(origin, key); + } + + @Override + public SimpleConfig atKey(String key) { + return root().atKey(key); + } + + @Override + public Config atPath(String path) { + return root().atPath(path); + } + + // serialization all goes through SerializedConfigValue + private Object writeReplace() throws ObjectStreamException { + return new SerializedConfigValue(this); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigDocument.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigDocument.java new file mode 100644 index 0000000..bc262ea --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigDocument.java @@ -0,0 +1,66 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigParseOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigRenderOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; +import com.drtshock.playervaults.lib.com.typesafe.config.parser.ConfigDocument; + +import java.io.StringReader; +import java.util.Iterator; + +final class SimpleConfigDocument implements ConfigDocument { + private ConfigNodeRoot configNodeTree; + private ConfigParseOptions parseOptions; + + SimpleConfigDocument(ConfigNodeRoot parsedNode, ConfigParseOptions parseOptions) { + configNodeTree = parsedNode; + this.parseOptions = parseOptions; + } + + @Override + public ConfigDocument withValueText(String path, String newValue) { + if (newValue == null) + throw new ConfigException.BugOrBroken("null value for " + path + " passed to withValueText"); + SimpleConfigOrigin origin = SimpleConfigOrigin.newSimple("single value parsing"); + StringReader reader = new StringReader(newValue); + Iterator tokens = Tokenizer.tokenize(origin, reader, parseOptions.getSyntax()); + AbstractConfigNodeValue parsedValue = ConfigDocumentParser.parseValue(tokens, origin, parseOptions); + reader.close(); + + return new SimpleConfigDocument(configNodeTree.setValue(path, parsedValue, parseOptions.getSyntax()), parseOptions); + } + + @Override + public ConfigDocument withValue(String path, ConfigValue newValue) { + if (newValue == null) + throw new ConfigException.BugOrBroken("null value for " + path + " passed to withValue"); + ConfigRenderOptions options = ConfigRenderOptions.defaults(); + options = options.setOriginComments(false); + return withValueText(path, newValue.render(options).trim()); + } + + @Override + public ConfigDocument withoutPath(String path) { + return new SimpleConfigDocument(configNodeTree.setValue(path, null, parseOptions.getSyntax()), parseOptions); + } + + @Override + public boolean hasPath(String path) { + return configNodeTree.hasValue(path); + } + + public String render() { + return configNodeTree.render(); + } + + @Override + public boolean equals(Object other) { + return other instanceof ConfigDocument && render().equals(((ConfigDocument) other).render()); + } + + @Override + public int hashCode() { + return render().hashCode(); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigList.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigList.java new file mode 100644 index 0000000..5007d9c --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigList.java @@ -0,0 +1,464 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigList; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigRenderOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +final class SimpleConfigList extends AbstractConfigValue implements ConfigList, Container, Serializable { + + private static final long serialVersionUID = 2L; + + final private List value; + final private boolean resolved; + + SimpleConfigList(ConfigOrigin origin, List value) { + this(origin, value, ResolveStatus + .fromValues(value)); + } + + SimpleConfigList(ConfigOrigin origin, List value, + ResolveStatus status) { + super(origin); + this.value = value; + this.resolved = status == ResolveStatus.RESOLVED; + + // kind of an expensive debug check (makes this constructor pointless) + if (status != ResolveStatus.fromValues(value)) + throw new ConfigException.BugOrBroken( + "SimpleConfigList created with wrong resolve status: " + this); + } + + @Override + public ConfigValueType valueType() { + return ConfigValueType.LIST; + } + + @Override + public List unwrapped() { + List list = new ArrayList(); + for (AbstractConfigValue v : value) { + list.add(v.unwrapped()); + } + return list; + } + + @Override + ResolveStatus resolveStatus() { + return ResolveStatus.fromBoolean(resolved); + } + + @Override + public SimpleConfigList replaceChild(AbstractConfigValue child, AbstractConfigValue replacement) { + List newList = replaceChildInList(value, child, replacement); + if (newList == null) { + return null; + } else { + // we use the constructor flavor that will recompute the resolve + // status + return new SimpleConfigList(origin(), newList); + } + } + + @Override + public boolean hasDescendant(AbstractConfigValue descendant) { + return hasDescendantInList(value, descendant); + } + + private SimpleConfigList modify(NoExceptionsModifier modifier, ResolveStatus newResolveStatus) { + try { + return modifyMayThrow(modifier, newResolveStatus); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new ConfigException.BugOrBroken("unexpected checked exception", e); + } + } + + private SimpleConfigList modifyMayThrow(Modifier modifier, ResolveStatus newResolveStatus) + throws Exception { + // lazy-create for optimization + List changed = null; + int i = 0; + for (AbstractConfigValue v : value) { + AbstractConfigValue modified = modifier.modifyChildMayThrow(null /* key */, v); + + // lazy-create the new list if required + if (changed == null && modified != v) { + changed = new ArrayList(); + for (int j = 0; j < i; ++j) { + changed.add(value.get(j)); + } + } + + // once the new list is created, all elements + // have to go in it. if modifyChild returned + // null, we drop that element. + if (changed != null && modified != null) { + changed.add(modified); + } + + i += 1; + } + + if (changed != null) { + if (newResolveStatus != null) { + return new SimpleConfigList(origin(), changed, newResolveStatus); + } else { + return new SimpleConfigList(origin(), changed); + } + } else { + return this; + } + } + + private static class ResolveModifier implements Modifier { + ResolveContext context; + final ResolveSource source; + ResolveModifier(ResolveContext context, ResolveSource source) { + this.context = context; + this.source = source; + } + + @Override + public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v) + throws NotPossibleToResolve { + ResolveResult result = context.resolve(v, source); + context = result.context; + return result.value; + } + } + + @Override + ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) + throws NotPossibleToResolve { + if (resolved) + return ResolveResult.make(context, this); + + if (context.isRestrictedToChild()) { + // if a list restricts to a child path, then it has no child paths, + // so nothing to do. + return ResolveResult.make(context, this); + } else { + try { + ResolveModifier modifier = new ResolveModifier(context, source.pushParent(this)); + SimpleConfigList value = modifyMayThrow(modifier, context.options().getAllowUnresolved() ? null : ResolveStatus.RESOLVED); + return ResolveResult.make(modifier.context, value); + } catch (NotPossibleToResolve e) { + throw e; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new ConfigException.BugOrBroken("unexpected checked exception", e); + } + } + } + + @Override + SimpleConfigList relativized(final Path prefix) { + return modify(new NoExceptionsModifier() { + @Override + public AbstractConfigValue modifyChild(String key, AbstractConfigValue v) { + return v.relativized(prefix); + } + + }, resolveStatus()); + } + + @Override + protected boolean canEqual(Object other) { + return other instanceof SimpleConfigList; + } + + @Override + public boolean equals(Object other) { + // note that "origin" is deliberately NOT part of equality + if (other instanceof SimpleConfigList) { + // optimization to avoid unwrapped() for two ConfigList + return canEqual(other) + && (value == ((SimpleConfigList) other).value || value.equals(((SimpleConfigList) other).value)); + } else { + return false; + } + } + + @Override + public int hashCode() { + // note that "origin" is deliberately NOT part of equality + return value.hashCode(); + } + + @Override + protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { + if (value.isEmpty()) { + sb.append("[]"); + } else { + sb.append("["); + if (options.getFormatted()) + sb.append('\n'); + for (AbstractConfigValue v : value) { + if (options.getOriginComments()) { + String[] lines = v.origin().description().split("\n"); + for (String l : lines) { + indent(sb, indent + 1, options); + sb.append('#'); + if (!l.isEmpty()) + sb.append(' '); + sb.append(l); + sb.append("\n"); + } + } + if (options.getComments()) { + for (String comment : v.origin().comments()) { + indent(sb, indent + 1, options); + sb.append("# "); + sb.append(comment); + sb.append("\n"); + } + } + indent(sb, indent + 1, options); + + v.render(sb, indent + 1, atRoot, options); + sb.append(","); + if (options.getFormatted()) + sb.append('\n'); + } + sb.setLength(sb.length() - 1); // chop or newline + if (options.getFormatted()) { + sb.setLength(sb.length() - 1); // also chop comma + sb.append('\n'); + indent(sb, indent, options); + } + sb.append("]"); + } + } + + @Override + public boolean contains(Object o) { + return value.contains(o); + } + + @Override + public boolean containsAll(Collection c) { + return value.containsAll(c); + } + + @Override + public AbstractConfigValue get(int index) { + return value.get(index); + } + + @Override + public int indexOf(Object o) { + return value.indexOf(o); + } + + @Override + public boolean isEmpty() { + return value.isEmpty(); + } + + @Override + public Iterator iterator() { + final Iterator i = value.iterator(); + + return new Iterator() { + @Override + public boolean hasNext() { + return i.hasNext(); + } + + @Override + public ConfigValue next() { + return i.next(); + } + + @Override + public void remove() { + throw weAreImmutable("iterator().remove"); + } + }; + } + + @Override + public int lastIndexOf(Object o) { + return value.lastIndexOf(o); + } + + private static ListIterator wrapListIterator( + final ListIterator i) { + return new ListIterator() { + @Override + public boolean hasNext() { + return i.hasNext(); + } + + @Override + public ConfigValue next() { + return i.next(); + } + + @Override + public void remove() { + throw weAreImmutable("listIterator().remove"); + } + + @Override + public void add(ConfigValue arg0) { + throw weAreImmutable("listIterator().add"); + } + + @Override + public boolean hasPrevious() { + return i.hasPrevious(); + } + + @Override + public int nextIndex() { + return i.nextIndex(); + } + + @Override + public ConfigValue previous() { + return i.previous(); + } + + @Override + public int previousIndex() { + return i.previousIndex(); + } + + @Override + public void set(ConfigValue arg0) { + throw weAreImmutable("listIterator().set"); + } + }; + } + + @Override + public ListIterator listIterator() { + return wrapListIterator(value.listIterator()); + } + + @Override + public ListIterator listIterator(int index) { + return wrapListIterator(value.listIterator(index)); + } + + @Override + public int size() { + return value.size(); + } + + @Override + public List subList(int fromIndex, int toIndex) { + List list = new ArrayList(); + // yay bloat caused by lack of type variance + for (AbstractConfigValue v : value.subList(fromIndex, toIndex)) { + list.add(v); + } + return list; + } + + @Override + public Object[] toArray() { + return value.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return value.toArray(a); + } + + private static UnsupportedOperationException weAreImmutable(String method) { + return new UnsupportedOperationException( + "ConfigList is immutable, you can't call List.'" + method + "'"); + } + + @Override + public boolean add(ConfigValue e) { + throw weAreImmutable("add"); + } + + @Override + public void add(int index, ConfigValue element) { + throw weAreImmutable("add"); + } + + @Override + public boolean addAll(Collection c) { + throw weAreImmutable("addAll"); + } + + @Override + public boolean addAll(int index, Collection c) { + throw weAreImmutable("addAll"); + } + + @Override + public void clear() { + throw weAreImmutable("clear"); + } + + @Override + public boolean remove(Object o) { + throw weAreImmutable("remove"); + } + + @Override + public ConfigValue remove(int index) { + throw weAreImmutable("remove"); + } + + @Override + public boolean removeAll(Collection c) { + throw weAreImmutable("removeAll"); + } + + @Override + public boolean retainAll(Collection c) { + throw weAreImmutable("retainAll"); + } + + @Override + public ConfigValue set(int index, ConfigValue element) { + throw weAreImmutable("set"); + } + + @Override + protected SimpleConfigList newCopy(ConfigOrigin newOrigin) { + return new SimpleConfigList(newOrigin, value); + } + + final SimpleConfigList concatenate(SimpleConfigList other) { + ConfigOrigin combinedOrigin = SimpleConfigOrigin.mergeOrigins(origin(), other.origin()); + List combined = new ArrayList(value.size() + + other.value.size()); + combined.addAll(value); + combined.addAll(other.value); + return new SimpleConfigList(combinedOrigin, combined); + } + + // serialization all goes through SerializedConfigValue + private Object writeReplace() throws ObjectStreamException { + return new SerializedConfigValue(this); + } + + @Override + public SimpleConfigList withOrigin(ConfigOrigin origin) { + return (SimpleConfigList) super.withOrigin(origin); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigObject.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigObject.java new file mode 100644 index 0000000..0e79c76 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigObject.java @@ -0,0 +1,671 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigObject; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigRenderOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; + +final class SimpleConfigObject extends AbstractConfigObject implements Serializable { + + private static final long serialVersionUID = 2L; + + // this map should never be modified - assume immutable + final private Map value; + final private boolean resolved; + final private boolean ignoresFallbacks; + + SimpleConfigObject(ConfigOrigin origin, + Map value, ResolveStatus status, + boolean ignoresFallbacks) { + super(origin); + if (value == null) + throw new ConfigException.BugOrBroken( + "creating config object with null map"); + this.value = value; + this.resolved = status == ResolveStatus.RESOLVED; + this.ignoresFallbacks = ignoresFallbacks; + + // Kind of an expensive debug check. Comment out? + if (status != ResolveStatus.fromValues(value.values())) + throw new ConfigException.BugOrBroken("Wrong resolved status on " + this); + } + + SimpleConfigObject(ConfigOrigin origin, + Map value) { + this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */); + } + + @Override + public SimpleConfigObject withOnlyKey(String key) { + return withOnlyPath(Path.newKey(key)); + } + + @Override + public SimpleConfigObject withoutKey(String key) { + return withoutPath(Path.newKey(key)); + } + + // gets the object with only the path if the path + // exists, otherwise null if it doesn't. this ensures + // that if we have { a : { b : 42 } } and do + // withOnlyPath("a.b.c") that we don't keep an empty + // "a" object. + @Override + protected SimpleConfigObject withOnlyPathOrNull(Path path) { + String key = path.first(); + Path next = path.remainder(); + AbstractConfigValue v = value.get(key); + + if (next != null) { + if (v != null && (v instanceof AbstractConfigObject)) { + v = ((AbstractConfigObject) v).withOnlyPathOrNull(next); + } else { + // if the path has more elements but we don't have an object, + // then the rest of the path does not exist. + v = null; + } + } + + if (v == null) { + return null; + } else { + return new SimpleConfigObject(origin(), Collections.singletonMap(key, v), + v.resolveStatus(), ignoresFallbacks); + } + } + + @Override + SimpleConfigObject withOnlyPath(Path path) { + SimpleConfigObject o = withOnlyPathOrNull(path); + if (o == null) { + return new SimpleConfigObject(origin(), + Collections. emptyMap(), ResolveStatus.RESOLVED, + ignoresFallbacks); + } else { + return o; + } + } + + @Override + SimpleConfigObject withoutPath(Path path) { + String key = path.first(); + Path next = path.remainder(); + AbstractConfigValue v = value.get(key); + + if (v != null && next != null && v instanceof AbstractConfigObject) { + v = ((AbstractConfigObject) v).withoutPath(next); + Map updated = new HashMap( + value); + updated.put(key, v); + return new SimpleConfigObject(origin(), updated, ResolveStatus.fromValues(updated + .values()), ignoresFallbacks); + } else if (next != null || v == null) { + // can't descend, nothing to remove + return this; + } else { + Map smaller = new HashMap( + value.size() - 1); + for (Map.Entry old : value.entrySet()) { + if (!old.getKey().equals(key)) + smaller.put(old.getKey(), old.getValue()); + } + return new SimpleConfigObject(origin(), smaller, ResolveStatus.fromValues(smaller + .values()), ignoresFallbacks); + } + } + + @Override + public SimpleConfigObject withValue(String key, ConfigValue v) { + if (v == null) + throw new ConfigException.BugOrBroken( + "Trying to store null ConfigValue in a ConfigObject"); + + Map newMap; + if (value.isEmpty()) { + newMap = Collections.singletonMap(key, (AbstractConfigValue) v); + } else { + newMap = new HashMap(value); + newMap.put(key, (AbstractConfigValue) v); + } + + return new SimpleConfigObject(origin(), newMap, ResolveStatus.fromValues(newMap.values()), + ignoresFallbacks); + } + + @Override + SimpleConfigObject withValue(Path path, ConfigValue v) { + String key = path.first(); + Path next = path.remainder(); + + if (next == null) { + return withValue(key, v); + } else { + AbstractConfigValue child = value.get(key); + if (child != null && child instanceof AbstractConfigObject) { + // if we have an object, add to it + return withValue(key, ((AbstractConfigObject) child).withValue(next, v)); + } else { + // as soon as we have a non-object, replace it entirely + SimpleConfig subtree = ((AbstractConfigValue) v).atPath( + SimpleConfigOrigin.newSimple("withValue(" + next.render() + ")"), next); + return withValue(key, subtree.root()); + } + } + } + + @Override + protected AbstractConfigValue attemptPeekWithPartialResolve(String key) { + return value.get(key); + } + + private SimpleConfigObject newCopy(ResolveStatus newStatus, ConfigOrigin newOrigin, + boolean newIgnoresFallbacks) { + return new SimpleConfigObject(newOrigin, value, newStatus, newIgnoresFallbacks); + } + + @Override + protected SimpleConfigObject newCopy(ResolveStatus newStatus, ConfigOrigin newOrigin) { + return newCopy(newStatus, newOrigin, ignoresFallbacks); + } + + @Override + protected SimpleConfigObject withFallbacksIgnored() { + if (ignoresFallbacks) + return this; + else + return newCopy(resolveStatus(), origin(), true /* ignoresFallbacks */); + } + + @Override + ResolveStatus resolveStatus() { + return ResolveStatus.fromBoolean(resolved); + } + + @Override + public SimpleConfigObject replaceChild(AbstractConfigValue child, AbstractConfigValue replacement) { + HashMap newChildren = new HashMap(value); + for (Map.Entry old : newChildren.entrySet()) { + if (old.getValue() == child) { + if (replacement != null) + old.setValue(replacement); + else + newChildren.remove(old.getKey()); + + return new SimpleConfigObject(origin(), newChildren, ResolveStatus.fromValues(newChildren.values()), + ignoresFallbacks); + } + } + throw new ConfigException.BugOrBroken("SimpleConfigObject.replaceChild did not find " + child + " in " + this); + } + + @Override + public boolean hasDescendant(AbstractConfigValue descendant) { + for (AbstractConfigValue child : value.values()) { + if (child == descendant) + return true; + } + // now do the expensive search + for (AbstractConfigValue child : value.values()) { + if (child instanceof Container && ((Container) child).hasDescendant(descendant)) + return true; + } + + return false; + } + + @Override + protected boolean ignoresFallbacks() { + return ignoresFallbacks; + } + + @Override + public Map unwrapped() { + Map m = new HashMap(); + for (Map.Entry e : value.entrySet()) { + m.put(e.getKey(), e.getValue().unwrapped()); + } + return m; + } + + @Override + protected SimpleConfigObject mergedWithObject(AbstractConfigObject abstractFallback) { + requireNotIgnoringFallbacks(); + + if (!(abstractFallback instanceof SimpleConfigObject)) { + throw new ConfigException.BugOrBroken( + "should not be reached (merging non-SimpleConfigObject)"); + } + + SimpleConfigObject fallback = (SimpleConfigObject) abstractFallback; + + boolean changed = false; + boolean allResolved = true; + Map merged = new HashMap(); + Set allKeys = new HashSet(); + allKeys.addAll(this.keySet()); + allKeys.addAll(fallback.keySet()); + for (String key : allKeys) { + AbstractConfigValue first = this.value.get(key); + AbstractConfigValue second = fallback.value.get(key); + AbstractConfigValue kept; + if (first == null) + kept = second; + else if (second == null) + kept = first; + else + kept = first.withFallback(second); + + merged.put(key, kept); + + if (first != kept) + changed = true; + + if (kept.resolveStatus() == ResolveStatus.UNRESOLVED) + allResolved = false; + } + + ResolveStatus newResolveStatus = ResolveStatus.fromBoolean(allResolved); + boolean newIgnoresFallbacks = fallback.ignoresFallbacks(); + + if (changed) + return new SimpleConfigObject(mergeOrigins(this, fallback), merged, newResolveStatus, + newIgnoresFallbacks); + else if (newResolveStatus != resolveStatus() || newIgnoresFallbacks != ignoresFallbacks()) + return newCopy(newResolveStatus, origin(), newIgnoresFallbacks); + else + return this; + } + + private SimpleConfigObject modify(NoExceptionsModifier modifier) { + try { + return modifyMayThrow(modifier); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new ConfigException.BugOrBroken("unexpected checked exception", e); + } + } + + private SimpleConfigObject modifyMayThrow(Modifier modifier) throws Exception { + Map changes = null; + for (String k : keySet()) { + AbstractConfigValue v = value.get(k); + // "modified" may be null, which means remove the child; + // to do that we put null in the "changes" map. + AbstractConfigValue modified = modifier.modifyChildMayThrow(k, v); + if (modified != v) { + if (changes == null) + changes = new HashMap(); + changes.put(k, modified); + } + } + if (changes == null) { + return this; + } else { + Map modified = new HashMap(); + boolean sawUnresolved = false; + for (String k : keySet()) { + if (changes.containsKey(k)) { + AbstractConfigValue newValue = changes.get(k); + if (newValue != null) { + modified.put(k, newValue); + if (newValue.resolveStatus() == ResolveStatus.UNRESOLVED) + sawUnresolved = true; + } else { + // remove this child; don't put it in the new map. + } + } else { + AbstractConfigValue newValue = value.get(k); + modified.put(k, newValue); + if (newValue.resolveStatus() == ResolveStatus.UNRESOLVED) + sawUnresolved = true; + } + } + return new SimpleConfigObject(origin(), modified, + sawUnresolved ? ResolveStatus.UNRESOLVED : ResolveStatus.RESOLVED, + ignoresFallbacks()); + } + } + + private static final class ResolveModifier implements Modifier { + + final Path originalRestrict; + ResolveContext context; + final ResolveSource source; + + ResolveModifier(ResolveContext context, ResolveSource source) { + this.context = context; + this.source = source; + originalRestrict = context.restrictToChild(); + } + + @Override + public AbstractConfigValue modifyChildMayThrow(String key, AbstractConfigValue v) throws NotPossibleToResolve { + if (context.isRestrictedToChild()) { + if (key.equals(context.restrictToChild().first())) { + Path remainder = context.restrictToChild().remainder(); + if (remainder != null) { + ResolveResult result = context.restrict(remainder).resolve(v, + source); + context = result.context.unrestricted().restrict(originalRestrict); + return result.value; + } else { + // we don't want to resolve the leaf child. + return v; + } + } else { + // not in the restrictToChild path + return v; + } + } else { + // no restrictToChild, resolve everything + ResolveResult result = context.unrestricted().resolve(v, source); + context = result.context.unrestricted().restrict(originalRestrict); + return result.value; + } + } + + } + + @Override + ResolveResult resolveSubstitutions(ResolveContext context, ResolveSource source) + throws NotPossibleToResolve { + if (resolveStatus() == ResolveStatus.RESOLVED) + return ResolveResult.make(context, this); + + final ResolveSource sourceWithParent = source.pushParent(this); + + try { + ResolveModifier modifier = new ResolveModifier(context, sourceWithParent); + + AbstractConfigValue value = modifyMayThrow(modifier); + return ResolveResult.make(modifier.context, value).asObjectResult(); + } catch (NotPossibleToResolve e) { + throw e; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new ConfigException.BugOrBroken("unexpected checked exception", e); + } + } + + @Override + SimpleConfigObject relativized(final Path prefix) { + return modify(new NoExceptionsModifier() { + + @Override + public AbstractConfigValue modifyChild(String key, AbstractConfigValue v) { + return v.relativized(prefix); + } + + }); + } + + // this is only Serializable to chill out a findbugs warning + static final private class RenderComparator implements java.util.Comparator, Serializable { + private static final long serialVersionUID = 1L; + + private static boolean isAllDigits(String s) { + int length = s.length(); + + // empty string doesn't count as a number + if (length == 0) + return false; + + for (int i = 0; i < length; ++i) { + char c = s.charAt(i); + + if (Character.isDigit(c)) + continue; + else + return false; + } + return true; + } + + // This is supposed to sort numbers before strings, + // and sort the numbers numerically. The point is + // to make objects which are really list-like + // (numeric indices) appear in order. + @Override + public int compare(String a, String b) { + boolean aDigits = isAllDigits(a); + boolean bDigits = isAllDigits(b); + if (aDigits && bDigits) { + return Integer.compare(Integer.parseInt(a), Integer.parseInt(b)); + } else if (aDigits) { + return -1; + } else if (bDigits) { + return 1; + } else { + return a.compareTo(b); + } + } + } + + @Override + protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { + if (isEmpty()) { + sb.append("{}"); + } else { + boolean outerBraces = options.getJson() || !atRoot; + + int innerIndent; + if (outerBraces) { + innerIndent = indent + 1; + sb.append("{"); + + if (options.getFormatted()) + sb.append('\n'); + } else { + innerIndent = indent; + } + + int separatorCount = 0; + String[] keys = keySet().toArray(new String[size()]); + Arrays.sort(keys, new RenderComparator()); + for (String k : keys) { + AbstractConfigValue v; + v = value.get(k); + + if (options.getOriginComments()) { + String[] lines = v.origin().description().split("\n"); + for (String l : lines) { + indent(sb, indent + 1, options); + sb.append('#'); + if (!l.isEmpty()) + sb.append(' '); + sb.append(l); + sb.append("\n"); + } + } + if (options.getComments()) { + for (String comment : v.origin().comments()) { + indent(sb, innerIndent, options); + sb.append("#"); + if (!comment.startsWith(" ")) + sb.append(' '); + sb.append(comment); + sb.append("\n"); + } + } + indent(sb, innerIndent, options); + v.render(sb, innerIndent, false /* atRoot */, k, options); + + if (options.getFormatted()) { + if (options.getJson()) { + sb.append(","); + separatorCount = 2; + } else { + separatorCount = 1; + } + sb.append('\n'); + } else { + sb.append(","); + separatorCount = 1; + } + } + // chop last commas/newlines + sb.setLength(sb.length() - separatorCount); + + if (outerBraces) { + if (options.getFormatted()) { + sb.append('\n'); // put a newline back + if (outerBraces) + indent(sb, indent, options); + } + sb.append("}"); + } + } + if (atRoot && options.getFormatted()) + sb.append('\n'); + } + + @Override + public AbstractConfigValue get(Object key) { + return value.get(key); + } + + private static boolean mapEquals(Map a, Map b) { + if (a == b) + return true; + + Set aKeys = a.keySet(); + Set bKeys = b.keySet(); + + if (!aKeys.equals(bKeys)) + return false; + + for (String key : aKeys) { + if (!a.get(key).equals(b.get(key))) + return false; + } + return true; + } + + private static int mapHash(Map m) { + // the keys have to be sorted, otherwise we could be equal + // to another map but have a different hashcode. + List keys = new ArrayList(); + keys.addAll(m.keySet()); + Collections.sort(keys); + + int valuesHash = 0; + for (String k : keys) { + valuesHash += m.get(k).hashCode(); + } + return 41 * (41 + keys.hashCode()) + valuesHash; + } + + @Override + protected boolean canEqual(Object other) { + return other instanceof ConfigObject; + } + + @Override + public boolean equals(Object other) { + // note that "origin" is deliberately NOT part of equality. + // neither are other "extras" like ignoresFallbacks or resolve status. + if (other instanceof ConfigObject) { + // optimization to avoid unwrapped() for two ConfigObject, + // which is what AbstractConfigValue does. + return canEqual(other) && mapEquals(this, ((ConfigObject) other)); + } else { + return false; + } + } + + @Override + public int hashCode() { + // note that "origin" is deliberately NOT part of equality + // neither are other "extras" like ignoresFallbacks or resolve status. + return mapHash(this); + } + + @Override + public boolean containsKey(Object key) { + return value.containsKey(key); + } + + @Override + public Set keySet() { + return value.keySet(); + } + + @Override + public boolean containsValue(Object v) { + return value.containsValue(v); + } + + @Override + public Set> entrySet() { + // total bloat just to work around lack of type variance + + HashSet> entries = new HashSet>(); + for (Map.Entry e : value.entrySet()) { + entries.add(new AbstractMap.SimpleImmutableEntry( + e.getKey(), e + .getValue())); + } + return entries; + } + + @Override + public boolean isEmpty() { + return value.isEmpty(); + } + + @Override + public int size() { + return value.size(); + } + + @Override + public Collection values() { + return new HashSet(value.values()); + } + + final private static String EMPTY_NAME = "empty config"; + final private static SimpleConfigObject emptyInstance = empty(SimpleConfigOrigin + .newSimple(EMPTY_NAME)); + + final static SimpleConfigObject empty() { + return emptyInstance; + } + + final static SimpleConfigObject empty(ConfigOrigin origin) { + if (origin == null) + return empty(); + else + return new SimpleConfigObject(origin, + Collections. emptyMap()); + } + + final static SimpleConfigObject emptyMissing(ConfigOrigin baseOrigin) { + return new SimpleConfigObject(SimpleConfigOrigin.newSimple( + baseOrigin.description() + " (not found)"), + Collections. emptyMap()); + } + + // serialization all goes through SerializedConfigValue + private Object writeReplace() throws ObjectStreamException { + return new SerializedConfigValue(this); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigOrigin.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigOrigin.java new file mode 100644 index 0000000..790fccb --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleConfigOrigin.java @@ -0,0 +1,575 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.impl.SerializedConfigValue.SerializedField; + +// it would be cleaner to have a class hierarchy for various origin types, +// but was hoping this would be enough simpler to be a little messy. eh. +final class SimpleConfigOrigin implements ConfigOrigin { + + final private String description; + final private int lineNumber; + final private int endLineNumber; + final private OriginType originType; + final private String urlOrNull; + final private String resourceOrNull; + final private List commentsOrNull; + + protected SimpleConfigOrigin(String description, int lineNumber, int endLineNumber, OriginType originType, + String urlOrNull, String resourceOrNull, List commentsOrNull) { + if (description == null) + throw new ConfigException.BugOrBroken("description may not be null"); + this.description = description; + this.lineNumber = lineNumber; + this.endLineNumber = endLineNumber; + this.originType = originType; + this.urlOrNull = urlOrNull; + this.resourceOrNull = resourceOrNull; + this.commentsOrNull = commentsOrNull; + } + + static SimpleConfigOrigin newSimple(String description) { + return new SimpleConfigOrigin(description, -1, -1, OriginType.GENERIC, null, null, null); + } + + static SimpleConfigOrigin newFile(String filename) { + String url; + try { + url = (new File(filename)).toURI().toURL().toExternalForm(); + } catch (MalformedURLException e) { + url = null; + } + return new SimpleConfigOrigin(filename, -1, -1, OriginType.FILE, url, null, null); + } + + static SimpleConfigOrigin newURL(URL url) { + String u = url.toExternalForm(); + return new SimpleConfigOrigin(u, -1, -1, OriginType.URL, u, null, null); + } + + static SimpleConfigOrigin newResource(String resource, URL url) { + String desc; + if (url != null) + desc = resource + " @ " + url.toExternalForm(); + else + desc = resource; + return new SimpleConfigOrigin(desc, -1, -1, OriginType.RESOURCE, url != null ? url.toExternalForm() : null, + resource, null); + } + + static SimpleConfigOrigin newResource(String resource) { + return newResource(resource, null); + } + + @Override + public SimpleConfigOrigin withLineNumber(int lineNumber) { + if (lineNumber == this.lineNumber && lineNumber == this.endLineNumber) { + return this; + } else { + return new SimpleConfigOrigin(this.description, lineNumber, lineNumber, this.originType, this.urlOrNull, + this.resourceOrNull, this.commentsOrNull); + } + } + + SimpleConfigOrigin addURL(URL url) { + return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber, this.originType, + url != null ? url.toExternalForm() : null, this.resourceOrNull, this.commentsOrNull); + } + + @Override + public SimpleConfigOrigin withComments(List comments) { + if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull)) { + return this; + } else { + return new SimpleConfigOrigin(this.description, this.lineNumber, this.endLineNumber, this.originType, + this.urlOrNull, this.resourceOrNull, comments); + } + } + + SimpleConfigOrigin prependComments(List comments) { + if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) { + return this; + } else if (this.commentsOrNull == null) { + return withComments(comments); + } else { + List merged = new ArrayList(comments.size() + this.commentsOrNull.size()); + merged.addAll(comments); + merged.addAll(this.commentsOrNull); + return withComments(merged); + } + } + + SimpleConfigOrigin appendComments(List comments) { + if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) { + return this; + } else if (this.commentsOrNull == null) { + return withComments(comments); + } else { + List merged = new ArrayList(comments.size() + this.commentsOrNull.size()); + merged.addAll(this.commentsOrNull); + merged.addAll(comments); + return withComments(merged); + } + } + + @Override + public String description() { + if (lineNumber < 0) { + return description; + } else if (endLineNumber == lineNumber) { + return description + ": " + lineNumber; + } else { + return description + ": " + lineNumber + "-" + endLineNumber; + } + } + + @Override + public boolean equals(Object other) { + if (other instanceof SimpleConfigOrigin) { + SimpleConfigOrigin otherOrigin = (SimpleConfigOrigin) other; + + return this.description.equals(otherOrigin.description) && this.lineNumber == otherOrigin.lineNumber + && this.endLineNumber == otherOrigin.endLineNumber && this.originType == otherOrigin.originType + && ConfigImplUtil.equalsHandlingNull(this.urlOrNull, otherOrigin.urlOrNull) + && ConfigImplUtil.equalsHandlingNull(this.resourceOrNull, otherOrigin.resourceOrNull); + } else { + return false; + } + } + + @Override + public int hashCode() { + int h = 41 * (41 + description.hashCode()); + h = 41 * (h + lineNumber); + h = 41 * (h + endLineNumber); + h = 41 * (h + originType.hashCode()); + if (urlOrNull != null) + h = 41 * (h + urlOrNull.hashCode()); + if (resourceOrNull != null) + h = 41 * (h + resourceOrNull.hashCode()); + return h; + } + + @Override + public String toString() { + return "ConfigOrigin(" + description + ")"; + } + + @Override + public String filename() { + if (originType == OriginType.FILE) { + return description; + } else if (urlOrNull != null) { + URL url; + try { + url = new URL(urlOrNull); + } catch (MalformedURLException e) { + return null; + } + if (url.getProtocol().equals("file")) { + return url.getFile(); + } else { + return null; + } + } else { + return null; + } + } + + @Override + public URL url() { + if (urlOrNull == null) { + return null; + } else { + try { + return new URL(urlOrNull); + } catch (MalformedURLException e) { + return null; + } + } + } + + @Override + public String resource() { + return resourceOrNull; + } + + @Override + public int lineNumber() { + return lineNumber; + } + + @Override + public List comments() { + if (commentsOrNull != null) { + return Collections.unmodifiableList(commentsOrNull); + } else { + return Collections.emptyList(); + } + } + + static final String MERGE_OF_PREFIX = "merge of "; + + private static SimpleConfigOrigin mergeTwo(SimpleConfigOrigin a, SimpleConfigOrigin b) { + String mergedDesc; + int mergedStartLine; + int mergedEndLine; + List mergedComments; + + OriginType mergedType; + if (a.originType == b.originType) { + mergedType = a.originType; + } else { + mergedType = OriginType.GENERIC; + } + + // first use the "description" field which has no line numbers + // cluttering it. + String aDesc = a.description; + String bDesc = b.description; + if (aDesc.startsWith(MERGE_OF_PREFIX)) + aDesc = aDesc.substring(MERGE_OF_PREFIX.length()); + if (bDesc.startsWith(MERGE_OF_PREFIX)) + bDesc = bDesc.substring(MERGE_OF_PREFIX.length()); + + if (aDesc.equals(bDesc)) { + mergedDesc = aDesc; + + if (a.lineNumber < 0) + mergedStartLine = b.lineNumber; + else if (b.lineNumber < 0) + mergedStartLine = a.lineNumber; + else + mergedStartLine = Math.min(a.lineNumber, b.lineNumber); + + mergedEndLine = Math.max(a.endLineNumber, b.endLineNumber); + } else { + // this whole merge song-and-dance was intended to avoid this case + // whenever possible, but we've lost. Now we have to lose some + // structured information and cram into a string. + + // description() method includes line numbers, so use it instead + // of description field. + String aFull = a.description(); + String bFull = b.description(); + if (aFull.startsWith(MERGE_OF_PREFIX)) + aFull = aFull.substring(MERGE_OF_PREFIX.length()); + if (bFull.startsWith(MERGE_OF_PREFIX)) + bFull = bFull.substring(MERGE_OF_PREFIX.length()); + + mergedDesc = MERGE_OF_PREFIX + aFull + "," + bFull; + + mergedStartLine = -1; + mergedEndLine = -1; + } + + String mergedURL; + if (ConfigImplUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull)) { + mergedURL = a.urlOrNull; + } else { + mergedURL = null; + } + + String mergedResource; + if (ConfigImplUtil.equalsHandlingNull(a.resourceOrNull, b.resourceOrNull)) { + mergedResource = a.resourceOrNull; + } else { + mergedResource = null; + } + + if (ConfigImplUtil.equalsHandlingNull(a.commentsOrNull, b.commentsOrNull)) { + mergedComments = a.commentsOrNull; + } else { + mergedComments = new ArrayList(); + if (a.commentsOrNull != null) + mergedComments.addAll(a.commentsOrNull); + if (b.commentsOrNull != null) + mergedComments.addAll(b.commentsOrNull); + } + + return new SimpleConfigOrigin(mergedDesc, mergedStartLine, mergedEndLine, mergedType, mergedURL, + mergedResource, mergedComments); + } + + private static int similarity(SimpleConfigOrigin a, SimpleConfigOrigin b) { + int count = 0; + + if (a.originType == b.originType) + count += 1; + + if (a.description.equals(b.description)) { + count += 1; + + // only count these if the description field (which is the file + // or resource name) also matches. + if (a.lineNumber == b.lineNumber) + count += 1; + if (a.endLineNumber == b.endLineNumber) + count += 1; + if (ConfigImplUtil.equalsHandlingNull(a.urlOrNull, b.urlOrNull)) + count += 1; + if (ConfigImplUtil.equalsHandlingNull(a.resourceOrNull, b.resourceOrNull)) + count += 1; + } + + return count; + } + + // this picks the best pair to merge, because the pair has the most in + // common. we want to merge two lines in the same file rather than something + // else with one of the lines; because two lines in the same file can be + // better consolidated. + private static SimpleConfigOrigin mergeThree(SimpleConfigOrigin a, SimpleConfigOrigin b, SimpleConfigOrigin c) { + if (similarity(a, b) >= similarity(b, c)) { + return mergeTwo(mergeTwo(a, b), c); + } else { + return mergeTwo(a, mergeTwo(b, c)); + } + } + + static ConfigOrigin mergeOrigins(ConfigOrigin a, ConfigOrigin b) { + return mergeTwo((SimpleConfigOrigin) a, (SimpleConfigOrigin) b); + } + + static ConfigOrigin mergeOrigins(List stack) { + List origins = new ArrayList(stack.size()); + for (AbstractConfigValue v : stack) { + origins.add(v.origin()); + } + return mergeOrigins(origins); + } + + static ConfigOrigin mergeOrigins(Collection stack) { + if (stack.isEmpty()) { + throw new ConfigException.BugOrBroken("can't merge empty list of origins"); + } else if (stack.size() == 1) { + return stack.iterator().next(); + } else if (stack.size() == 2) { + Iterator i = stack.iterator(); + return mergeTwo((SimpleConfigOrigin) i.next(), (SimpleConfigOrigin) i.next()); + } else { + List remaining = new ArrayList(); + for (ConfigOrigin o : stack) { + remaining.add((SimpleConfigOrigin) o); + } + while (remaining.size() > 2) { + SimpleConfigOrigin c = remaining.get(remaining.size() - 1); + remaining.remove(remaining.size() - 1); + SimpleConfigOrigin b = remaining.get(remaining.size() - 1); + remaining.remove(remaining.size() - 1); + SimpleConfigOrigin a = remaining.get(remaining.size() - 1); + remaining.remove(remaining.size() - 1); + + SimpleConfigOrigin merged = mergeThree(a, b, c); + + remaining.add(merged); + } + + // should be down to either 1 or 2 + return mergeOrigins(remaining); + } + } + + Map toFields() { + Map m = new EnumMap(SerializedField.class); + + m.put(SerializedField.ORIGIN_DESCRIPTION, description); + + if (lineNumber >= 0) + m.put(SerializedField.ORIGIN_LINE_NUMBER, lineNumber); + if (endLineNumber >= 0) + m.put(SerializedField.ORIGIN_END_LINE_NUMBER, endLineNumber); + + m.put(SerializedField.ORIGIN_TYPE, originType.ordinal()); + + if (urlOrNull != null) + m.put(SerializedField.ORIGIN_URL, urlOrNull); + if (resourceOrNull != null) + m.put(SerializedField.ORIGIN_RESOURCE, resourceOrNull); + if (commentsOrNull != null) + m.put(SerializedField.ORIGIN_COMMENTS, commentsOrNull); + + return m; + } + + Map toFieldsDelta(SimpleConfigOrigin baseOrigin) { + Map baseFields; + if (baseOrigin != null) + baseFields = baseOrigin.toFields(); + else + baseFields = Collections. emptyMap(); + return fieldsDelta(baseFields, toFields()); + } + + // Here we're trying to avoid serializing the same info over and over + // in the common case that child objects have the same origin fields + // as their parent objects. e.g. we don't need to store the source + // filename with every single value. + static Map fieldsDelta(Map base, + Map child) { + Map m = new EnumMap(child); + + for (Map.Entry baseEntry : base.entrySet()) { + SerializedField f = baseEntry.getKey(); + if (m.containsKey(f) && ConfigImplUtil.equalsHandlingNull(baseEntry.getValue(), m.get(f))) { + // if field is unchanged, just remove it so we inherit + m.remove(f); + } else if (!m.containsKey(f)) { + // if field has been removed, we have to add a deletion entry + switch (f) { + case ORIGIN_DESCRIPTION: + throw new ConfigException.BugOrBroken("origin missing description field? " + child); + case ORIGIN_LINE_NUMBER: + m.put(SerializedField.ORIGIN_LINE_NUMBER, -1); + break; + case ORIGIN_END_LINE_NUMBER: + m.put(SerializedField.ORIGIN_END_LINE_NUMBER, -1); + break; + case ORIGIN_TYPE: + throw new ConfigException.BugOrBroken("should always be an ORIGIN_TYPE field"); + case ORIGIN_URL: + m.put(SerializedField.ORIGIN_NULL_URL, ""); + break; + case ORIGIN_RESOURCE: + m.put(SerializedField.ORIGIN_NULL_RESOURCE, ""); + break; + case ORIGIN_COMMENTS: + m.put(SerializedField.ORIGIN_NULL_COMMENTS, ""); + break; + case ORIGIN_NULL_URL: // FALL THRU + case ORIGIN_NULL_RESOURCE: // FALL THRU + case ORIGIN_NULL_COMMENTS: + throw new ConfigException.BugOrBroken("computing delta, base object should not contain " + f + " " + + base); + case END_MARKER: + case ROOT_VALUE: + case ROOT_WAS_CONFIG: + case UNKNOWN: + case VALUE_DATA: + case VALUE_ORIGIN: + throw new ConfigException.BugOrBroken("should not appear here: " + f); + } + } else { + // field is in base and child, but differs, so leave it + } + } + + return m; + } + + static SimpleConfigOrigin fromFields(Map m) throws IOException { + // we represent a null origin as one with no fields at all + if (m.isEmpty()) + return null; + + String description = (String) m.get(SerializedField.ORIGIN_DESCRIPTION); + Integer lineNumber = (Integer) m.get(SerializedField.ORIGIN_LINE_NUMBER); + Integer endLineNumber = (Integer) m.get(SerializedField.ORIGIN_END_LINE_NUMBER); + Number originTypeOrdinal = (Number) m.get(SerializedField.ORIGIN_TYPE); + if (originTypeOrdinal == null) + throw new IOException("Missing ORIGIN_TYPE field"); + OriginType originType = OriginType.values()[originTypeOrdinal.byteValue()]; + String urlOrNull = (String) m.get(SerializedField.ORIGIN_URL); + String resourceOrNull = (String) m.get(SerializedField.ORIGIN_RESOURCE); + @SuppressWarnings("unchecked") + List commentsOrNull = (List) m.get(SerializedField.ORIGIN_COMMENTS); + // Older versions did not have a resource field, they stuffed it into + // the description. + if (originType == OriginType.RESOURCE && resourceOrNull == null) { + resourceOrNull = description; + } + return new SimpleConfigOrigin(description, lineNumber != null ? lineNumber : -1, + endLineNumber != null ? endLineNumber : -1, originType, urlOrNull, resourceOrNull, commentsOrNull); + } + + static Map applyFieldsDelta(Map base, + Map delta) throws IOException { + + Map m = new EnumMap(delta); + + for (Map.Entry baseEntry : base.entrySet()) { + SerializedField f = baseEntry.getKey(); + if (delta.containsKey(f)) { + // delta overrides when keys are in both + // "m" should already contain the right thing + } else { + // base has the key and delta does not. + // we inherit from base unless a "NULL" key blocks. + switch (f) { + case ORIGIN_DESCRIPTION: + m.put(f, base.get(f)); + break; + case ORIGIN_URL: + if (delta.containsKey(SerializedField.ORIGIN_NULL_URL)) { + m.remove(SerializedField.ORIGIN_NULL_URL); + } else { + m.put(f, base.get(f)); + } + break; + case ORIGIN_RESOURCE: + if (delta.containsKey(SerializedField.ORIGIN_NULL_RESOURCE)) { + m.remove(SerializedField.ORIGIN_NULL_RESOURCE); + } else { + m.put(f, base.get(f)); + } + break; + case ORIGIN_COMMENTS: + if (delta.containsKey(SerializedField.ORIGIN_NULL_COMMENTS)) { + m.remove(SerializedField.ORIGIN_NULL_COMMENTS); + } else { + m.put(f, base.get(f)); + } + break; + case ORIGIN_NULL_URL: // FALL THRU + case ORIGIN_NULL_RESOURCE: // FALL THRU + case ORIGIN_NULL_COMMENTS: // FALL THRU + // base objects shouldn't contain these, should just + // lack the field. these are only in deltas. + throw new ConfigException.BugOrBroken("applying fields, base object should not contain " + f + " " + + base); + case ORIGIN_END_LINE_NUMBER: // FALL THRU + case ORIGIN_LINE_NUMBER: // FALL THRU + case ORIGIN_TYPE: + m.put(f, base.get(f)); + break; + + case END_MARKER: + case ROOT_VALUE: + case ROOT_WAS_CONFIG: + case UNKNOWN: + case VALUE_DATA: + case VALUE_ORIGIN: + throw new ConfigException.BugOrBroken("should not appear here: " + f); + } + } + } + return m; + } + + static SimpleConfigOrigin fromBase(SimpleConfigOrigin baseOrigin, Map delta) + throws IOException { + Map baseFields; + if (baseOrigin != null) + baseFields = baseOrigin.toFields(); + else + baseFields = Collections. emptyMap(); + Map fields = applyFieldsDelta(baseFields, delta); + return fromFields(fields); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleIncludeContext.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleIncludeContext.java new file mode 100644 index 0000000..b81d990 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleIncludeContext.java @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigIncludeContext; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigParseOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigParseable; + +class SimpleIncludeContext implements ConfigIncludeContext { + + private final Parseable parseable; + private final ConfigParseOptions options; + + SimpleIncludeContext(Parseable parseable) { + this.parseable = parseable; + this.options = SimpleIncluder.clearForInclude(parseable.options()); + } + + private SimpleIncludeContext(Parseable parseable, ConfigParseOptions options) { + this.parseable = parseable; + this.options = options; + } + + SimpleIncludeContext withParseable(Parseable parseable) { + if (parseable == this.parseable) + return this; + else + return new SimpleIncludeContext(parseable); + } + + @Override + public ConfigParseable relativeTo(String filename) { + if (ConfigImpl.traceLoadsEnabled()) + ConfigImpl.trace("Looking for '" + filename + "' relative to " + parseable); + if (parseable != null) + return parseable.relativeTo(filename); + else + return null; + } + + @Override + public ConfigParseOptions parseOptions() { + return options; + } + + @Override + public ConfigIncludeContext setParseOptions(ConfigParseOptions options) { + return new SimpleIncludeContext(parseable, options.setSyntax(null).setOriginDescription(null)); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleIncluder.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleIncluder.java new file mode 100644 index 0000000..04c30be --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SimpleIncluder.java @@ -0,0 +1,302 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigFactory; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigIncludeContext; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigIncluder; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigIncluderClasspath; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigIncluderFile; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigIncluderURL; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigObject; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigParseOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigParseable; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigSyntax; + +class SimpleIncluder implements FullIncluder { + + private ConfigIncluder fallback; + + SimpleIncluder(ConfigIncluder fallback) { + this.fallback = fallback; + } + + // ConfigIncludeContext does this for us on its options + static ConfigParseOptions clearForInclude(ConfigParseOptions options) { + // the class loader and includer are inherited, but not this other + // stuff. + return options.setSyntax(null).setOriginDescription(null).setAllowMissing(true); + } + + // this is the heuristic includer + @Override + public ConfigObject include(final ConfigIncludeContext context, String name) { + ConfigObject obj = includeWithoutFallback(context, name); + + // now use the fallback includer if any and merge + // its result. + if (fallback != null) { + return obj.withFallback(fallback.include(context, name)); + } else { + return obj; + } + } + + // the heuristic includer in static form + static ConfigObject includeWithoutFallback(final ConfigIncludeContext context, String name) { + // the heuristic is valid URL then URL, else relative to including file; + // relativeTo in a file falls back to classpath inside relativeTo(). + + URL url; + try { + url = new URL(name); + } catch (MalformedURLException e) { + url = null; + } + + if (url != null) { + return includeURLWithoutFallback(context, url); + } else { + NameSource source = new RelativeNameSource(context); + return fromBasename(source, name, context.parseOptions()); + } + } + + @Override + public ConfigObject includeURL(ConfigIncludeContext context, URL url) { + ConfigObject obj = includeURLWithoutFallback(context, url); + + // now use the fallback includer if any and merge + // its result. + if (fallback != null && fallback instanceof ConfigIncluderURL) { + return obj.withFallback(((ConfigIncluderURL) fallback).includeURL(context, url)); + } else { + return obj; + } + } + + static ConfigObject includeURLWithoutFallback(final ConfigIncludeContext context, URL url) { + return ConfigFactory.parseURL(url, context.parseOptions()).root(); + } + + @Override + public ConfigObject includeFile(ConfigIncludeContext context, File file) { + ConfigObject obj = includeFileWithoutFallback(context, file); + + // now use the fallback includer if any and merge + // its result. + if (fallback != null && fallback instanceof ConfigIncluderFile) { + return obj.withFallback(((ConfigIncluderFile) fallback).includeFile(context, file)); + } else { + return obj; + } + } + + static ConfigObject includeFileWithoutFallback(final ConfigIncludeContext context, File file) { + return ConfigFactory.parseFileAnySyntax(file, context.parseOptions()).root(); + } + + @Override + public ConfigObject includeResources(ConfigIncludeContext context, String resource) { + ConfigObject obj = includeResourceWithoutFallback(context, resource); + + // now use the fallback includer if any and merge + // its result. + if (fallback != null && fallback instanceof ConfigIncluderClasspath) { + return obj.withFallback(((ConfigIncluderClasspath) fallback).includeResources(context, + resource)); + } else { + return obj; + } + } + + static ConfigObject includeResourceWithoutFallback(final ConfigIncludeContext context, + String resource) { + return ConfigFactory.parseResourcesAnySyntax(resource, context.parseOptions()).root(); + } + + @Override + public ConfigIncluder withFallback(ConfigIncluder fallback) { + if (this == fallback) { + throw new ConfigException.BugOrBroken("trying to create includer cycle"); + } else if (this.fallback == fallback) { + return this; + } else if (this.fallback != null) { + return new SimpleIncluder(this.fallback.withFallback(fallback)); + } else { + return new SimpleIncluder(fallback); + } + } + + interface NameSource { + ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions); + } + + static private class RelativeNameSource implements NameSource { + final private ConfigIncludeContext context; + + RelativeNameSource(ConfigIncludeContext context) { + this.context = context; + } + + @Override + public ConfigParseable nameToParseable(String name, ConfigParseOptions options) { + ConfigParseable p = context.relativeTo(name); + if (p == null) { + // avoid returning null + return Parseable + .newNotFound(name, "include was not found: '" + name + "'", options); + } else { + return p; + } + } + }; + + // this function is a little tricky because there are three places we're + // trying to use it; for 'include "basename"' in a .conf file, for + // loading app.{conf,json,properties} from classpath, and for + // loading app.{conf,json,properties} from the filesystem. + static ConfigObject fromBasename(NameSource source, String name, ConfigParseOptions options) { + ConfigObject obj; + if (name.endsWith(".conf") || name.endsWith(".json") || name.endsWith(".properties")) { + ConfigParseable p = source.nameToParseable(name, options); + + obj = p.parse(p.options().setAllowMissing(options.getAllowMissing())); + } else { + ConfigParseable confHandle = source.nameToParseable(name + ".conf", options); + ConfigParseable jsonHandle = source.nameToParseable(name + ".json", options); + ConfigParseable propsHandle = source.nameToParseable(name + ".properties", options); + boolean gotSomething = false; + List fails = new ArrayList(); + + ConfigSyntax syntax = options.getSyntax(); + + obj = SimpleConfigObject.empty(SimpleConfigOrigin.newSimple(name)); + if (syntax == null || syntax == ConfigSyntax.CONF) { + try { + obj = confHandle.parse(confHandle.options().setAllowMissing(false) + .setSyntax(ConfigSyntax.CONF)); + gotSomething = true; + } catch (ConfigException.IO e) { + fails.add(e); + } + } + + if (syntax == null || syntax == ConfigSyntax.JSON) { + try { + ConfigObject parsed = jsonHandle.parse(jsonHandle.options() + .setAllowMissing(false).setSyntax(ConfigSyntax.JSON)); + obj = obj.withFallback(parsed); + gotSomething = true; + } catch (ConfigException.IO e) { + fails.add(e); + } + } + + if (syntax == null || syntax == ConfigSyntax.PROPERTIES) { + try { + ConfigObject parsed = propsHandle.parse(propsHandle.options() + .setAllowMissing(false).setSyntax(ConfigSyntax.PROPERTIES)); + obj = obj.withFallback(parsed); + gotSomething = true; + } catch (ConfigException.IO e) { + fails.add(e); + } + } + + if (!options.getAllowMissing() && !gotSomething) { + if (ConfigImpl.traceLoadsEnabled()) { + // the individual exceptions should have been logged already + // with tracing enabled + ConfigImpl.trace("Did not find '" + name + + "' with any extension (.conf, .json, .properties); " + + "exceptions should have been logged above."); + } + + if (fails.isEmpty()) { + // this should not happen + throw new ConfigException.BugOrBroken( + "should not be reached: nothing found but no exceptions thrown"); + } else { + StringBuilder sb = new StringBuilder(); + for (Throwable t : fails) { + sb.append(t.getMessage()); + sb.append(", "); + } + sb.setLength(sb.length() - 2); + throw new ConfigException.IO(SimpleConfigOrigin.newSimple(name), sb.toString(), + fails.get(0)); + } + } else if (!gotSomething) { + if (ConfigImpl.traceLoadsEnabled()) { + ConfigImpl.trace("Did not find '" + name + + "' with any extension (.conf, .json, .properties); but '" + name + + "' is allowed to be missing. Exceptions from load attempts should have been logged above."); + } + } + } + + return obj; + } + + // the Proxy is a proxy for an application-provided includer that uses our + // default implementations when the application-provided includer doesn't + // have an implementation. + static private class Proxy implements FullIncluder { + final ConfigIncluder delegate; + + Proxy(ConfigIncluder delegate) { + this.delegate = delegate; + } + + @Override + public ConfigIncluder withFallback(ConfigIncluder fallback) { + // we never fall back + return this; + } + + @Override + public ConfigObject include(ConfigIncludeContext context, String what) { + return delegate.include(context, what); + } + + @Override + public ConfigObject includeResources(ConfigIncludeContext context, String what) { + if (delegate instanceof ConfigIncluderClasspath) + return ((ConfigIncluderClasspath) delegate).includeResources(context, what); + else + return includeResourceWithoutFallback(context, what); + } + + @Override + public ConfigObject includeURL(ConfigIncludeContext context, URL what) { + if (delegate instanceof ConfigIncluderURL) + return ((ConfigIncluderURL) delegate).includeURL(context, what); + else + return includeURLWithoutFallback(context, what); + } + + @Override + public ConfigObject includeFile(ConfigIncludeContext context, File what) { + if (delegate instanceof ConfigIncluderFile) + return ((ConfigIncluderFile) delegate).includeFile(context, what); + else + return includeFileWithoutFallback(context, what); + } + } + + static FullIncluder makeFull(ConfigIncluder includer) { + if (includer instanceof FullIncluder) + return (FullIncluder) includer; + else + return new Proxy(includer); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SubstitutionExpression.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SubstitutionExpression.java new file mode 100644 index 0000000..e8aab95 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/SubstitutionExpression.java @@ -0,0 +1,49 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +final class SubstitutionExpression { + + final private Path path; + final private boolean optional; + + SubstitutionExpression(Path path, boolean optional) { + this.path = path; + this.optional = optional; + } + + Path path() { + return path; + } + + boolean optional() { + return optional; + } + + SubstitutionExpression changePath(Path newPath) { + if (newPath == path) + return this; + else + return new SubstitutionExpression(newPath, optional); + } + + @Override + public String toString() { + return "${" + (optional ? "?" : "") + path.render() + "}"; + } + + @Override + public boolean equals(Object other) { + if (other instanceof SubstitutionExpression) { + SubstitutionExpression otherExp = (SubstitutionExpression) other; + return otherExp.path.equals(this.path) && otherExp.optional == this.optional; + } else { + return false; + } + } + + @Override + public int hashCode() { + int h = 41 * (41 + path.hashCode()); + h = 41 * (h + (optional ? 1 : 0)); + return h; + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Token.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Token.java new file mode 100644 index 0000000..ee6dbee --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Token.java @@ -0,0 +1,87 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; + +class Token { + final private TokenType tokenType; + final private String debugString; + final private ConfigOrigin origin; + final private String tokenText; + + Token(TokenType tokenType, ConfigOrigin origin) { + this(tokenType, origin, null); + } + + Token(TokenType tokenType, ConfigOrigin origin, String tokenText) { + this(tokenType, origin, tokenText, null); + } + + Token(TokenType tokenType, ConfigOrigin origin, String tokenText, String debugString) { + this.tokenType = tokenType; + this.origin = origin; + this.debugString = debugString; + this.tokenText = tokenText; + } + + // this is used for singleton tokens like COMMA or OPEN_CURLY + static Token newWithoutOrigin(TokenType tokenType, String debugString, String tokenText) { + return new Token(tokenType, null, tokenText, debugString); + } + + final TokenType tokenType() { + return tokenType; + } + + public String tokenText() { return tokenText; } + + // this is final because we don't always use the origin() accessor, + // and we don't because it throws if origin is null + final ConfigOrigin origin() { + // code is only supposed to call origin() on token types that are + // expected to have an origin. + if (origin == null) + throw new ConfigException.BugOrBroken( + "tried to get origin from token that doesn't have one: " + this); + return origin; + } + + final int lineNumber() { + if (origin != null) + return origin.lineNumber(); + else + return -1; + } + + @Override + public String toString() { + if (debugString != null) + return debugString; + else + return tokenType.name(); + } + + protected boolean canEqual(Object other) { + return other instanceof Token; + } + + @Override + public boolean equals(Object other) { + if (other instanceof Token) { + // origin is deliberately left out + return canEqual(other) + && this.tokenType == ((Token) other).tokenType; + } else { + return false; + } + } + + @Override + public int hashCode() { + // origin is deliberately left out + return tokenType.hashCode(); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/TokenType.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/TokenType.java new file mode 100644 index 0000000..f3dd507 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/TokenType.java @@ -0,0 +1,24 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +enum TokenType { + START, + END, + COMMA, + EQUALS, + COLON, + OPEN_CURLY, + CLOSE_CURLY, + OPEN_SQUARE, + CLOSE_SQUARE, + VALUE, + NEWLINE, + UNQUOTED_TEXT, + IGNORED_WHITESPACE, + SUBSTITUTION, + PROBLEM, + COMMENT, + PLUS_EQUALS; +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Tokenizer.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Tokenizer.java new file mode 100644 index 0000000..e6d7a5c --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Tokenizer.java @@ -0,0 +1,695 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigSyntax; + +final class Tokenizer { + // this exception should not leave this file + private static class ProblemException extends Exception { + private static final long serialVersionUID = 1L; + + final private Token problem; + + ProblemException(Token problem) { + this.problem = problem; + } + + Token problem() { + return problem; + } + } + + private static String asString(int codepoint) { + if (codepoint == '\n') + return "newline"; + else if (codepoint == '\t') + return "tab"; + else if (codepoint == -1) + return "end of file"; + else if (ConfigImplUtil.isC0Control(codepoint)) + return String.format("control character 0x%x", codepoint); + else + return String.format("%c", codepoint); + } + + /** + * Tokenizes a Reader. Does not close the reader; you have to arrange to do + * that after you're done with the returned iterator. + */ + static Iterator tokenize(ConfigOrigin origin, Reader input, ConfigSyntax flavor) { + return new TokenIterator(origin, input, flavor != ConfigSyntax.JSON); + } + + static String render(Iterator tokens) { + StringBuilder renderedText = new StringBuilder(); + while (tokens.hasNext()) { + renderedText.append(tokens.next().tokenText()); + } + return renderedText.toString(); + } + + private static class TokenIterator implements Iterator { + + private static class WhitespaceSaver { + // has to be saved inside value concatenations + private StringBuilder whitespace; + // may need to value-concat with next value + private boolean lastTokenWasSimpleValue; + + WhitespaceSaver() { + whitespace = new StringBuilder(); + lastTokenWasSimpleValue = false; + } + + void add(int c) { + whitespace.appendCodePoint(c); + } + + Token check(Token t, ConfigOrigin baseOrigin, int lineNumber) { + if (isSimpleValue(t)) { + return nextIsASimpleValue(baseOrigin, lineNumber); + } else { + return nextIsNotASimpleValue(baseOrigin, lineNumber); + } + } + + // called if the next token is not a simple value; + // discards any whitespace we were saving between + // simple values. + private Token nextIsNotASimpleValue(ConfigOrigin baseOrigin, int lineNumber) { + lastTokenWasSimpleValue = false; + return createWhitespaceTokenFromSaver(baseOrigin, lineNumber); + } + + // called if the next token IS a simple value, + // so creates a whitespace token if the previous + // token also was. + private Token nextIsASimpleValue(ConfigOrigin baseOrigin, + int lineNumber) { + Token t = createWhitespaceTokenFromSaver(baseOrigin, lineNumber); + if (!lastTokenWasSimpleValue) { + lastTokenWasSimpleValue = true; + } + return t; + } + + private Token createWhitespaceTokenFromSaver(ConfigOrigin baseOrigin, + int lineNumber) { + if (whitespace.length() > 0) { + Token t; + if (lastTokenWasSimpleValue) { + t = Tokens.newUnquotedText( + lineOrigin(baseOrigin, lineNumber), + whitespace.toString()); + } else { + t = Tokens.newIgnoredWhitespace(lineOrigin(baseOrigin, lineNumber), + whitespace.toString()); + } + whitespace.setLength(0); // reset + return t; + } + return null; + } + } + + final private SimpleConfigOrigin origin; + final private Reader input; + final private LinkedList buffer; + private int lineNumber; + private ConfigOrigin lineOrigin; + final private Queue tokens; + final private WhitespaceSaver whitespaceSaver; + final private boolean allowComments; + + TokenIterator(ConfigOrigin origin, Reader input, boolean allowComments) { + this.origin = (SimpleConfigOrigin) origin; + this.input = input; + this.allowComments = allowComments; + this.buffer = new LinkedList(); + lineNumber = 1; + lineOrigin = this.origin.withLineNumber(lineNumber); + tokens = new LinkedList(); + tokens.add(Tokens.START); + whitespaceSaver = new WhitespaceSaver(); + } + + + // this should ONLY be called from nextCharSkippingComments + // or when inside a quoted string, or when parsing a sequence + // like ${ or +=, everything else should use + // nextCharSkippingComments(). + private int nextCharRaw() { + if (buffer.isEmpty()) { + try { + return input.read(); + } catch (IOException e) { + throw new ConfigException.IO(origin, "read error: " + + e.getMessage(), e); + } + } else { + int c = buffer.pop(); + return c; + } + } + + private void putBack(int c) { + if (buffer.size() > 2) { + throw new ConfigException.BugOrBroken( + "bug: putBack() three times, undesirable look-ahead"); + } + buffer.push(c); + } + + static boolean isWhitespace(int c) { + return ConfigImplUtil.isWhitespace(c); + } + + static boolean isWhitespaceNotNewline(int c) { + return c != '\n' && ConfigImplUtil.isWhitespace(c); + } + + private boolean startOfComment(int c) { + if (c == -1) { + return false; + } else { + if (allowComments) { + if (c == '#') { + return true; + } else if (c == '/') { + int maybeSecondSlash = nextCharRaw(); + // we want to predictably NOT consume any chars + putBack(maybeSecondSlash); + if (maybeSecondSlash == '/') { + return true; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + } + + // get next char, skipping non-newline whitespace + private int nextCharAfterWhitespace(WhitespaceSaver saver) { + for (;;) { + int c = nextCharRaw(); + + if (c == -1) { + return -1; + } else { + if (isWhitespaceNotNewline(c)) { + saver.add(c); + continue; + } else { + return c; + } + } + } + } + + private ProblemException problem(String message) { + return problem("", message, null); + } + + private ProblemException problem(String what, String message) { + return problem(what, message, null); + } + + private ProblemException problem(String what, String message, boolean suggestQuotes) { + return problem(what, message, suggestQuotes, null); + } + + private ProblemException problem(String what, String message, Throwable cause) { + return problem(lineOrigin, what, message, cause); + } + + private ProblemException problem(String what, String message, boolean suggestQuotes, + Throwable cause) { + return problem(lineOrigin, what, message, suggestQuotes, cause); + } + + private static ProblemException problem(ConfigOrigin origin, String what, + String message, + Throwable cause) { + return problem(origin, what, message, false, cause); + } + + private static ProblemException problem(ConfigOrigin origin, String what, String message, + boolean suggestQuotes, Throwable cause) { + if (what == null || message == null) + throw new ConfigException.BugOrBroken( + "internal error, creating bad ProblemException"); + return new ProblemException(Tokens.newProblem(origin, what, message, suggestQuotes, + cause)); + } + + private static ProblemException problem(ConfigOrigin origin, String message) { + return problem(origin, "", message, null); + } + + private static ConfigOrigin lineOrigin(ConfigOrigin baseOrigin, + int lineNumber) { + return ((SimpleConfigOrigin) baseOrigin).withLineNumber(lineNumber); + } + + // ONE char has always been consumed, either the # or the first /, but + // not both slashes + private Token pullComment(int firstChar) { + boolean doubleSlash = false; + if (firstChar == '/') { + int discard = nextCharRaw(); + if (discard != '/') + throw new ConfigException.BugOrBroken("called pullComment but // not seen"); + doubleSlash = true; + } + + StringBuilder sb = new StringBuilder(); + for (;;) { + int c = nextCharRaw(); + if (c == -1 || c == '\n') { + putBack(c); + if (doubleSlash) + return Tokens.newCommentDoubleSlash(lineOrigin, sb.toString()); + else + return Tokens.newCommentHash(lineOrigin, sb.toString()); + } else { + sb.appendCodePoint(c); + } + } + } + + // chars JSON allows a number to start with + static final String firstNumberChars = "0123456789-"; + // chars JSON allows to be part of a number + static final String numberChars = "0123456789eE+-."; + // chars that stop an unquoted string + static final String notInUnquotedText = "$\"{}[]:=,+#`^?!@*&\\"; + + // The rules here are intended to maximize convenience while + // avoiding confusion with real valid JSON. Basically anything + // that parses as JSON is treated the JSON way and otherwise + // we assume it's a string and let the parser sort it out. + private Token pullUnquotedText() { + ConfigOrigin origin = lineOrigin; + StringBuilder sb = new StringBuilder(); + int c = nextCharRaw(); + while (true) { + if (c == -1) { + break; + } else if (notInUnquotedText.indexOf(c) >= 0) { + break; + } else if (isWhitespace(c)) { + break; + } else if (startOfComment(c)) { + break; + } else { + sb.appendCodePoint(c); + } + + // we parse true/false/null tokens as such no matter + // what is after them, as long as they are at the + // start of the unquoted token. + if (sb.length() == 4) { + String s = sb.toString(); + if (s.equals("true")) + return Tokens.newBoolean(origin, true); + else if (s.equals("null")) + return Tokens.newNull(origin); + } else if (sb.length() == 5) { + String s = sb.toString(); + if (s.equals("false")) + return Tokens.newBoolean(origin, false); + } + + c = nextCharRaw(); + } + + // put back the char that ended the unquoted text + putBack(c); + + String s = sb.toString(); + return Tokens.newUnquotedText(origin, s); + } + + private Token pullNumber(int firstChar) throws ProblemException { + StringBuilder sb = new StringBuilder(); + sb.appendCodePoint(firstChar); + boolean containedDecimalOrE = false; + int c = nextCharRaw(); + while (c != -1 && numberChars.indexOf(c) >= 0) { + if (c == '.' || c == 'e' || c == 'E') + containedDecimalOrE = true; + sb.appendCodePoint(c); + c = nextCharRaw(); + } + // the last character we looked at wasn't part of the number, put it + // back + putBack(c); + String s = sb.toString(); + try { + if (containedDecimalOrE) { + // force floating point representation + return Tokens.newDouble(lineOrigin, Double.parseDouble(s), s); + } else { + // this should throw if the integer is too large for Long + return Tokens.newLong(lineOrigin, Long.parseLong(s), s); + } + } catch (NumberFormatException e) { + // not a number after all, see if it's an unquoted string. + for (char u : s.toCharArray()) { + if (notInUnquotedText.indexOf(u) >= 0) + throw problem(asString(u), "Reserved character '" + asString(u) + + "' is not allowed outside quotes", true /* suggestQuotes */); + } + // no evil chars so we just decide this was a string and + // not a number. + return Tokens.newUnquotedText(lineOrigin, s); + } + } + + private void pullEscapeSequence(StringBuilder sb, StringBuilder sbOrig) throws ProblemException { + int escaped = nextCharRaw(); + if (escaped == -1) + throw problem("End of input but backslash in string had nothing after it"); + + // This is needed so we return the unescaped escape characters back out when rendering + // the token + sbOrig.appendCodePoint('\\'); + sbOrig.appendCodePoint(escaped); + + switch (escaped) { + case '"': + sb.append('"'); + break; + case '\\': + sb.append('\\'); + break; + case '/': + sb.append('/'); + break; + case 'b': + sb.append('\b'); + break; + case 'f': + sb.append('\f'); + break; + case 'n': + sb.append('\n'); + break; + case 'r': + sb.append('\r'); + break; + case 't': + sb.append('\t'); + break; + case 'u': { + // kind of absurdly slow, but screw it for now + char[] a = new char[4]; + for (int i = 0; i < 4; ++i) { + int c = nextCharRaw(); + if (c == -1) + throw problem("End of input but expecting 4 hex digits for \\uXXXX escape"); + a[i] = (char) c; + } + String digits = new String(a); + sbOrig.append(a); + try { + sb.appendCodePoint(Integer.parseInt(digits, 16)); + } catch (NumberFormatException e) { + throw problem(digits, String.format( + "Malformed hex digits after \\u escape in string: '%s'", digits), e); + } + } + break; + default: + throw problem( + asString(escaped), + String.format( + "backslash followed by '%s', this is not a valid escape sequence (quoted strings use JSON escaping, so use double-backslash \\\\ for literal backslash)", + asString(escaped))); + } + } + + private void appendTripleQuotedString(StringBuilder sb, StringBuilder sbOrig) throws ProblemException { + // we are after the opening triple quote and need to consume the + // close triple + int consecutiveQuotes = 0; + for (;;) { + int c = nextCharRaw(); + + if (c == '"') { + consecutiveQuotes += 1; + } else if (consecutiveQuotes >= 3) { + // the last three quotes end the string and the others are + // kept. + sb.setLength(sb.length() - 3); + putBack(c); + break; + } else { + consecutiveQuotes = 0; + if (c == -1) + throw problem("End of input but triple-quoted string was still open"); + else if (c == '\n') { + // keep the line number accurate + lineNumber += 1; + lineOrigin = origin.withLineNumber(lineNumber); + } + } + + sb.appendCodePoint(c); + sbOrig.appendCodePoint(c); + } + } + + private Token pullQuotedString() throws ProblemException { + // the open quote has already been consumed + StringBuilder sb = new StringBuilder(); + + // We need a second string builder to keep track of escape characters. + // We want to return them exactly as they appeared in the original text, + // which means we will need a new StringBuilder to escape escape characters + // so we can also keep the actual value of the string. This is gross. + StringBuilder sbOrig = new StringBuilder(); + sbOrig.appendCodePoint('"'); + + while (true) { + int c = nextCharRaw(); + if (c == -1) + throw problem("End of input but string quote was still open"); + + if (c == '\\') { + pullEscapeSequence(sb, sbOrig); + } else if (c == '"') { + sbOrig.appendCodePoint(c); + break; + } else if (ConfigImplUtil.isC0Control(c)) { + throw problem(asString(c), "JSON does not allow unescaped " + asString(c) + + " in quoted strings, use a backslash escape"); + } else { + sb.appendCodePoint(c); + sbOrig.appendCodePoint(c); + } + } + + // maybe switch to triple-quoted string, sort of hacky... + if (sb.length() == 0) { + int third = nextCharRaw(); + if (third == '"') { + sbOrig.appendCodePoint(third); + appendTripleQuotedString(sb, sbOrig); + } else { + putBack(third); + } + + } + return Tokens.newString(lineOrigin, sb.toString(), sbOrig.toString()); + } + + private Token pullPlusEquals() throws ProblemException { + // the initial '+' has already been consumed + int c = nextCharRaw(); + if (c != '=') { + throw problem(asString(c), "'+' not followed by =, '" + asString(c) + + "' not allowed after '+'", true /* suggestQuotes */); + } + return Tokens.PLUS_EQUALS; + } + + private Token pullSubstitution() throws ProblemException { + // the initial '$' has already been consumed + ConfigOrigin origin = lineOrigin; + int c = nextCharRaw(); + if (c != '{') { + throw problem(asString(c), "'$' not followed by {, '" + asString(c) + + "' not allowed after '$'", true /* suggestQuotes */); + } + + boolean optional = false; + c = nextCharRaw(); + if (c == '?') { + optional = true; + } else { + putBack(c); + } + + WhitespaceSaver saver = new WhitespaceSaver(); + List expression = new ArrayList(); + + Token t; + do { + t = pullNextToken(saver); + + // note that we avoid validating the allowed tokens inside + // the substitution here; we even allow nested substitutions + // in the tokenizer. The parser sorts it out. + if (t == Tokens.CLOSE_CURLY) { + // end the loop, done! + break; + } else if (t == Tokens.END) { + throw problem(origin, + "Substitution ${ was not closed with a }"); + } else { + Token whitespace = saver.check(t, origin, lineNumber); + if (whitespace != null) + expression.add(whitespace); + expression.add(t); + } + } while (true); + + return Tokens.newSubstitution(origin, optional, expression); + } + + private Token pullNextToken(WhitespaceSaver saver) throws ProblemException { + int c = nextCharAfterWhitespace(saver); + if (c == -1) { + return Tokens.END; + } else if (c == '\n') { + // newline tokens have the just-ended line number + Token line = Tokens.newLine(lineOrigin); + lineNumber += 1; + lineOrigin = origin.withLineNumber(lineNumber); + return line; + } else { + Token t; + if (startOfComment(c)) { + t = pullComment(c); + } else { + switch (c) { + case '"': + t = pullQuotedString(); + break; + case '$': + t = pullSubstitution(); + break; + case ':': + t = Tokens.COLON; + break; + case ',': + t = Tokens.COMMA; + break; + case '=': + t = Tokens.EQUALS; + break; + case '{': + t = Tokens.OPEN_CURLY; + break; + case '}': + t = Tokens.CLOSE_CURLY; + break; + case '[': + t = Tokens.OPEN_SQUARE; + break; + case ']': + t = Tokens.CLOSE_SQUARE; + break; + case '+': + t = pullPlusEquals(); + break; + default: + t = null; + break; + } + + if (t == null) { + if (firstNumberChars.indexOf(c) >= 0) { + t = pullNumber(c); + } else if (notInUnquotedText.indexOf(c) >= 0) { + throw problem(asString(c), "Reserved character '" + asString(c) + + "' is not allowed outside quotes", true /* suggestQuotes */); + } else { + putBack(c); + t = pullUnquotedText(); + } + } + } + + if (t == null) + throw new ConfigException.BugOrBroken( + "bug: failed to generate next token"); + + return t; + } + } + + private static boolean isSimpleValue(Token t) { + if (Tokens.isSubstitution(t) || Tokens.isUnquotedText(t) + || Tokens.isValue(t)) { + return true; + } else { + return false; + } + } + + private void queueNextToken() throws ProblemException { + Token t = pullNextToken(whitespaceSaver); + Token whitespace = whitespaceSaver.check(t, origin, lineNumber); + if (whitespace != null) + tokens.add(whitespace); + + tokens.add(t); + } + + @Override + public boolean hasNext() { + return !tokens.isEmpty(); + } + + @Override + public Token next() { + Token t = tokens.remove(); + if (tokens.isEmpty() && t != Tokens.END) { + try { + queueNextToken(); + } catch (ProblemException e) { + tokens.add(e.problem()); + } + if (tokens.isEmpty()) + throw new ConfigException.BugOrBroken( + "bug: tokens queue should not be empty here"); + } + return t; + } + + @Override + public void remove() { + throw new UnsupportedOperationException( + "Does not make sense to remove items from token stream"); + } + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Tokens.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Tokens.java new file mode 100644 index 0000000..08762d0 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Tokens.java @@ -0,0 +1,521 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.List; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigOrigin; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValueType; + +/* FIXME the way the subclasses of Token are private with static isFoo and accessors is kind of ridiculous. */ +final class Tokens { + static private class Value extends Token { + + final private AbstractConfigValue value; + + Value(AbstractConfigValue value) { + this(value, null); + } + + Value(AbstractConfigValue value, String origText) { + super(TokenType.VALUE, value.origin(), origText); + this.value = value; + } + + AbstractConfigValue value() { + return value; + } + + @Override + public String toString() { + if (value().resolveStatus() == ResolveStatus.RESOLVED) + return "'" + value().unwrapped() + "' (" + value.valueType().name() + ")"; + else + return "'' (" + value.valueType().name() + ")"; + } + + @Override + protected boolean canEqual(Object other) { + return other instanceof Value; + } + + @Override + public boolean equals(Object other) { + return super.equals(other) && ((Value) other).value.equals(value); + } + + @Override + public int hashCode() { + return 41 * (41 + super.hashCode()) + value.hashCode(); + } + } + + static private class Line extends Token { + Line(ConfigOrigin origin) { + super(TokenType.NEWLINE, origin); + } + + @Override + public String toString() { + return "'\\n'@" + lineNumber(); + } + + @Override + protected boolean canEqual(Object other) { + return other instanceof Line; + } + + @Override + public boolean equals(Object other) { + return super.equals(other) && ((Line) other).lineNumber() == lineNumber(); + } + + @Override + public int hashCode() { + return 41 * (41 + super.hashCode()) + lineNumber(); + } + + @Override + public String tokenText() { + return "\n"; + } + } + + // This is not a Value, because it requires special processing + static private class UnquotedText extends Token { + final private String value; + + UnquotedText(ConfigOrigin origin, String s) { + super(TokenType.UNQUOTED_TEXT, origin); + this.value = s; + } + + String value() { + return value; + } + + @Override + public String toString() { + return "'" + value + "'"; + } + + @Override + protected boolean canEqual(Object other) { + return other instanceof UnquotedText; + } + + @Override + public boolean equals(Object other) { + return super.equals(other) + && ((UnquotedText) other).value.equals(value); + } + + @Override + public int hashCode() { + return 41 * (41 + super.hashCode()) + value.hashCode(); + } + + @Override + public String tokenText() { + return value; + } + } + + static private class IgnoredWhitespace extends Token { + final private String value; + + IgnoredWhitespace(ConfigOrigin origin, String s) { + super(TokenType.IGNORED_WHITESPACE, origin); + this.value = s; + } + + @Override + public String toString() { return "'" + value + "' (WHITESPACE)"; } + + @Override + protected boolean canEqual(Object other) { + return other instanceof IgnoredWhitespace; + } + + @Override + public boolean equals(Object other) { + return super.equals(other) + && ((IgnoredWhitespace) other).value.equals(value); + } + + @Override + public int hashCode() { + return 41 * (41 + super.hashCode()) + value.hashCode(); + } + + @Override + public String tokenText() { + return value; + } + } + + static private class Problem extends Token { + final private String what; + final private String message; + final private boolean suggestQuotes; + final private Throwable cause; + + Problem(ConfigOrigin origin, String what, String message, boolean suggestQuotes, + Throwable cause) { + super(TokenType.PROBLEM, origin); + this.what = what; + this.message = message; + this.suggestQuotes = suggestQuotes; + this.cause = cause; + } + + String what() { + return what; + } + + String message() { + return message; + } + + boolean suggestQuotes() { + return suggestQuotes; + } + + Throwable cause() { + return cause; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('\''); + sb.append(what); + sb.append('\''); + sb.append(" ("); + sb.append(message); + sb.append(")"); + return sb.toString(); + } + + @Override + protected boolean canEqual(Object other) { + return other instanceof Problem; + } + + @Override + public boolean equals(Object other) { + return super.equals(other) && ((Problem) other).what.equals(what) + && ((Problem) other).message.equals(message) + && ((Problem) other).suggestQuotes == suggestQuotes + && ConfigImplUtil.equalsHandlingNull(((Problem) other).cause, cause); + } + + @Override + public int hashCode() { + int h = 41 * (41 + super.hashCode()); + h = 41 * (h + what.hashCode()); + h = 41 * (h + message.hashCode()); + h = 41 * (h + Boolean.valueOf(suggestQuotes).hashCode()); + if (cause != null) + h = 41 * (h + cause.hashCode()); + return h; + } + } + + static private abstract class Comment extends Token { + final private String text; + + Comment(ConfigOrigin origin, String text) { + super(TokenType.COMMENT, origin); + this.text = text; + } + + final static class DoubleSlashComment extends Comment { + DoubleSlashComment(ConfigOrigin origin, String text) { + super(origin, text); + } + + @Override + public String tokenText() { + return "//" + super.text; + } + } + + final static class HashComment extends Comment { + HashComment(ConfigOrigin origin, String text) { + super(origin, text); + } + + @Override + public String tokenText() { + return "#" + super.text; + } + } + + String text() { + return text; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("'#"); + sb.append(text); + sb.append("' (COMMENT)"); + return sb.toString(); + } + + @Override + protected boolean canEqual(Object other) { + return other instanceof Comment; + } + + @Override + public boolean equals(Object other) { + return super.equals(other) && ((Comment) other).text.equals(text); + } + + @Override + public int hashCode() { + int h = 41 * (41 + super.hashCode()); + h = 41 * (h + text.hashCode()); + return h; + } + } + + // This is not a Value, because it requires special processing + static private class Substitution extends Token { + final private boolean optional; + final private List value; + + Substitution(ConfigOrigin origin, boolean optional, List expression) { + super(TokenType.SUBSTITUTION, origin); + this.optional = optional; + this.value = expression; + } + + boolean optional() { + return optional; + } + + List value() { + return value; + } + + @Override + public String tokenText() { + return "${" + (this.optional? "?" : "") + Tokenizer.render(this.value.iterator()) + "}"; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Token t : value) { + sb.append(t.toString()); + } + return "'${" + sb.toString() + "}'"; + } + + @Override + protected boolean canEqual(Object other) { + return other instanceof Substitution; + } + + @Override + public boolean equals(Object other) { + return super.equals(other) + && ((Substitution) other).value.equals(value); + } + + @Override + public int hashCode() { + return 41 * (41 + super.hashCode()) + value.hashCode(); + } + } + + static boolean isValue(Token token) { + return token instanceof Value; + } + + static AbstractConfigValue getValue(Token token) { + if (token instanceof Value) { + return ((Value) token).value(); + } else { + throw new ConfigException.BugOrBroken( + "tried to get value of non-value token " + token); + } + } + + static boolean isValueWithType(Token t, ConfigValueType valueType) { + return isValue(t) && getValue(t).valueType() == valueType; + } + + static boolean isNewline(Token token) { + return token instanceof Line; + } + + static boolean isProblem(Token token) { + return token instanceof Problem; + } + + static String getProblemWhat(Token token) { + if (token instanceof Problem) { + return ((Problem) token).what(); + } else { + throw new ConfigException.BugOrBroken("tried to get problem what from " + token); + } + } + + static String getProblemMessage(Token token) { + if (token instanceof Problem) { + return ((Problem) token).message(); + } else { + throw new ConfigException.BugOrBroken("tried to get problem message from " + token); + } + } + + static boolean getProblemSuggestQuotes(Token token) { + if (token instanceof Problem) { + return ((Problem) token).suggestQuotes(); + } else { + throw new ConfigException.BugOrBroken("tried to get problem suggestQuotes from " + + token); + } + } + + static Throwable getProblemCause(Token token) { + if (token instanceof Problem) { + return ((Problem) token).cause(); + } else { + throw new ConfigException.BugOrBroken("tried to get problem cause from " + token); + } + } + + static boolean isComment(Token token) { + return token instanceof Comment; + } + + static String getCommentText(Token token) { + if (token instanceof Comment) { + return ((Comment) token).text(); + } else { + throw new ConfigException.BugOrBroken("tried to get comment text from " + token); + } + } + + static boolean isUnquotedText(Token token) { + return token instanceof UnquotedText; + } + + static String getUnquotedText(Token token) { + if (token instanceof UnquotedText) { + return ((UnquotedText) token).value(); + } else { + throw new ConfigException.BugOrBroken( + "tried to get unquoted text from " + token); + } + } + + static boolean isIgnoredWhitespace(Token token) { + return token instanceof IgnoredWhitespace; + } + + static boolean isSubstitution(Token token) { + return token instanceof Substitution; + } + + static List getSubstitutionPathExpression(Token token) { + if (token instanceof Substitution) { + return ((Substitution) token).value(); + } else { + throw new ConfigException.BugOrBroken( + "tried to get substitution from " + token); + } + } + + static boolean getSubstitutionOptional(Token token) { + if (token instanceof Substitution) { + return ((Substitution) token).optional(); + } else { + throw new ConfigException.BugOrBroken("tried to get substitution optionality from " + + token); + } + } + + final static Token START = Token.newWithoutOrigin(TokenType.START, "start of file", ""); + final static Token END = Token.newWithoutOrigin(TokenType.END, "end of file", ""); + final static Token COMMA = Token.newWithoutOrigin(TokenType.COMMA, "','", ","); + final static Token EQUALS = Token.newWithoutOrigin(TokenType.EQUALS, "'='", "="); + final static Token COLON = Token.newWithoutOrigin(TokenType.COLON, "':'", ":"); + final static Token OPEN_CURLY = Token.newWithoutOrigin(TokenType.OPEN_CURLY, "'{'", "{"); + final static Token CLOSE_CURLY = Token.newWithoutOrigin(TokenType.CLOSE_CURLY, "'}'", "}"); + final static Token OPEN_SQUARE = Token.newWithoutOrigin(TokenType.OPEN_SQUARE, "'['", "["); + final static Token CLOSE_SQUARE = Token.newWithoutOrigin(TokenType.CLOSE_SQUARE, "']'", "]"); + final static Token PLUS_EQUALS = Token.newWithoutOrigin(TokenType.PLUS_EQUALS, "'+='", "+="); + + static Token newLine(ConfigOrigin origin) { + return new Line(origin); + } + + static Token newProblem(ConfigOrigin origin, String what, String message, + boolean suggestQuotes, Throwable cause) { + return new Problem(origin, what, message, suggestQuotes, cause); + } + + static Token newCommentDoubleSlash(ConfigOrigin origin, String text) { + return new Comment.DoubleSlashComment(origin, text); + } + + static Token newCommentHash(ConfigOrigin origin, String text) { + return new Comment.HashComment(origin, text); + } + + static Token newUnquotedText(ConfigOrigin origin, String s) { + return new UnquotedText(origin, s); + } + + static Token newIgnoredWhitespace(ConfigOrigin origin, String s) { + return new IgnoredWhitespace(origin, s); + } + + static Token newSubstitution(ConfigOrigin origin, boolean optional, List expression) { + return new Substitution(origin, optional, expression); + } + + static Token newValue(AbstractConfigValue value) { + return new Value(value); + } + static Token newValue(AbstractConfigValue value, String origText) { + return new Value(value, origText); + } + + static Token newString(ConfigOrigin origin, String value, String origText) { + return newValue(new ConfigString.Quoted(origin, value), origText); + } + + static Token newInt(ConfigOrigin origin, int value, String origText) { + return newValue(ConfigNumber.newNumber(origin, value, + origText), origText); + } + + static Token newDouble(ConfigOrigin origin, double value, + String origText) { + return newValue(ConfigNumber.newNumber(origin, value, + origText), origText); + } + + static Token newLong(ConfigOrigin origin, long value, String origText) { + return newValue(ConfigNumber.newNumber(origin, value, + origText), origText); + } + + static Token newNull(ConfigOrigin origin) { + return newValue(new ConfigNull(origin), "null"); + } + + static Token newBoolean(ConfigOrigin origin, boolean value) { + return newValue(new ConfigBoolean(origin, value), "" + value); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Unmergeable.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Unmergeable.java new file mode 100644 index 0000000..a59256c --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/Unmergeable.java @@ -0,0 +1,16 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.impl; + +import java.util.Collection; + +/** + * Interface that tags a ConfigValue that is not mergeable until after + * substitutions are resolved. Basically these are special ConfigValue that + * never appear in a resolved tree, like {@link ConfigSubstitution} and + * {@link ConfigDelayedMerge}. + */ +interface Unmergeable { + Collection unmergedValues(); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/package.html b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/package.html new file mode 100644 index 0000000..52592b0 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/impl/package.html @@ -0,0 +1,25 @@ + + + + + + + + +

+Internal implementation details that can change ABI at any time. +

+ +

+Please check out the {@link com.typesafe.config.Config public API} instead, unless +you're interested in browsing implementation details. None of the ABI +under impl has any guarantees; it will change whenever someone +feels like changing it. If you feel you need access to something +in impl, please +file a feature request. +

+ + + diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/package.html b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/package.html new file mode 100644 index 0000000..8f5d9f4 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/package.html @@ -0,0 +1,58 @@ + + + + + + + + +

+An API for loading and using configuration files, see the project site +for more information. +

+ +

+Typically you would load configuration with a static method from {@link com.typesafe.config.ConfigFactory} and then use +it with methods in the {@link com.typesafe.config.Config} interface. Configuration may be in the form of JSON files, +Java properties, or HOCON files; you may also +build your own configuration in code or from your own file formats. +

+ +

+An application can simply call {@link com.typesafe.config.ConfigFactory#load()} and place +its configuration in "application.conf" on the classpath. +If you use the default configuration from {@link com.typesafe.config.ConfigFactory#load()} +there's no need to pass a configuration to your libraries +and frameworks, as long as they all default to this same default, which they should. +
Example application code: Java and Scala. +
Showing a couple of more special-purpose features, a more complex example: Java and Scala. +

+ +

+A library or framework should ship a file "reference.conf" in its jar, and allow an application to pass in a +{@link com.typesafe.config.Config} to be used for the library. If no {@link com.typesafe.config.Config} is provided, +call {@link com.typesafe.config.ConfigFactory#load()} +to get the default one. Typically a library might offer two constructors, one with a Config parameter +and one which uses {@link com.typesafe.config.ConfigFactory#load()}. +
Example library code: Java and Scala. +

+ +

+Check out the full examples directory on GitHub. +

+ +

+What else to read: +

    +
  • The overview documentation for interface {@link com.typesafe.config.Config}.
  • +
  • The README for the library.
  • +
  • If you want to use .conf files in addition to .json and .properties, + see the README for some short examples + and the full HOCON spec for the long version.
  • +
+

+ + + diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/ConfigDocument.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/ConfigDocument.java new file mode 100644 index 0000000..99855da --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/ConfigDocument.java @@ -0,0 +1,82 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.parser; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigValue; + +/** + * Represents an individual HOCON or JSON file, preserving all + * formatting and syntax details. This can be used to replace + * individual values and exactly render the original text of the + * input. + * + *

+ * Because this object is immutable, it is safe to use from multiple threads and + * there's no need for "defensive copies." + * + *

+ * Do not implement interface {@code ConfigDocument}; it should only be + * implemented by the config library. Arbitrary implementations will not work + * because the library internals assume a specific concrete implementation. + * Also, this interface is likely to grow new methods over time, so third-party + * implementations will break. + */ +public interface ConfigDocument { + /** + * Returns a new ConfigDocument that is a copy of the current ConfigDocument, + * but with the desired value set at the desired path. If the path exists, it will + * remove all duplicates before the final occurrence of the path, and replace the value + * at the final occurrence of the path. If the path does not exist, it will be added. If + * the document has an array as the root value, an exception will be thrown. + * + * @param path the path at which to set the desired value + * @param newValue the value to set at the desired path, represented as a string. This + * string will be parsed into a ConfigNode using the same options used to + * parse the entire document, and the text will be inserted + * as-is into the document. Leading and trailing comments, whitespace, or + * newlines are not allowed, and if present an exception will be thrown. + * If a concatenation is passed in for newValue but the document was parsed + * with JSON, the first value in the concatenation will be parsed and inserted + * into the ConfigDocument. + * @return a copy of the ConfigDocument with the desired value at the desired path + */ + ConfigDocument withValueText(String path, String newValue); + + /** + * Returns a new ConfigDocument that is a copy of the current + * ConfigDocument, but with the desired value set at the + * desired path. Works like {@link #withValueText(String, String)}, + * but takes a ConfigValue instead of a string. + * + * @param path the path at which to set the desired value + * @param newValue the value to set at the desired path, represented as a ConfigValue. + * The rendered text of the ConfigValue will be inserted into the + * ConfigDocument. + * @return a copy of the ConfigDocument with the desired value at the desired path + */ + ConfigDocument withValue(String path, ConfigValue newValue); + + /** + * Returns a new ConfigDocument that is a copy of the current ConfigDocument, but with + * all values at the desired path removed. If the path does not exist in the document, + * a copy of the current document will be returned. If there is an array at the root, an exception + * will be thrown. + * + * @param path the path to remove from the document + * @return a copy of the ConfigDocument with the desired value removed from the document. + */ + ConfigDocument withoutPath(String path); + + /** + * Returns a boolean indicating whether or not a ConfigDocument has a value at the desired path. + * null counts as a value for purposes of this check. + * @param path the path to check + * @return true if the path exists in the document, otherwise false + */ + boolean hasPath(String path); + + /** + * The original text of the input, modified if necessary with + * any replaced or added values. + * @return the modified original text + */ + String render(); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/ConfigDocumentFactory.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/ConfigDocumentFactory.java new file mode 100644 index 0000000..5a3bb4d --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/ConfigDocumentFactory.java @@ -0,0 +1,93 @@ +package com.drtshock.playervaults.lib.com.typesafe.config.parser; + +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigException; +import com.drtshock.playervaults.lib.com.typesafe.config.ConfigParseOptions; +import com.drtshock.playervaults.lib.com.typesafe.config.impl.Parseable; + +import java.io.File; +import java.io.Reader; + +/** + * Factory for creating {@link + * ConfigDocument} instances. + */ +public final class ConfigDocumentFactory { + + /** + * Parses a Reader into a ConfigDocument instance. + * + * @param reader + * the reader to parse + * @param options + * parse options to control how the reader is interpreted + * @return the parsed configuration + * @throws ConfigException on IO or parse errors + */ + public static ConfigDocument parseReader(Reader reader, ConfigParseOptions options) { + return Parseable.newReader(reader, options).parseConfigDocument(); + } + + /** + * Parses a reader into a Config instance as with + * {@link #parseReader(Reader,ConfigParseOptions)} but always uses the + * default parse options. + * + * @param reader + * the reader to parse + * @return the parsed configuration + * @throws ConfigException on IO or parse errors + */ + public static ConfigDocument parseReader(Reader reader) { + return parseReader(reader, ConfigParseOptions.defaults()); + } + + /** + * Parses a file into a ConfigDocument instance. + * + * @param file + * the file to parse + * @param options + * parse options to control how the file is interpreted + * @return the parsed configuration + * @throws ConfigException on IO or parse errors + */ + public static ConfigDocument parseFile(File file, ConfigParseOptions options) { + return Parseable.newFile(file, options).parseConfigDocument(); + } + + /** + * Parses a file into a ConfigDocument instance as with + * {@link #parseFile(File,ConfigParseOptions)} but always uses the + * default parse options. + * + * @param file + * the file to parse + * @return the parsed configuration + * @throws ConfigException on IO or parse errors + */ + public static ConfigDocument parseFile(File file) { + return parseFile(file, ConfigParseOptions.defaults()); + } + + /** + * Parses a string which should be valid HOCON or JSON. + * + * @param s string to parse + * @param options parse options + * @return the parsed configuration + */ + public static ConfigDocument parseString(String s, ConfigParseOptions options) { + return Parseable.newString(s, options).parseConfigDocument(); + } + + /** + * Parses a string (which should be valid HOCON or JSON). Uses the + * default parse options. + * + * @param s string to parse + * @return the parsed configuration + */ + public static ConfigDocument parseString(String s) { + return parseString(s, ConfigParseOptions.defaults()); + } +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/ConfigNode.java b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/ConfigNode.java new file mode 100644 index 0000000..7d03b19 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/ConfigNode.java @@ -0,0 +1,35 @@ +/** + * Copyright (C) 2015 Typesafe Inc. + */ +package com.drtshock.playervaults.lib.com.typesafe.config.parser; + +/** + * A node in the syntax tree for a HOCON or JSON document. + * + *

+ * Note: at present there is no way to obtain an instance of this interface, so + * please ignore it. A future release will make syntax tree nodes available in + * the public API. If you are interested in working on it, please see: https://github.com/lightbend/config/issues/300 + * + *

+ * Because this object is immutable, it is safe to use from multiple threads and + * there's no need for "defensive copies." + * + *

+ * Do not implement interface {@code ConfigNode}; it should only be + * implemented by the config library. Arbitrary implementations will not work + * because the library internals assume a specific concrete implementation. + * Also, this interface is likely to grow new methods over time, so third-party + * implementations will break. + */ +public interface ConfigNode { + /** + * The original text of the input which was used to form this particular + * node. + * + * @return the original text used to form this node as a String + */ + public String render(); +} diff --git a/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/package.html b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/package.html new file mode 100644 index 0000000..b0e7b3c --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/lib/com/typesafe/config/parser/package.html @@ -0,0 +1,33 @@ + + + + + + + + +

+This package supplies a raw parser and syntax tree for individual HOCON and JSON +files. You do not want this package for everyday config in your app: see +the com.typesafe.config package instead. You would use the raw +parser if you're doing something like reading, modifying, and re-saving a config +file. For info on the main config API this parser is a part of, +see the project site. +

+ +

+ For working with the raw syntax tree, some important classes are: +

    +
  • {@link com.typesafe.config.parser.ConfigDocument} - a loaded HOCON + or JSON document
  • +
  • {@link com.typesafe.config.parser.ConfigDocumentFactory} - + static methods to instantiate a document
  • +
  • {@link com.typesafe.config.parser.ConfigNode} - syntax node + in a document
  • +
+

+ + + diff --git a/src/main/java/com/drtshock/playervaults/listeners/SignListener.java b/src/main/java/com/drtshock/playervaults/listeners/SignListener.java index 3310806..6ab46c4 100644 --- a/src/main/java/com/drtshock/playervaults/listeners/SignListener.java +++ b/src/main/java/com/drtshock/playervaults/listeners/SignListener.java @@ -54,7 +54,7 @@ public class SignListener implements Listener { @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onInteract(PlayerInteractEvent event) { - if (!PlayerVaults.getInstance().getConfig().getBoolean("signs-enabled")) { + if (!PlayerVaults.getInstance().getConf().isSigns()) { return; } Player player = event.getPlayer(); @@ -146,7 +146,7 @@ public class SignListener implements Listener { @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onBlockPhysics(BlockPhysicsEvent event) { - if (!PlayerVaults.getInstance().getConfig().getBoolean("signs-enabled")) { + if (!PlayerVaults.getInstance().getConf().isSigns()) { return; } blockChangeCheck(event.getBlock().getLocation()); @@ -154,7 +154,7 @@ public class SignListener implements Listener { @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityChangeBlock(EntityChangeBlockEvent event) { - if (!PlayerVaults.getInstance().getConfig().getBoolean("signs-enabled")) { + if (!PlayerVaults.getInstance().getConf().isSigns()) { return; } blockChangeCheck(event.getBlock().getLocation()); @@ -162,7 +162,7 @@ public class SignListener implements Listener { @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onBlockBreak(BlockBreakEvent event) { - if (!PlayerVaults.getInstance().getConfig().getBoolean("signs-enabled")) { + if (!PlayerVaults.getInstance().getConf().isSigns()) { return; } blockChangeCheck(event.getBlock().getLocation()); diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/EconomyOperations.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/EconomyOperations.java index 93d9a55..bb6a558 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/EconomyOperations.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/EconomyOperations.java @@ -33,9 +33,6 @@ import java.io.File; */ public class EconomyOperations { - private static final PlayerVaults PLUGIN = PlayerVaults.getInstance(); - private static final FileConfiguration BUKKIT_CONFIG = PLUGIN.getConfig(); - /** * Have a player pay to open a vault. * @@ -44,14 +41,14 @@ public class EconomyOperations { * @return The transaction success. */ public static boolean payToOpen(Player player, int number) { - if (!PLUGIN.isEconomyEnabled() || player.hasPermission("playervaults.free")) { + if (!PlayerVaults.getInstance().isEconomyEnabled() || player.hasPermission("playervaults.free")) { return true; } if (!VaultManager.getInstance().vaultExists(player.getUniqueId().toString(), number)) { return payToCreate(player); } else { - double cost = BUKKIT_CONFIG.getDouble("economy.cost-to-open", 10); + double cost = PlayerVaults.getInstance().getConf().getEconomy().getFeeToOpen(); EconomyResponse resp = PlayerVaults.getInstance().getEconomy().withdrawPlayer(player, cost); if (resp.transactionSuccess()) { player.sendMessage(Lang.TITLE.toString() + Lang.COST_TO_OPEN.toString().replaceAll("%price", "" + cost)); @@ -69,11 +66,11 @@ public class EconomyOperations { * @return The transaction success */ public static boolean payToCreate(Player player) { - if (!PLUGIN.isEconomyEnabled() || player.hasPermission("playervaults.free")) { + if (!PlayerVaults.getInstance().isEconomyEnabled() || player.hasPermission("playervaults.free")) { return true; } - double cost = BUKKIT_CONFIG.getDouble("economy.cost-to-create", 100); + double cost = PlayerVaults.getInstance().getConf().getEconomy().getFeeToCreate(); EconomyResponse resp = PlayerVaults.getInstance().getEconomy().withdrawPlayer(player, cost); if (resp.transactionSuccess()) { player.sendMessage(Lang.TITLE.toString() + Lang.COST_TO_CREATE.toString().replaceAll("%price", "" + cost)); @@ -91,11 +88,11 @@ public class EconomyOperations { * @return The transaction success. */ public static boolean refundOnDelete(Player player, int number) { - if (!PLUGIN.isEconomyEnabled() || player.hasPermission("playervaults.free")) { + if (!PlayerVaults.getInstance().isEconomyEnabled() || player.hasPermission("playervaults.free")) { return true; } - File playerFile = new File(PLUGIN.getVaultData(), player.getUniqueId().toString() + ".yml"); + File playerFile = new File(PlayerVaults.getInstance().getVaultData(), player.getUniqueId().toString() + ".yml"); if (playerFile.exists()) { YamlConfiguration playerData = YamlConfiguration.loadConfiguration(playerFile); if (playerData.getString("vault" + number) == null) { @@ -107,7 +104,7 @@ public class EconomyOperations { return false; } - double cost = BUKKIT_CONFIG.getDouble("economy.refund-on-delete"); + double cost = PlayerVaults.getInstance().getConf().getEconomy().getRefundOnDelete(); EconomyResponse resp = PlayerVaults.getInstance().getEconomy().depositPlayer(player, cost); if (resp.transactionSuccess()) { player.sendMessage(Lang.TITLE.toString() + Lang.REFUND_AMOUNT.toString().replaceAll("%price", String.valueOf(cost))); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml deleted file mode 100644 index 0a066cd..0000000 --- a/src/main/resources/config.yml +++ /dev/null @@ -1,56 +0,0 @@ -# PlayerVaults -# Created by: https://github.com/drtshock/PlayerVaults/graphs/contributors/ -# Resource page: https://www.spigotmc.org/resources/51204/ -# Discord server: https://discordapp.com/invite/JZcWDEt/ -# Made with love <3 - -# Debug Mode -# This will print everything the plugin is doing to console. -# You should only enable this if you're working with a contributor to fix something. -debug: false - -# Language -# This determines which language file the plugin will read from. -# Valid options are (don't include .yml): bulgarian, dutch, english, german, turkish, russian -language: english - -# Signs -# This will determine whether vault signs are enabled. -# If you don't know what this is or if it's for you, see the resource page. -signs-enabled: false - -# Economy -# These are all of the settings for the economy integration. (Requires Vault) -# Bypass permission is: playervaults.free -economy: - enabled: false - cost-to-create: 100 - cost-to-open: 10 - refund-on-delete: 50 - -# Blocked Items -# This will allow you to block specific materials from vaults. -# Bypass permission is: playervaults.bypassblockeditems -blockitems: true - -# Material list for blocked items (does not support ID's), only effective if the feature is enabled. -# If you don't know material names: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html -blocked-items: - - PUMPKIN - - DIAMOND_BLOCK - -# Cleanup -# Enabling this will purge vaults that haven't been touched in the specified time frame. -# Reminder: This is only checked during startup. -# This will not lag your server or touch the backups folder. -# The time format is in days. -cleanup: - enable: false - lastEdit: 30 - -# Backups -# Enabling this will create backups of vaults automagically. -backups: - enabled: true - -max-vault-amount-perm-to-test: 99 \ No newline at end of file diff --git a/src/main/resources/credit/apache2-license.txt b/src/main/resources/credit/apache2-license.txt new file mode 100644 index 0000000..e25e752 --- /dev/null +++ b/src/main/resources/credit/apache2-license.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/src/main/resources/credit/credit.txt b/src/main/resources/credit/credit.txt new file mode 100644 index 0000000..073bbd7 --- /dev/null +++ b/src/main/resources/credit/credit.txt @@ -0,0 +1,5 @@ +This project overall is licensed under the GNU GENERAL PUBLIC LICENSE version 3 +and can be found at https://github.com/drtshock/PlayerVaults + +This project also contains Typesafe Config, which is licensed under the Apache License, Version 2.0 +and can be found at https://github.com/lightbend/config/ \ No newline at end of file diff --git a/src/main/resources/credit/gplv3-license.txt b/src/main/resources/credit/gplv3-license.txt new file mode 100644 index 0000000..47d403b --- /dev/null +++ b/src/main/resources/credit/gplv3-license.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + PlayerVaultsX Copyright (C) 2013 Trent Hensler + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +.