New config

This commit is contained in:
CmdrKittens
2020-04-09 00:18:17 -04:00
parent 97d977b996
commit 2286fd7656
113 changed files with 19303 additions and 100 deletions
+7
View File
@@ -23,6 +23,7 @@
<includes> <includes>
<include>*.yml</include> <include>*.yml</include>
<include>lang/*.yml</include> <include>lang/*.yml</include>
<include>credit/*.txt</include>
</includes> </includes>
</resource> </resource>
</resources> </resources>
@@ -62,5 +63,11 @@
<version>1.7</version> <version>1.7</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.3.0</version>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>
@@ -23,6 +23,8 @@ import com.drtshock.playervaults.commands.DeleteCommand;
import com.drtshock.playervaults.commands.SignCommand; import com.drtshock.playervaults.commands.SignCommand;
import com.drtshock.playervaults.commands.SignSetInfo; import com.drtshock.playervaults.commands.SignSetInfo;
import com.drtshock.playervaults.commands.VaultCommand; 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.Listeners;
import com.drtshock.playervaults.listeners.SignListener; import com.drtshock.playervaults.listeners.SignListener;
import com.drtshock.playervaults.listeners.VaultPreloadListener; import com.drtshock.playervaults.listeners.VaultPreloadListener;
@@ -53,6 +55,7 @@ import org.bukkit.scheduler.BukkitRunnable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@@ -83,6 +86,7 @@ public class PlayerVaults extends JavaPlugin {
private String _versionString; private String _versionString;
private int maxVaultAmountPermTest; private int maxVaultAmountPermTest;
private Metrics metrics; private Metrics metrics;
private Config config = new Config();
public static PlayerVaults getInstance() { public static PlayerVaults getInstance() {
return instance; return instance;
@@ -105,7 +109,7 @@ public class PlayerVaults extends JavaPlugin {
public void onEnable() { public void onEnable() {
instance = this; instance = this;
loadConfig(); loadConfig();
DEBUG = getConfig().getBoolean("debug", false); DEBUG = getConf().isDebug();
debug("config", System.currentTimeMillis()); debug("config", System.currentTimeMillis());
uuidData = new File(this.getDataFolder(), "uuidvaults"); uuidData = new File(this.getDataFolder(), "uuidvaults");
vaultData = new File(this.getDataFolder(), "base64vaults"); 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 VaultPreloadListener(), this);
getServer().getPluginManager().registerEvents(new SignListener(this), this); getServer().getPluginManager().registerEvents(new SignListener(this), this);
debug("registering listeners", System.currentTimeMillis()); debug("registering listeners", System.currentTimeMillis());
this.backupsEnabled = this.getConfig().getBoolean("backups.enabled", true); this.backupsEnabled = this.getConf().getStorage().getFlatFile().isBackups();
this.maxVaultAmountPermTest = this.getConfig().getInt("max-vault-amount-perm-to-test", 99); this.maxVaultAmountPermTest = this.getConf().getMaxVaultAmountPermTest();
loadSigns(); loadSigns();
debug("loaded signs", System.currentTimeMillis()); debug("loaded signs", System.currentTimeMillis());
debug("check update", System.currentTimeMillis()); debug("check update", System.currentTimeMillis());
@@ -136,8 +140,8 @@ public class PlayerVaults extends JavaPlugin {
useVault = setupEconomy(); useVault = setupEconomy();
debug("setup economy", System.currentTimeMillis()); debug("setup economy", System.currentTimeMillis());
if (getConfig().getBoolean("cleanup.enable", false)) { if (getConf().getPurge().isEnabled()) {
getServer().getScheduler().runTaskAsynchronously(this, new Cleanup(getConfig().getInt("cleanup.lastEdit", 30))); getServer().getScheduler().runTaskAsynchronously(this, new Cleanup(getConf().getPurge().getDaysSinceLastEdit()));
} }
new BukkitRunnable() { new BukkitRunnable() {
@@ -200,14 +204,14 @@ public class PlayerVaults extends JavaPlugin {
} }
} }
this.metricsSimplePie("signs", () -> getConfig().getBoolean("signs-enabled") ? "enabled" : "disabled"); this.metricsSimplePie("signs", () -> getConf().isSigns() ? "enabled" : "disabled");
this.metricsSimplePie("cleanup", () -> getConfig().getBoolean("cleanup.enable") ? "enabled" : "disabled"); this.metricsSimplePie("cleanup", () -> getConf().getPurge().isEnabled() ? "enabled" : "disabled");
this.metricsSimplePie("language", () -> getConfig().getString("language", "english")); this.metricsSimplePie("language", () -> getConf().getLanguage());
this.metricsDrillPie("block_items", () -> { this.metricsDrillPie("block_items", () -> {
Map<String, Map<String, Integer>> map = new HashMap<>(); Map<String, Map<String, Integer>> map = new HashMap<>();
Map<String, Integer> entry = new HashMap<>(); Map<String, Integer> entry = new HashMap<>();
if (getConfig().getBoolean("blockitems")) { if (getConf().getItemBlocking().isEnabled()) {
for (Material material : blockedMats) { for (Material material : blockedMats) {
entry.put(material.toString(), 1); entry.put(material.toString(), 1);
} }
@@ -215,7 +219,7 @@ public class PlayerVaults extends JavaPlugin {
if (entry.isEmpty()) { if (entry.isEmpty()) {
entry.put("none", 1); entry.put("none", 1);
} }
map.put(getConfig().getBoolean("blockitems") ? "enabled" : "disabled", entry); map.put(getConf().getItemBlocking().isEnabled() ? "enabled" : "disabled", entry);
return map; return map;
}); });
@@ -265,7 +269,7 @@ public class PlayerVaults extends JavaPlugin {
} }
} }
if (getConfig().getBoolean("cleanup.enable", false)) { if (getConf().getPurge().isEnabled()) {
saveSignsFile(); saveSignsFile();
} }
} }
@@ -297,12 +301,27 @@ public class PlayerVaults extends JavaPlugin {
} }
private void loadConfig() { 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. // Clear just in case this is a reload.
blockedMats.clear(); blockedMats.clear();
if (getConfig().getBoolean("blockitems", false) && getConfig().contains("blocked-items")) { if (getConf().getItemBlocking().isEnabled()) {
for (String s : getConfig().getStringList("blocked-items")) { for (String s : getConf().getItemBlocking().getList()) {
Material mat = Material.matchMaterial(s); Material mat = Material.matchMaterial(s);
if (mat != null) { if (mat != null) {
blockedMats.add(mat); blockedMats.add(mat);
@@ -312,6 +331,10 @@ public class PlayerVaults extends JavaPlugin {
} }
} }
public Config getConf() {
return this.config;
}
private void loadSigns() { private void loadSigns() {
File signs = new File(getDataFolder(), "signs.yml"); File signs = new File(getDataFolder(), "signs.yml");
if (!signs.exists()) { if (!signs.exists()) {
@@ -328,7 +351,7 @@ public class PlayerVaults extends JavaPlugin {
} }
private void reloadSigns() { private void reloadSigns() {
if (!getConfig().getBoolean("signs-enabled")) { if (!getConf().isSigns()) {
return; return;
} }
if (!signsFile.exists()) loadSigns(); if (!signsFile.exists()) loadSigns();
@@ -358,7 +381,7 @@ public class PlayerVaults extends JavaPlugin {
} }
private void saveSignsFile() { private void saveSignsFile() {
if (!getConfig().getBoolean("signs-enabled")) { if (!getConf().isSigns()) {
return; 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 <T> void setInConfig(String path, T object, YamlConfiguration conf) {
conf.set(path, object);
}
public void loadLang() { public void loadLang() {
File folder = new File(getDataFolder(), "lang"); File folder = new File(getDataFolder(), "lang");
if (!folder.exists()) { if (!folder.exists()) {
folder.mkdir(); folder.mkdir();
} }
String definedLanguage = getConfig().getString("language", "english"); String definedLanguage = getConf().getLanguage();
// Save as default just incase. // Save as default just incase.
File english = null; File english = null;
@@ -444,7 +456,7 @@ public class PlayerVaults extends JavaPlugin {
} }
public boolean isEconomyEnabled() { public boolean isEconomyEnabled() {
return this.getConfig().getBoolean("economy.enabled", false) && this.useVault; return this.getConf().getEconomy().isEnabled() && this.useVault;
} }
public File getVaultData() { public File getVaultData() {
@@ -30,7 +30,7 @@ public class SignCommand implements CommandExecutor {
@Override @Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender.hasPermission("playervaults.signs.set")) { 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()); sender.sendMessage(Lang.TITLE.toString() + Lang.SIGNS_DISABLED.toString());
return true; return true;
} }
@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<Class<?>> 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<String, ConfigValue> 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<Object>((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<Field> getFields(Class<?> clazz) {
return Loader.getFields(new ArrayList<>(), clazz);
}
private static @NonNull List<Field> getFields(@NonNull List<Field> fields, @NonNull Class<?> clazz) {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
if (clazz.getSuperclass() != null) {
Loader.getFields(fields, clazz.getSuperclass());
}
return fields;
}
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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();
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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();
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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 {
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String> list = new ArrayList<String>() {
{
this.add("PUMPKIN");
this.add("DIAMOND_BLOCK");
}
};
public boolean isEnabled() {
return this.enabled;
}
public List<String> 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;
}
}
File diff suppressed because it is too large Load Diff
@@ -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:
*
* <pre>
* Config configSource = ConfigFactory.load().getConfig("foo");
* FooConfig config = ConfigBeanFactory.create(configSource, FooConfig.class);
* </pre>
*
* The Java class should follow JavaBean conventions. Field types
* can be any of the types you can normally get from a {@link
* Config}, including <code>java.time.Duration</code> 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 <code>foo-bar</code> becomes JavaBean
* setter <code>setFooBar</code>.
*
* @since 1.3.0
*
* @param config source of config information
* @param clazz class to be instantiated
* @param <T> 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 <code>Config</code>
*/
public static <T> T create(Config config, Class<T> clazz) {
return ConfigBeanImpl.createInternal(config, clazz);
}
}
@@ -0,0 +1,447 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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
* <code>ConfigException</code>.
*/
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 <T> void setOriginField(T hasOriginField, Class<T> 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
* <code>checkValid()</code> 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 <code>getMessage()</code> 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<ValidationProblem> problems;
public ValidationFailed(Iterable<ValidationProblem> problems) {
super(makeMessage(problems), null);
this.problems = problems;
}
public Iterable<ValidationProblem> problems() {
return problems;
}
private static String makeMessage(Iterable<ValidationProblem> 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);
}
}
}
@@ -0,0 +1,56 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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.
*
* <p>
* <em>Do not implement this interface</em>; 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);
}
@@ -0,0 +1,50 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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);
}
@@ -0,0 +1,25 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.drtshock.playervaults.lib.com.typesafe.config;
/**
* Implement this <em>in addition to</em> {@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);
}
@@ -0,0 +1,27 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.drtshock.playervaults.lib.com.typesafe.config;
import java.io.File;
/**
* Implement this <em>in addition to</em> {@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);
}
@@ -0,0 +1,27 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.drtshock.playervaults.lib.com.typesafe.config;
import java.net.URL;
/**
* Implement this <em>in addition to</em> {@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);
}
@@ -0,0 +1,48 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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.
*
* <p>
* {@code ConfigList} implements {@code java.util.List<ConfigValue>} so you can
* use it like a regular Java list. Or call {@link #unwrapped()} to unwrap the
* list elements into plain Java values.
*
* <p>
* 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}.
*
* <p>
* The {@link ConfigValue#valueType} method on a list returns
* {@link ConfigValueType#LIST}.
*
* <p>
* <em>Do not implement {@code ConfigList}</em>; 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>, 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<Object> unwrapped();
@Override
ConfigList withOrigin(ConfigOrigin origin);
}
@@ -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);
}
@@ -0,0 +1,63 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com>
*/
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();
}
}
@@ -0,0 +1,72 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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.
*
* <p>
* <em>Do not implement this interface</em>; 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.
*
* <p>
* This associative operation may be used to combine configurations from
* multiple sources (such as multiple configuration files).
*
* <p>
* The semantics of merging are described in the <a
* href="https://github.com/lightbend/config/blob/master/HOCON.md">spec
* for HOCON</a>. Merging typically occurs when either the same object is
* created twice in the same file, or two config files are both loaded. For
* example:
*
* <pre>
* foo = { a: 42 }
* foo = { b: 43 }
* </pre>
*
* Here, the two objects are merged as if you had written:
*
* <pre>
* foo = { a: 42, b: 43 }
* </pre>
*
* <p>
* 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
* <code>object.withFallback(nonObject).withFallback(otherObject)</code>,
* then <code>otherObject</code> 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:
*
* <pre>
* foo = { a: 42 }
* foo = 10
* </pre>
*
* Here, the number 10 "wins" and the value of <code>foo</code> 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);
}
@@ -0,0 +1,135 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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 <code>{ "a" : 42 }</code> syntax.
*
* <p>
* An object may also be viewed as a {@link Config} by calling
* {@link ConfigObject#toConfig()}.
*
* <p>
* {@code ConfigObject} implements {@code java.util.Map<String, ConfigValue>} 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}.
*
* <p>
* 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}.
*
* <p>
* The {@link ConfigValue#valueType} method on an object returns
* {@link ConfigValueType#OBJECT}.
*
* <p>
* 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}.
*
* <p>
* 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.
*
* <p>
* Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert
* between path expressions and individual path elements (keys).
*
* <p>
* 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.
*
* <p>
* <em>Do not implement interface {@code ConfigObject}</em>; 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<String, ConfigValue> {
/**
* 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<String, Object> 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);
}
@@ -0,0 +1,118 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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
* <code>ConfigException.origin()</code> may return null.
*
* <p>
* 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.
*
* <p>
* <em>Do not implement this interface</em>; 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<String> 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}.
*
* <p>
* 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<String> 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}.
*
* <p>
* 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);
}
@@ -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);
}
}
@@ -0,0 +1,228 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.drtshock.playervaults.lib.com.typesafe.config;
/**
* A set of options related to parsing.
*
* <p>
* This object is immutable, so the "setters" return a new object.
*
* <p>
* Here is an example of creating a custom {@code ConfigParseOptions}:
*
* <pre>
* ConfigParseOptions options = ConfigParseOptions.defaults()
* .setSyntax(ConfigSyntax.JSON)
* .setAllowMissing(false)
* </pre>
*
*/
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 <code>ConfigParseOptions</code> 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,
* <code>Thread.currentThread().getContextClassLoader()</code> 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
* <code>Thread.currentThread().getContextClassLoader()</code>.
*
* @return class loader to use
*/
public ClassLoader getClassLoader() {
if (this.classLoader == null)
return Thread.currentThread().getContextClassLoader();
else
return this.classLoader;
}
}
@@ -0,0 +1,45 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.drtshock.playervaults.lib.com.typesafe.config;
/**
* An opaque handle to something that can be parsed, obtained from
* {@link ConfigIncludeContext}.
*
* <p>
* <em>Do not implement this interface</em>; 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();
}
@@ -0,0 +1,182 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.drtshock.playervaults.lib.com.typesafe.config;
/**
* <p>
* A set of options related to rendering a {@link ConfigValue}. Passed to
* {@link ConfigValue#render(ConfigRenderOptions)}.
*
* <p>
* Here is an example of creating a {@code ConfigRenderOptions}:
*
* <pre>
* ConfigRenderOptions options =
* ConfigRenderOptions.defaults().setComments(false)
* </pre>
*/
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.
*
* <p>
* {@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();
}
}
@@ -0,0 +1,176 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.drtshock.playervaults.lib.com.typesafe.config;
/**
* A set of options related to resolving substitutions. Substitutions use the
* <code>${foo.bar}</code> syntax and are documented in the <a
* href="https://github.com/lightbend/config/blob/master/HOCON.md">HOCON</a>
* spec.
* <p>
* Typically this class would be used with the method
* {@link Config#resolve(ConfigResolveOptions)}.
* <p>
* This object is immutable, so the "setters" return a new object.
* <p>
* Here is an example of creating a custom {@code ConfigResolveOptions}:
*
* <pre>
* ConfigResolveOptions options = ConfigResolveOptions.defaults()
* .setUseSystemEnvironment(false)
* </pre>
* <p>
* 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,
*
* <pre>
* ConfigResolveOptions options = ConfigResolveOptions.defaults()
* .appendResolver(primary)
* .appendResolver(secondary)
* .appendResolver(tertiary);
* </pre>
*
* 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;
}
};
}
@@ -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);
}
@@ -0,0 +1,36 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.drtshock.playervaults.lib.com.typesafe.config;
/**
* The syntax of a character stream (<a href="http://json.org">JSON</a>, <a
* href="https://github.com/lightbend/config/blob/master/HOCON.md">HOCON</a>
* aka ".conf", or <a href=
* "http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29"
* >Java properties</a>).
*
*/
public enum ConfigSyntax {
/**
* Pedantically strict <a href="http://json.org">JSON</a> format; no
* comments, no unexpected commas, no duplicate keys in the same object.
* Associated with the <code>.json</code> file extension and
* <code>application/json</code> Content-Type.
*/
JSON,
/**
* The JSON-superset <a
* href="https://github.com/lightbend/config/blob/master/HOCON.md"
* >HOCON</a> format. Associated with the <code>.conf</code> file extension
* and <code>application/hocon</code> Content-Type.
*/
CONF,
/**
* Standard <a href=
* "http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29"
* >Java properties</a> format. Associated with the <code>.properties</code>
* file extension and <code>text/x-java-properties</code> Content-Type.
*/
PROPERTIES;
}
@@ -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}.
* <p>
* 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}.
* <p>
* 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<String> 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}.
* <p>
* 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<String> splitPath(String path) {
return ConfigImplUtil.splitPath(path);
}
}
@@ -0,0 +1,122 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.drtshock.playervaults.lib.com.typesafe.config;
/**
* An immutable value, following the <a href="http://json.org">JSON</a> type
* schema.
*
* <p>
* Because this object is immutable, it is safe to use from multiple threads and
* there's no need for "defensive copies."
*
* <p>
* <em>Do not implement interface {@code ConfigValue}</em>; 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<String,Object>},
* {@code List<Object>}, 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.
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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);
}
@@ -0,0 +1,153 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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 <code>Boolean</code>, <code>Number</code>, <code>String</code>,
* <code>Map</code>, <code>Iterable</code>, or <code>null</code>. A
* <code>Map</code> must be a <code>Map</code> from String to more values
* that can be supplied to <code>fromAnyRef()</code>. An
* <code>Iterable</code> must iterate over more values that can be supplied
* to <code>fromAnyRef()</code>. A <code>Map</code> will become a
* {@link ConfigObject} and an <code>Iterable</code> will become a
* {@link ConfigList}. If the <code>Iterable</code> is not an ordered
* collection, results could be strange, since <code>ConfigList</code> is
* ordered.
*
* <p>
* In a <code>Map</code> passed to <code>fromAnyRef()</code>, the map's keys
* are plain keys, not path expressions. So if your <code>Map</code> 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".
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* If you pass in a <code>ConfigValue</code> to this
* function, it will be returned unmodified. (The
* <code>originDescription</code> will be ignored in this
* case.)
*
* <p>
* 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}.
*
* <p>
* If your <code>Map</code> 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 <code>Map</code> 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.
*
* <p>
* 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<String, ? extends Object> 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<? extends Object> 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.
*
* <p>
* 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<String, ? extends Object> 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<? extends Object> values) {
return fromIterable(values, null);
}
}
@@ -0,0 +1,12 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.drtshock.playervaults.lib.com.typesafe.config;
/**
* The type of a configuration value (following the <a
* href="http://json.org">JSON</a> type schema).
*/
public enum ConfigValueType {
OBJECT, LIST, NUMBER, BOOLEAN, NULL, STRING
}
@@ -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);
}
}
}
}
}
@@ -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 {
}
@@ -0,0 +1,29 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com>
*/
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<Token> tokens();
final public String render() {
StringBuilder origText = new StringBuilder();
Iterable<Token> 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();
}
}
@@ -0,0 +1,11 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com>
*/
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 {
}
@@ -0,0 +1,221 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<AbstractConfigValue> 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<? extends AbstractConfigValue> stack) {
if (stack.isEmpty())
throw new ConfigException.BugOrBroken(
"can't merge origins on empty list");
List<ConfigOrigin> origins = new ArrayList<ConfigOrigin>();
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<? extends AbstractConfigObject> 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<? extends String, ? extends ConfigValue> arg0) {
throw weAreImmutable("putAll");
}
@Override
public ConfigValue remove(Object arg0) {
throw weAreImmutable("remove");
}
@Override
public AbstractConfigObject withOrigin(ConfigOrigin origin) {
return (AbstractConfigObject) super.withOrigin(origin);
}
}
@@ -0,0 +1,411 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<? extends AbstractConfigValue> resolveSubstitutions(ResolveContext context, ResolveSource source)
throws NotPossibleToResolve {
return ResolveResult.make(context, this);
}
ResolveStatus resolveStatus() {
return ResolveStatus.RESOLVED;
}
protected static List<AbstractConfigValue> replaceChildInList(List<AbstractConfigValue> 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<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>(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<AbstractConfigValue> 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<AbstractConfigValue> stack) {
return new ConfigDelayedMerge(origin, stack);
}
protected final AbstractConfigValue mergedWithTheUnmergeable(
Collection<AbstractConfigValue> 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<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
newStack.addAll(stack);
newStack.addAll(fallback.unmergedValues());
return constructDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack);
}
private final AbstractConfigValue delayMerge(Collection<AbstractConfigValue> 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<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
newStack.addAll(stack);
newStack.add(fallback);
return constructDelayedMerge(AbstractConfigObject.mergeOrigins(newStack), newStack);
}
protected final AbstractConfigValue mergedWithObject(Collection<AbstractConfigValue> 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<AbstractConfigValue> 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<String, AbstractConfigValue> 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));
}
}
@@ -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 <T> type of the bean
* @param config config to use
* @param clazz class of the bean
* @return the bean instance
*/
public static <T> T createInternal(Config config, Class<T> 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<String, AbstractConfigValue> configProps = new HashMap<String, AbstractConfigValue>();
Map<String, String> originalNames = new HashMap<String, String>();
for (Map.Entry<String, ConfigValue> 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<PropertyDescriptor> beanProps = new ArrayList<PropertyDescriptor>();
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<ConfigException.ValidationProblem> problems = new ArrayList<ConfigException.ValidationProblem>();
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<String,Object> 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<Enum>) 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<Enum> enumValues = config.getEnumList((Class<Enum>) elementType, configPropName);
return enumValues;
} else if (hasAtLeastOneBeanProperty((Class<?>) elementType)) {
List<Object> beanList = new ArrayList<Object>();
List<? extends Config> 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);
}
}
@@ -0,0 +1,47 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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);
}
}
@@ -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<AbstractConfigValue> pieces;
ConfigConcatenation(ConfigOrigin origin, List<AbstractConfigValue> 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<ConfigConcatenation> 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<AbstractConfigValue> 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<AbstractConfigValue> consolidate(List<AbstractConfigValue> pieces) {
if (pieces.size() < 2) {
return pieces;
} else {
List<AbstractConfigValue> flattened = new ArrayList<AbstractConfigValue>(pieces.size());
for (AbstractConfigValue v : pieces) {
if (v instanceof ConfigConcatenation) {
flattened.addAll(((ConfigConcatenation) v).pieces);
} else {
flattened.add(v);
}
}
ArrayList<AbstractConfigValue> consolidated = new ArrayList<AbstractConfigValue>(
flattened.size());
for (AbstractConfigValue v : flattened) {
if (consolidated.isEmpty())
consolidated.add(v);
else
join(consolidated, v);
}
return consolidated;
}
}
static AbstractConfigValue concatenate(List<AbstractConfigValue> pieces) {
List<AbstractConfigValue> 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<? extends AbstractConfigValue> 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<AbstractConfigValue> resolved = new ArrayList<AbstractConfigValue>(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<? extends AbstractConfigValue> 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<AbstractConfigValue> 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<AbstractConfigValue> 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<AbstractConfigValue> newPieces = new ArrayList<AbstractConfigValue>();
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);
}
}
}
@@ -0,0 +1,342 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<AbstractConfigValue> stack;
ConfigDelayedMerge(ConfigOrigin origin, List<AbstractConfigValue> 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<? extends AbstractConfigValue> resolveSubstitutions(ResolveContext context, ResolveSource source)
throws NotPossibleToResolve {
return resolveSubstitutions(this, stack, context, source);
}
// static method also used by ConfigDelayedMergeObject
static ResolveResult<? extends AbstractConfigValue> resolveSubstitutions(ReplaceableMergeStack replaceable,
List<AbstractConfigValue> 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<? extends AbstractConfigValue> 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<AbstractConfigValue> stack, int skipping) {
List<AbstractConfigValue> 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<AbstractConfigValue> 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<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
for (AbstractConfigValue o : stack) {
newStack.add(o.relativized(prefix));
}
return new ConfigDelayedMerge(origin(), newStack);
}
// static utility shared with ConfigDelayedMergeObject
static boolean stackIgnoresFallbacks(List<AbstractConfigValue> 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<AbstractConfigValue> 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<AbstractConfigValue> 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<AbstractConfigValue> reversed = new ArrayList<AbstractConfigValue>();
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");
}
}
}
@@ -0,0 +1,326 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<AbstractConfigValue> stack;
ConfigDelayedMergeObject(ConfigOrigin origin, List<AbstractConfigValue> 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<? extends AbstractConfigObject> resolveSubstitutions(ResolveContext context, ResolveSource source)
throws NotPossibleToResolve {
ResolveResult<? extends AbstractConfigValue> 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<AbstractConfigValue> 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<AbstractConfigValue> newStack = new ArrayList<AbstractConfigValue>();
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<AbstractConfigValue> 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<String, Object> 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<java.util.Map.Entry<String, ConfigValue>> entrySet() {
throw notResolved();
}
@Override
public boolean isEmpty() {
throw notResolved();
}
@Override
public Set<String> keySet() {
throw notResolved();
}
@Override
public int size() {
throw notResolved();
}
@Override
public Collection<ConfigValue> 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");
}
}
@@ -0,0 +1,716 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com>
*/
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<Token> 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<Token> 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<Token> buffer;
final private Iterator<Token> 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<Token> tokens) {
lineNumber = 1;
buffer = new Stack<Token>();
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<AbstractConfigNode> 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<AbstractConfigNode> 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<AbstractConfigNode> nodes) {
// this trick is not done in JSON
if (flavor == ConfigSyntax.JSON)
return null;
// create only if we have value tokens
ArrayList<AbstractConfigNode> values = new ArrayList<AbstractConfigNode>();
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<Token>(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<Token> expression = new ArrayList<Token>();
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<AbstractConfigNode> 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<AbstractConfigNode> 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<AbstractConfigNode> objectNodes = new ArrayList<AbstractConfigNode>();
ArrayList<AbstractConfigNode> keyValueNodes;
HashMap<String, Boolean> keys = new HashMap<String, Boolean>();
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<AbstractConfigNode> includeNodes = new ArrayList<AbstractConfigNode>();
includeNodes.add(new ConfigNodeSingleToken(t));
objectNodes.add(parseInclude(includeNodes));
afterComma = false;
} else {
keyValueNodes = new ArrayList<AbstractConfigNode>();
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<AbstractConfigNode> children = new ArrayList<AbstractConfigNode>();
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<AbstractConfigNode> children = new ArrayList<AbstractConfigNode>();
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<AbstractConfigNode> nodes = new ArrayList<AbstractConfigNode>();
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");
}
}
}
}
}
@@ -0,0 +1,61 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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);
}
}
@@ -0,0 +1,477 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<ClassLoader> currentLoader;
private Map<String, Config> cache;
LoaderCache() {
this.currentSystemProperties = null;
this.currentLoader = new WeakReference<ClassLoader>(null);
this.cache = new HashMap<String, Config>();
}
// for now, caching as long as the loader remains the same,
// drop entire cache if it changes.
synchronized Config getOrElseUpdate(ClassLoader loader, String key, Callable<Config> updater) {
if (loader != currentLoader.get()) {
// reset the cache if we start using a different loader
cache.clear();
currentLoader = new WeakReference<ClassLoader>(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<Config> updater) {
LoaderCache cache;
try {
cache = LoaderCacheHolder.cache;
} catch (ExceptionInInitializerError e) {
throw ConfigImplUtil.extractInitializerError(e);
}
return cache.getOrElseUpdate(loader, key, updater);
}
static class FileNameSource implements SimpleIncluder.NameSource {
@Override
public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) {
return Parseable.newFile(new File(name), parseOptions);
}
};
static class ClasspathNameSource implements SimpleIncluder.NameSource {
@Override
public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) {
return Parseable.newResources(name, parseOptions);
}
};
static class ClasspathNameSourceWithClass implements SimpleIncluder.NameSource {
final private Class<?> klass;
public ClasspathNameSourceWithClass(Class<?> klass) {
this.klass = klass;
}
@Override
public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) {
return Parseable.newResources(klass, name, parseOptions);
}
};
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.<AbstractConfigValue> emptyList());
final private static SimpleConfigObject defaultEmptyObject = SimpleConfigObject
.empty(defaultValueOrigin);
private static SimpleConfigList emptyList(ConfigOrigin origin) {
if (origin == null || origin == defaultValueOrigin)
return defaultEmptyList;
else
return new SimpleConfigList(origin,
Collections.<AbstractConfigValue> emptyList());
}
private static AbstractConfigObject emptyObject(ConfigOrigin origin) {
// we want null origin to go to SimpleConfigObject.empty() to 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<String, ? extends Object> pathMap, String originDescription) {
ConfigOrigin origin = valueOrigin(originDescription);
return (ConfigObject) fromAnyRef(pathMap, origin,
FromMapMode.KEYS_ARE_PATHS);
}
static AbstractConfigValue fromAnyRef(Object object, ConfigOrigin origin,
FromMapMode mapMode) {
if (origin == null)
throw new ConfigException.BugOrBroken(
"origin not supposed to be null");
if (object == null) {
if (origin != defaultValueOrigin)
return new ConfigNull(origin);
else
return defaultNullValue;
} else if(object instanceof 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<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
for (Map.Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
Object key = entry.getKey();
if (!(key instanceof String))
throw new ConfigException.BugOrBroken(
"bug in method caller: not valid to create ConfigObject from map with non-String key: "
+ key);
AbstractConfigValue value = fromAnyRef(entry.getValue(),
origin, mapMode);
values.put((String) key, value);
}
return new SimpleConfigObject(origin, values);
} else {
return PropertiesParser.fromPathMap(origin, (Map<?, ?>) object);
}
} else if (object instanceof Iterable) {
Iterator<?> i = ((Iterable<?>) object).iterator();
if (!i.hasNext())
return emptyList(origin);
List<AbstractConfigValue> values = new ArrayList<AbstractConfigValue>();
while (i.hasNext()) {
AbstractConfigValue v = fromAnyRef(i.next(), origin, mapMode);
values.add(v);
}
return new SimpleConfigList(origin, values);
} else 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<Config>() {
@Override
public Config call() {
Config unresolvedResources = Parseable
.newResources("reference.conf",
ConfigParseOptions.defaults().setClassLoader(loader))
.parse().toConfig();
return systemPropertiesAsConfig().withFallback(unresolvedResources).resolve();
}
});
}
private static class DebugHolder {
private static String LOADS = "loads";
private static String SUBSTITUTIONS = "substitutions";
private static Map<String, Boolean> loadDiagnostics() {
Map<String, Boolean> result = new HashMap<String, Boolean>();
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<String, Boolean> 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);
}
}
@@ -0,0 +1,236 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<String> elements) {
return joinPath(elements.toArray(new String[0]));
}
public static List<String> splitPath(String path) {
Path p = Path.newPath(path);
List<String> elements = new ArrayList<String>();
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();
}
}
@@ -0,0 +1,5 @@
package com.drtshock.playervaults.lib.com.typesafe.config.impl;
enum ConfigIncludeKind {
URL, FILE, CLASSPATH, HEURISTIC
}
@@ -0,0 +1,61 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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);
}
}
@@ -0,0 +1,61 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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);
}
}
@@ -0,0 +1,14 @@
package com.drtshock.playervaults.lib.com.typesafe.config.impl;
import java.util.Collection;
final class ConfigNodeArray extends ConfigNodeComplexValue {
ConfigNodeArray(Collection<AbstractConfigNode> children) {
super(children);
}
@Override
protected ConfigNodeArray newNode(Collection<AbstractConfigNode> nodes) {
return new ConfigNodeArray(nodes);
}
}
@@ -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);
}
}
@@ -0,0 +1,52 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com>
*/
package com.drtshock.playervaults.lib.com.typesafe.config.impl;
import java.util.*;
abstract class ConfigNodeComplexValue extends AbstractConfigNodeValue {
final protected ArrayList<AbstractConfigNode> children;
ConfigNodeComplexValue(Collection<AbstractConfigNode> children) {
this.children = new ArrayList<AbstractConfigNode>(children);
}
final public Collection<AbstractConfigNode> children() {
return children;
}
@Override
protected Collection<Token> tokens() {
ArrayList<Token> tokens = new ArrayList<Token>();
for (AbstractConfigNode child : children) {
tokens.addAll(child.tokens());
}
return tokens;
}
protected ConfigNodeComplexValue indentText(AbstractConfigNode indentation) {
ArrayList<AbstractConfigNode> childrenCopy = new ArrayList<AbstractConfigNode>(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<AbstractConfigNode> nodes);
}
@@ -0,0 +1,14 @@
package com.drtshock.playervaults.lib.com.typesafe.config.impl;
import java.util.Collection;
final class ConfigNodeConcatenation extends ConfigNodeComplexValue {
ConfigNodeConcatenation(Collection<AbstractConfigNode> children) {
super(children);
}
@Override
protected ConfigNodeConcatenation newNode(Collection<AbstractConfigNode> nodes) {
return new ConfigNodeConcatenation(nodes);
}
}
@@ -0,0 +1,78 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com>
*/
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<AbstractConfigNode> children;
public ConfigNodeField(Collection<AbstractConfigNode> children) {
this.children = new ArrayList<AbstractConfigNode>(children);
}
@Override
protected Collection<Token> tokens() {
ArrayList<Token> tokens = new ArrayList<Token>();
for (AbstractConfigNode child : children) {
tokens.addAll(child.tokens());
}
return tokens;
}
public ConfigNodeField replaceValue(AbstractConfigNodeValue newValue) {
ArrayList<AbstractConfigNode> childrenCopy = new ArrayList<AbstractConfigNode>(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<String> comments() {
List<String> comments = new ArrayList<String>();
for (AbstractConfigNode child : children) {
if (child instanceof ConfigNodeComment) {
comments.add(((ConfigNodeComment) child).commentText());
}
}
return comments;
}
}
@@ -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<AbstractConfigNode> children;
final private ConfigIncludeKind kind;
final private boolean isRequired;
ConfigNodeInclude(Collection<AbstractConfigNode> children, ConfigIncludeKind kind, boolean isRequired) {
this.children = new ArrayList<AbstractConfigNode>(children);
this.kind = kind;
this.isRequired = isRequired;
}
final public Collection<AbstractConfigNode> children() {
return children;
}
@Override
protected Collection<Token> tokens() {
ArrayList<Token> tokens = new ArrayList<Token>();
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;
}
}
@@ -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<AbstractConfigNode> children) {
super(children);
}
@Override
protected ConfigNodeObject newNode(Collection<AbstractConfigNode> 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<AbstractConfigNode> childrenCopy = new ArrayList<AbstractConfigNode>(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<AbstractConfigNode> indentation() {
boolean seenNewLine = false;
ArrayList<AbstractConfigNode> indentation = new ArrayList<AbstractConfigNode>();
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<AbstractConfigNode> childrenCopy = new ArrayList<AbstractConfigNode>(super.children);
ArrayList<AbstractConfigNode> indentation = new ArrayList<AbstractConfigNode>(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<AbstractConfigNode> newNodes = new ArrayList<AbstractConfigNode>();
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<AbstractConfigNode> newObjectNodes = new ArrayList<AbstractConfigNode>();
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);
}
}
@@ -0,0 +1,52 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com>
*/
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<Token> tokens;
ConfigNodePath(Path path, Collection<Token> tokens) {
this.path = path;
this.tokens = new ArrayList<Token>(tokens);
}
@Override
protected Collection<Token> tokens() {
return tokens;
}
protected Path value() {
return path;
}
protected ConfigNodePath subPath(int toRemove) {
int periodCount = 0;
ArrayList<Token> tokensCopy = new ArrayList<Token>(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<Token> tokensCopy = new ArrayList<Token>(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;
}
}
@@ -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<AbstractConfigNode> children, ConfigOrigin origin) {
super(children);
this.origin = origin;
}
@Override
protected ConfigNodeRoot newNode(Collection<AbstractConfigNode> 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<AbstractConfigNode> childrenCopy = new ArrayList<AbstractConfigNode>(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<AbstractConfigNode> childrenCopy = new ArrayList<AbstractConfigNode>(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");
}
}
@@ -0,0 +1,39 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com>
*/
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<Token> 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<Token> 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");
}
}
@@ -0,0 +1,21 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com>
*/
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<Token> tokens() {
return Collections.singletonList(token);
}
protected Token token() { return token; }
}
@@ -0,0 +1,58 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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);
}
}
@@ -0,0 +1,110 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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);
}
}
@@ -0,0 +1,426 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<Path> 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<Path>();
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<AbstractConfigValue> values = new ArrayList<AbstractConfigValue>();
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<String> 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<String>(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<String> keys = new ArrayList<String>();
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<String> i = keys.listIterator(keys.size());
String deepest = i.previous();
AbstractConfigObject o = new SimpleConfigObject(value.origin().withComments(null),
Collections.<String, AbstractConfigValue> singletonMap(
deepest, value));
while (i.hasPrevious()) {
Map<String, AbstractConfigValue> m = Collections.<String, AbstractConfigValue> singletonMap(
i.previous(), o);
o = new SimpleConfigObject(value.origin().withComments(null), m);
}
return o;
}
private void parseInclude(Map<String, AbstractConfigValue> 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<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
SimpleConfigOrigin objectOrigin = lineOrigin();
boolean lastWasNewline = false;
ArrayList<AbstractConfigNode> nodes = new ArrayList<AbstractConfigNode>(n.children());
List<String> comments = new ArrayList<String>();
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<AbstractConfigValue> concat = new ArrayList<AbstractConfigValue>(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<AbstractConfigValue> values = new ArrayList<AbstractConfigValue>();
boolean lastWasNewLine = false;
List<String> comments = new ArrayList<String>();
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<String>(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<String>(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<String>(comments))));
}
arrayCount -= 1;
return new SimpleConfigList(arrayOrigin, values);
}
AbstractConfigValue parse() {
AbstractConfigValue result = null;
ArrayList<String> comments = new ArrayList<String>();
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<String>(comments)));
comments.clear();
break;
}
lastWasNewLine = true;
}
} else if (node instanceof ConfigNodeComplexValue) {
result = parseValue((ConfigNodeComplexValue)node, comments);
lastWasNewLine = false;
}
}
return result;
}
}
}
@@ -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<ConfigReference> 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<? extends AbstractConfigValue> 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<? extends AbstractConfigValue> 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;
}
}
@@ -0,0 +1,90 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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);
}
}
@@ -0,0 +1,27 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://typesafe.com>
*/
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);
}
@@ -0,0 +1,128 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<Integer, AbstractConfigValue> values = new HashMap<Integer, AbstractConfigValue>();
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<Map.Entry<Integer, AbstractConfigValue>> entryList = new ArrayList<Map.Entry<Integer, AbstractConfigValue>>(
values.entrySet());
// sort by numeric index
Collections.sort(entryList,
new Comparator<Map.Entry<Integer, AbstractConfigValue>>() {
@Override
public int compare(Map.Entry<Integer, AbstractConfigValue> a,
Map.Entry<Integer, AbstractConfigValue> b) {
return Integer.compare(a.getKey(), b.getKey());
}
});
// drop the indices (we allow gaps in the indices, for better or
// worse)
ArrayList<AbstractConfigValue> list = new ArrayList<AbstractConfigValue>();
for (Map.Entry<Integer, AbstractConfigValue> entry : entryList) {
list.add(entry.getValue());
}
return new SimpleConfigList(value.origin(), list);
}
}
return value;
}
}
@@ -0,0 +1,8 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
package com.drtshock.playervaults.lib.com.typesafe.config.impl;
enum FromMapMode {
KEYS_ARE_PATHS, KEYS_ARE_KEYS
}
@@ -0,0 +1,14 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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 {
}
@@ -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 + ")";
}
}
@@ -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();
}
@@ -0,0 +1,9 @@
package com.drtshock.playervaults.lib.com.typesafe.config.impl;
// caution: ordinals used in serialization
enum OriginType {
GENERIC,
FILE,
URL,
RESOURCE
}
@@ -0,0 +1,891 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<LinkedList<Parseable>> parseStack = new ThreadLocal<LinkedList<Parseable>>() {
@Override
protected LinkedList<Parseable> initialValue() {
return new LinkedList<Parseable>();
}
};
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<Parseable> 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<AbstractConfigNode> children = new ArrayList<AbstractConfigNode>();
children.add(new ConfigNodeObject(new ArrayList<AbstractConfigNode>()));
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<Token> 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<Token> 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<URL> 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);
}
}
@@ -0,0 +1,232 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<Path> pathsToConcat) {
this(pathsToConcat.iterator());
}
// append all the paths in the iterator together into one path
Path(Iterator<Path> 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);
}
}
@@ -0,0 +1,60 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<String> keys;
private Path result;
PathBuilder() {
keys = new Stack<String>();
}
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;
}
}
@@ -0,0 +1,281 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://typesafe.com>
*/
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<Token> 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<Token> tokens = Tokenizer.tokenize(apiOrigin, reader,
ConfigSyntax.CONF);
tokens.next(); // drop START
return parsePathExpression(tokens, apiOrigin, path);
} finally {
reader.close();
}
}
protected static Path parsePathExpression(Iterator<Token> expression,
ConfigOrigin origin) {
return parsePathExpression(expression, origin, null, null, ConfigSyntax.CONF);
}
protected static Path parsePathExpression(Iterator<Token> expression,
ConfigOrigin origin, String originalText) {
return parsePathExpression(expression, origin, originalText, null, ConfigSyntax.CONF);
}
protected static ConfigNodePath parsePathNodeExpression(Iterator<Token> expression,
ConfigOrigin origin) {
return parsePathNodeExpression(expression, origin, null, ConfigSyntax.CONF);
}
protected static ConfigNodePath parsePathNodeExpression(Iterator<Token> expression,
ConfigOrigin origin, String originalText, ConfigSyntax flavor) {
ArrayList<Token> pathTokens = new ArrayList<Token>();
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<Token> expression,
ConfigOrigin origin, String originalText,
ArrayList<Token> pathTokens,
ConfigSyntax flavor) {
// each builder in "buf" is an element in the path.
List<Element> buf = new ArrayList<Element>();
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<Token> splitTokenOnPeriod(Token t, ConfigSyntax flavor) {
String tokenText = t.tokenText();
if (tokenText.equals(".")) {
return Collections.singletonList(t);
}
String[] splitToken = tokenText.split("\\.");
ArrayList<Token> splitTokens = new ArrayList<Token>();
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<Element> 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<Token> tokens = new ArrayList<Token>();
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());
}
}
@@ -0,0 +1,210 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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 <K, V> AbstractConfigObject fromEntrySet(ConfigOrigin origin, Set<Map.Entry<K, V>> entries) {
final Map<Path, Object> pathMap = getPathMap(entries);
return fromPathMap(origin, pathMap, true /* from properties */);
}
private static <K, V> Map<Path, Object> getPathMap(Set<Map.Entry<K, V>> entries) {
Map<Path, Object> pathMap = new HashMap<Path, Object>();
for (Map.Entry<K, V> 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<String, String> stringMap) {
return fromEntrySet(origin, stringMap.entrySet());
}
static AbstractConfigObject fromPathMap(ConfigOrigin origin,
Map<?, ?> pathExpressionMap) {
Map<Path, Object> pathMap = new HashMap<Path, Object>();
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<Path, Object> pathMap, boolean convertedFromProperties) {
/*
* First, build a list of paths that will have values, either string or
* object values.
*/
Set<Path> scopePaths = new HashSet<Path>();
Set<Path> valuePaths = new HashSet<Path>();
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<String, AbstractConfigValue> root = new HashMap<String, AbstractConfigValue>();
Map<Path, Map<String, AbstractConfigValue>> scopes = new HashMap<Path, Map<String, AbstractConfigValue>>();
for (Path path : scopePaths) {
Map<String, AbstractConfigValue> scope = new HashMap<String, AbstractConfigValue>();
scopes.put(path, scope);
}
/* Store string values in the associated scope maps */
for (Path path : valuePaths) {
Path parentPath = path.parent();
Map<String, AbstractConfigValue> 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<Path> sortedScopePaths = new ArrayList<Path>();
sortedScopePaths.addAll(scopePaths);
// sort descending by length
Collections.sort(sortedScopePaths, new Comparator<Path>() {
@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<String, AbstractConfigValue> scope = scopes.get(scopePath);
Path parentPath = scopePath.parent();
Map<String, AbstractConfigValue> 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 */);
}
}
@@ -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);
}
@@ -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<AbstractConfigValue> resolveStack;
final private Set<AbstractConfigValue> cycleMarkers;
ResolveContext(ResolveMemos memos, ConfigResolveOptions options, Path restrictToChild,
List<AbstractConfigValue> resolveStack, Set<AbstractConfigValue> cycleMarkers) {
this.memos = memos;
this.options = options;
this.restrictToChild = restrictToChild;
this.resolveStack = Collections.unmodifiableList(resolveStack);
this.cycleMarkers = Collections.unmodifiableSet(cycleMarkers);
}
private static Set<AbstractConfigValue> newCycleMarkers() {
return Collections.newSetFromMap(new IdentityHashMap<AbstractConfigValue, Boolean>());
}
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<AbstractConfigValue>(), 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<AbstractConfigValue> 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<AbstractConfigValue> 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<AbstractConfigValue> copy = new ArrayList<AbstractConfigValue>(resolveStack);
copy.add(value);
return new ResolveContext(memos, options, restrictToChild, copy, cycleMarkers);
}
ResolveContext popTrace() {
List<AbstractConfigValue> copy = new ArrayList<AbstractConfigValue>(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<? extends AbstractConfigValue> 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<? extends AbstractConfigValue> 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<? extends AbstractConfigValue> 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);
}
}
}
@@ -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<MemoKey, AbstractConfigValue> memos;
private ResolveMemos(Map<MemoKey, AbstractConfigValue> memos) {
this.memos = memos;
}
ResolveMemos() {
this(new HashMap<MemoKey, AbstractConfigValue>());
}
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<MemoKey, AbstractConfigValue> copy = new HashMap<MemoKey, AbstractConfigValue>(memos);
copy.put(key, value);
return new ResolveMemos(copy);
}
}
@@ -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<V extends AbstractConfigValue> {
public final ResolveContext context;
public final V value;
private ResolveResult(ResolveContext context, V value) {
this.context = context;
this.value = value;
}
static <V extends AbstractConfigValue> ResolveResult<V> make(ResolveContext context, V value) {
return new ResolveResult<V>(context, value);
}
// better option? we don't have variance
@SuppressWarnings("unchecked")
ResolveResult<AbstractConfigObject> 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<AbstractConfigObject>) o;
}
// better option? we don't have variance
@SuppressWarnings("unchecked")
ResolveResult<AbstractConfigValue> asValueResult() {
Object o = this;
return (ResolveResult<AbstractConfigValue>) o;
}
ResolveResult<V> popTrace() {
return make(context.popTrace(), value);
}
@Override
public String toString() {
return "ResolveResult(" + value + ")";
}
}
@@ -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<Container> pathFromRoot;
ResolveSource(AbstractConfigObject root, Node<Container> 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<? extends AbstractConfigValue> 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<Container> 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<Container> newParents = parents == null ? new Node<Container>(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<Container>(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<Container> replace(Node<Container> 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>((Container) replacement);
} else {
AbstractConfigValue newParent = parent.replaceChild((AbstractConfigValue) old, replacement);
Node<Container> newTail = replace(list.tail(), parent, newParent);
if (newTail != null)
return newTail.prepend((Container) replacement);
else
return new Node<Container>((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<Container> 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<T> {
final T value;
final Node<T> next;
Node(T value, Node<T> next) {
this.value = value;
this.next = next;
}
Node(T value) {
this(value, null);
}
Node<T> prepend(T value) {
return new Node<T>(value, this);
}
T head() {
return value;
}
Node<T> tail() {
return next;
}
T last() {
Node<T> i = this;
while (i.next != null)
i = i.next;
return i.value;
}
Node<T> reverse() {
if (next == null) {
return this;
} else {
Node<T> reversed = new Node<T>(value);
Node<T> 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<T> 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<Container> pathFromRoot;
ValueWithPath(AbstractConfigValue value, Node<Container> pathFromRoot) {
this.value = value;
this.pathFromRoot = pathFromRoot;
}
@Override
public String toString() {
return "ValueWithPath(value=" + value + ", pathFromRoot=" + pathFromRoot + ")";
}
}
static final class ResultWithPath {
final ResolveResult<? extends AbstractConfigValue> result;
final Node<Container> pathFromRoot;
ResultWithPath(ResolveResult<? extends AbstractConfigValue> result, Node<Container> pathFromRoot) {
this.result = result;
this.pathFromRoot = pathFromRoot;
}
@Override
public String toString() {
return "ResultWithPath(result=" + result + ", pathFromRoot=" + pathFromRoot + ")";
}
}
}
@@ -0,0 +1,26 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<? extends AbstractConfigValue> 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;
}
}
@@ -0,0 +1,534 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<String> list = (List<String>) 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<SerializedField, Object> 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<SerializedField, Object> 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<SerializedField, Object> m = new EnumMap<SerializedField, Object>(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<String> list = new ArrayList<String>(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<String, ConfigValue> 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<AbstractConfigValue> list = new ArrayList<AbstractConfigValue>(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<String, AbstractConfigValue> map = new HashMap<String, AbstractConfigValue>(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;
}
}
@@ -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<Token> 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();
}
}
@@ -0,0 +1,464 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<AbstractConfigValue> value;
final private boolean resolved;
SimpleConfigList(ConfigOrigin origin, List<AbstractConfigValue> value) {
this(origin, value, ResolveStatus
.fromValues(value));
}
SimpleConfigList(ConfigOrigin origin, List<AbstractConfigValue> 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<Object> unwrapped() {
List<Object> list = new ArrayList<Object>();
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<AbstractConfigValue> 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<AbstractConfigValue> 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<AbstractConfigValue>();
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<? extends AbstractConfigValue> result = context.resolve(v, source);
context = result.context;
return result.value;
}
}
@Override
ResolveResult<? extends SimpleConfigList> 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<ConfigValue> iterator() {
final Iterator<AbstractConfigValue> i = value.iterator();
return new Iterator<ConfigValue>() {
@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<ConfigValue> wrapListIterator(
final ListIterator<AbstractConfigValue> i) {
return new ListIterator<ConfigValue>() {
@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<ConfigValue> listIterator() {
return wrapListIterator(value.listIterator());
}
@Override
public ListIterator<ConfigValue> listIterator(int index) {
return wrapListIterator(value.listIterator(index));
}
@Override
public int size() {
return value.size();
}
@Override
public List<ConfigValue> subList(int fromIndex, int toIndex) {
List<ConfigValue> list = new ArrayList<ConfigValue>();
// 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> 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<? extends ConfigValue> c) {
throw weAreImmutable("addAll");
}
@Override
public boolean addAll(int index, Collection<? extends ConfigValue> 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<AbstractConfigValue> combined = new ArrayList<AbstractConfigValue>(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);
}
}
@@ -0,0 +1,671 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<String, AbstractConfigValue> value;
final private boolean resolved;
final private boolean ignoresFallbacks;
SimpleConfigObject(ConfigOrigin origin,
Map<String, AbstractConfigValue> 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<String, AbstractConfigValue> 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.<String, AbstractConfigValue> 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<String, AbstractConfigValue> updated = new HashMap<String, AbstractConfigValue>(
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<String, AbstractConfigValue> smaller = new HashMap<String, AbstractConfigValue>(
value.size() - 1);
for (Map.Entry<String, AbstractConfigValue> 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<String, AbstractConfigValue> newMap;
if (value.isEmpty()) {
newMap = Collections.singletonMap(key, (AbstractConfigValue) v);
} else {
newMap = new HashMap<String, AbstractConfigValue>(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<String, AbstractConfigValue> newChildren = new HashMap<String, AbstractConfigValue>(value);
for (Map.Entry<String, AbstractConfigValue> 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<String, Object> unwrapped() {
Map<String, Object> m = new HashMap<String, Object>();
for (Map.Entry<String, AbstractConfigValue> 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<String, AbstractConfigValue> merged = new HashMap<String, AbstractConfigValue>();
Set<String> allKeys = new HashSet<String>();
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<String, AbstractConfigValue> 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<String, AbstractConfigValue>();
changes.put(k, modified);
}
}
if (changes == null) {
return this;
} else {
Map<String, AbstractConfigValue> modified = new HashMap<String, AbstractConfigValue>();
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<? extends AbstractConfigValue> 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<? extends AbstractConfigValue> result = context.unrestricted().resolve(v, source);
context = result.context.unrestricted().restrict(originalRestrict);
return result.value;
}
}
}
@Override
ResolveResult<? extends AbstractConfigObject> 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<String>, 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<String, ConfigValue> a, Map<String, ConfigValue> b) {
if (a == b)
return true;
Set<String> aKeys = a.keySet();
Set<String> 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<String, ConfigValue> m) {
// the keys have to be sorted, otherwise we could be equal
// to another map but have a different hashcode.
List<String> keys = new ArrayList<String>();
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<String> keySet() {
return value.keySet();
}
@Override
public boolean containsValue(Object v) {
return value.containsValue(v);
}
@Override
public Set<Map.Entry<String, ConfigValue>> entrySet() {
// total bloat just to work around lack of type variance
HashSet<java.util.Map.Entry<String, ConfigValue>> entries = new HashSet<Map.Entry<String, ConfigValue>>();
for (Map.Entry<String, AbstractConfigValue> e : value.entrySet()) {
entries.add(new AbstractMap.SimpleImmutableEntry<String, ConfigValue>(
e.getKey(), e
.getValue()));
}
return entries;
}
@Override
public boolean isEmpty() {
return value.isEmpty();
}
@Override
public int size() {
return value.size();
}
@Override
public Collection<ConfigValue> values() {
return new HashSet<ConfigValue>(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.<String, AbstractConfigValue> emptyMap());
}
final static SimpleConfigObject emptyMissing(ConfigOrigin baseOrigin) {
return new SimpleConfigObject(SimpleConfigOrigin.newSimple(
baseOrigin.description() + " (not found)"),
Collections.<String, AbstractConfigValue> emptyMap());
}
// serialization all goes through SerializedConfigValue
private Object writeReplace() throws ObjectStreamException {
return new SerializedConfigValue(this);
}
}
@@ -0,0 +1,575 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<String> commentsOrNull;
protected SimpleConfigOrigin(String description, int lineNumber, int endLineNumber, OriginType originType,
String urlOrNull, String resourceOrNull, List<String> 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<String> 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<String> comments) {
if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) {
return this;
} else if (this.commentsOrNull == null) {
return withComments(comments);
} else {
List<String> merged = new ArrayList<String>(comments.size() + this.commentsOrNull.size());
merged.addAll(comments);
merged.addAll(this.commentsOrNull);
return withComments(merged);
}
}
SimpleConfigOrigin appendComments(List<String> comments) {
if (ConfigImplUtil.equalsHandlingNull(comments, this.commentsOrNull) || comments == null) {
return this;
} else if (this.commentsOrNull == null) {
return withComments(comments);
} else {
List<String> merged = new ArrayList<String>(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<String> 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<String> 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<String>();
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<? extends AbstractConfigValue> stack) {
List<ConfigOrigin> origins = new ArrayList<ConfigOrigin>(stack.size());
for (AbstractConfigValue v : stack) {
origins.add(v.origin());
}
return mergeOrigins(origins);
}
static ConfigOrigin mergeOrigins(Collection<? extends ConfigOrigin> 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<? extends ConfigOrigin> i = stack.iterator();
return mergeTwo((SimpleConfigOrigin) i.next(), (SimpleConfigOrigin) i.next());
} else {
List<SimpleConfigOrigin> remaining = new ArrayList<SimpleConfigOrigin>();
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<SerializedField, Object> toFields() {
Map<SerializedField, Object> m = new EnumMap<SerializedField, Object>(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<SerializedField, Object> toFieldsDelta(SimpleConfigOrigin baseOrigin) {
Map<SerializedField, Object> baseFields;
if (baseOrigin != null)
baseFields = baseOrigin.toFields();
else
baseFields = Collections.<SerializedField, Object> 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<SerializedField, Object> fieldsDelta(Map<SerializedField, Object> base,
Map<SerializedField, Object> child) {
Map<SerializedField, Object> m = new EnumMap<SerializedField, Object>(child);
for (Map.Entry<SerializedField, Object> 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<SerializedField, Object> 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<String> commentsOrNull = (List<String>) 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<SerializedField, Object> applyFieldsDelta(Map<SerializedField, Object> base,
Map<SerializedField, Object> delta) throws IOException {
Map<SerializedField, Object> m = new EnumMap<SerializedField, Object>(delta);
for (Map.Entry<SerializedField, Object> 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<SerializedField, Object> delta)
throws IOException {
Map<SerializedField, Object> baseFields;
if (baseOrigin != null)
baseFields = baseOrigin.toFields();
else
baseFields = Collections.<SerializedField, Object> emptyMap();
Map<SerializedField, Object> fields = applyFieldsDelta(baseFields, delta);
return fromFields(fields);
}
}
@@ -0,0 +1,51 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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));
}
}
@@ -0,0 +1,302 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<ConfigException.IO> fails = new ArrayList<ConfigException.IO>();
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);
}
}
@@ -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;
}
}
@@ -0,0 +1,87 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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();
}
}
@@ -0,0 +1,24 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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;
}
@@ -0,0 +1,695 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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<Token> tokenize(ConfigOrigin origin, Reader input, ConfigSyntax flavor) {
return new TokenIterator(origin, input, flavor != ConfigSyntax.JSON);
}
static String render(Iterator<Token> tokens) {
StringBuilder renderedText = new StringBuilder();
while (tokens.hasNext()) {
renderedText.append(tokens.next().tokenText());
}
return renderedText.toString();
}
private static class TokenIterator implements Iterator<Token> {
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<Integer> buffer;
private int lineNumber;
private ConfigOrigin lineOrigin;
final private Queue<Token> 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<Integer>();
lineNumber = 1;
lineOrigin = this.origin.withLineNumber(lineNumber);
tokens = new LinkedList<Token>();
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<Token> expression = new ArrayList<Token>();
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");
}
}
}
@@ -0,0 +1,521 @@
/**
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
*/
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 "'<unresolved value>' (" + 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<Token> value;
Substitution(ConfigOrigin origin, boolean optional, List<Token> expression) {
super(TokenType.SUBSTITUTION, origin);
this.optional = optional;
this.value = expression;
}
boolean optional() {
return optional;
}
List<Token> 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<Token> 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<Token> 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);
}
}

Some files were not shown because too many files have changed in this diff Show More