New config
This commit is contained in:
@@ -23,6 +23,8 @@ import com.drtshock.playervaults.commands.DeleteCommand;
|
||||
import com.drtshock.playervaults.commands.SignCommand;
|
||||
import com.drtshock.playervaults.commands.SignSetInfo;
|
||||
import com.drtshock.playervaults.commands.VaultCommand;
|
||||
import com.drtshock.playervaults.config.Loader;
|
||||
import com.drtshock.playervaults.config.file.Config;
|
||||
import com.drtshock.playervaults.listeners.Listeners;
|
||||
import com.drtshock.playervaults.listeners.SignListener;
|
||||
import com.drtshock.playervaults.listeners.VaultPreloadListener;
|
||||
@@ -53,6 +55,7 @@ import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
@@ -83,6 +86,7 @@ public class PlayerVaults extends JavaPlugin {
|
||||
private String _versionString;
|
||||
private int maxVaultAmountPermTest;
|
||||
private Metrics metrics;
|
||||
private Config config = new Config();
|
||||
|
||||
public static PlayerVaults getInstance() {
|
||||
return instance;
|
||||
@@ -105,7 +109,7 @@ public class PlayerVaults extends JavaPlugin {
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
loadConfig();
|
||||
DEBUG = getConfig().getBoolean("debug", false);
|
||||
DEBUG = getConf().isDebug();
|
||||
debug("config", System.currentTimeMillis());
|
||||
uuidData = new File(this.getDataFolder(), "uuidvaults");
|
||||
vaultData = new File(this.getDataFolder(), "base64vaults");
|
||||
@@ -123,8 +127,8 @@ public class PlayerVaults extends JavaPlugin {
|
||||
getServer().getPluginManager().registerEvents(new VaultPreloadListener(), this);
|
||||
getServer().getPluginManager().registerEvents(new SignListener(this), this);
|
||||
debug("registering listeners", System.currentTimeMillis());
|
||||
this.backupsEnabled = this.getConfig().getBoolean("backups.enabled", true);
|
||||
this.maxVaultAmountPermTest = this.getConfig().getInt("max-vault-amount-perm-to-test", 99);
|
||||
this.backupsEnabled = this.getConf().getStorage().getFlatFile().isBackups();
|
||||
this.maxVaultAmountPermTest = this.getConf().getMaxVaultAmountPermTest();
|
||||
loadSigns();
|
||||
debug("loaded signs", System.currentTimeMillis());
|
||||
debug("check update", System.currentTimeMillis());
|
||||
@@ -136,8 +140,8 @@ public class PlayerVaults extends JavaPlugin {
|
||||
useVault = setupEconomy();
|
||||
debug("setup economy", System.currentTimeMillis());
|
||||
|
||||
if (getConfig().getBoolean("cleanup.enable", false)) {
|
||||
getServer().getScheduler().runTaskAsynchronously(this, new Cleanup(getConfig().getInt("cleanup.lastEdit", 30)));
|
||||
if (getConf().getPurge().isEnabled()) {
|
||||
getServer().getScheduler().runTaskAsynchronously(this, new Cleanup(getConf().getPurge().getDaysSinceLastEdit()));
|
||||
}
|
||||
|
||||
new BukkitRunnable() {
|
||||
@@ -200,14 +204,14 @@ public class PlayerVaults extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
this.metricsSimplePie("signs", () -> getConfig().getBoolean("signs-enabled") ? "enabled" : "disabled");
|
||||
this.metricsSimplePie("cleanup", () -> getConfig().getBoolean("cleanup.enable") ? "enabled" : "disabled");
|
||||
this.metricsSimplePie("language", () -> getConfig().getString("language", "english"));
|
||||
this.metricsSimplePie("signs", () -> getConf().isSigns() ? "enabled" : "disabled");
|
||||
this.metricsSimplePie("cleanup", () -> getConf().getPurge().isEnabled() ? "enabled" : "disabled");
|
||||
this.metricsSimplePie("language", () -> getConf().getLanguage());
|
||||
|
||||
this.metricsDrillPie("block_items", () -> {
|
||||
Map<String, Map<String, Integer>> map = new HashMap<>();
|
||||
Map<String, Integer> entry = new HashMap<>();
|
||||
if (getConfig().getBoolean("blockitems")) {
|
||||
if (getConf().getItemBlocking().isEnabled()) {
|
||||
for (Material material : blockedMats) {
|
||||
entry.put(material.toString(), 1);
|
||||
}
|
||||
@@ -215,7 +219,7 @@ public class PlayerVaults extends JavaPlugin {
|
||||
if (entry.isEmpty()) {
|
||||
entry.put("none", 1);
|
||||
}
|
||||
map.put(getConfig().getBoolean("blockitems") ? "enabled" : "disabled", entry);
|
||||
map.put(getConf().getItemBlocking().isEnabled() ? "enabled" : "disabled", entry);
|
||||
return map;
|
||||
});
|
||||
|
||||
@@ -265,7 +269,7 @@ public class PlayerVaults extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
if (getConfig().getBoolean("cleanup.enable", false)) {
|
||||
if (getConf().getPurge().isEnabled()) {
|
||||
saveSignsFile();
|
||||
}
|
||||
}
|
||||
@@ -297,12 +301,27 @@ public class PlayerVaults extends JavaPlugin {
|
||||
}
|
||||
|
||||
private void loadConfig() {
|
||||
saveDefaultConfig();
|
||||
File configYaml = new File(this.getDataFolder(), "config.yml");
|
||||
if (configYaml.exists()) {
|
||||
this.config.setFromConfig(this.getConfig());
|
||||
try {
|
||||
Files.move(configYaml.toPath(), this.getDataFolder().toPath().resolve("old_unused_config.yml"));
|
||||
} catch (Exception e) {
|
||||
this.getLogger().log(Level.SEVERE, "Failed to move config for backup", e);
|
||||
configYaml.deleteOnExit();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Loader.loadAndSave("config", this.config);
|
||||
} catch (IOException | IllegalAccessException e) {
|
||||
this.getLogger().log(Level.SEVERE, "Could not load config.", e);
|
||||
}
|
||||
|
||||
// Clear just in case this is a reload.
|
||||
blockedMats.clear();
|
||||
if (getConfig().getBoolean("blockitems", false) && getConfig().contains("blocked-items")) {
|
||||
for (String s : getConfig().getStringList("blocked-items")) {
|
||||
if (getConf().getItemBlocking().isEnabled()) {
|
||||
for (String s : getConf().getItemBlocking().getList()) {
|
||||
Material mat = Material.matchMaterial(s);
|
||||
if (mat != null) {
|
||||
blockedMats.add(mat);
|
||||
@@ -312,6 +331,10 @@ public class PlayerVaults extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
public Config getConf() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
private void loadSigns() {
|
||||
File signs = new File(getDataFolder(), "signs.yml");
|
||||
if (!signs.exists()) {
|
||||
@@ -328,7 +351,7 @@ public class PlayerVaults extends JavaPlugin {
|
||||
}
|
||||
|
||||
private void reloadSigns() {
|
||||
if (!getConfig().getBoolean("signs-enabled")) {
|
||||
if (!getConf().isSigns()) {
|
||||
return;
|
||||
}
|
||||
if (!signsFile.exists()) loadSigns();
|
||||
@@ -358,7 +381,7 @@ public class PlayerVaults extends JavaPlugin {
|
||||
}
|
||||
|
||||
private void saveSignsFile() {
|
||||
if (!getConfig().getBoolean("signs-enabled")) {
|
||||
if (!getConf().isSigns()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -372,24 +395,13 @@ public class PlayerVaults extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an object in the config.yml
|
||||
*
|
||||
* @param path The path in the config.
|
||||
* @param object What to be saved.
|
||||
* @param conf Where to save the object.
|
||||
*/
|
||||
public <T> void setInConfig(String path, T object, YamlConfiguration conf) {
|
||||
conf.set(path, object);
|
||||
}
|
||||
|
||||
public void loadLang() {
|
||||
File folder = new File(getDataFolder(), "lang");
|
||||
if (!folder.exists()) {
|
||||
folder.mkdir();
|
||||
}
|
||||
|
||||
String definedLanguage = getConfig().getString("language", "english");
|
||||
String definedLanguage = getConf().getLanguage();
|
||||
|
||||
// Save as default just incase.
|
||||
File english = null;
|
||||
@@ -444,7 +456,7 @@ public class PlayerVaults extends JavaPlugin {
|
||||
}
|
||||
|
||||
public boolean isEconomyEnabled() {
|
||||
return this.getConfig().getBoolean("economy.enabled", false) && this.useVault;
|
||||
return this.getConf().getEconomy().isEnabled() && this.useVault;
|
||||
}
|
||||
|
||||
public File getVaultData() {
|
||||
|
||||
@@ -30,7 +30,7 @@ public class SignCommand implements CommandExecutor {
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (sender.hasPermission("playervaults.signs.set")) {
|
||||
if (!PlayerVaults.getInstance().getConfig().getBoolean("signs-enabled")) {
|
||||
if (!PlayerVaults.getInstance().getConf().isSigns()) {
|
||||
sender.sendMessage(Lang.TITLE.toString() + Lang.SIGNS_DISABLED.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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
+49
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+56
@@ -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);
|
||||
}
|
||||
+25
@@ -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);
|
||||
}
|
||||
+27
@@ -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);
|
||||
}
|
||||
+27
@@ -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);
|
||||
}
|
||||
+20
@@ -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);
|
||||
}
|
||||
+68
@@ -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);
|
||||
}
|
||||
}
|
||||
+228
@@ -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();
|
||||
}
|
||||
+182
@@ -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();
|
||||
}
|
||||
}
|
||||
+176
@@ -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);
|
||||
}
|
||||
+153
@@ -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
|
||||
}
|
||||
+62
@@ -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 {
|
||||
|
||||
}
|
||||
+29
@@ -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();
|
||||
}
|
||||
}
|
||||
+11
@@ -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 {
|
||||
|
||||
}
|
||||
+221
@@ -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);
|
||||
}
|
||||
}
|
||||
+411
@@ -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));
|
||||
}
|
||||
}
|
||||
+305
@@ -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);
|
||||
}
|
||||
}
|
||||
+47
@@ -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);
|
||||
}
|
||||
}
|
||||
+293
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+342
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
+326
@@ -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");
|
||||
|
||||
}
|
||||
}
|
||||
+716
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -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);
|
||||
}
|
||||
}
|
||||
+236
@@ -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();
|
||||
}
|
||||
}
|
||||
+5
@@ -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);
|
||||
}
|
||||
}
|
||||
+14
@@ -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);
|
||||
}
|
||||
}
|
||||
+17
@@ -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);
|
||||
}
|
||||
}
|
||||
+52
@@ -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);
|
||||
}
|
||||
+14
@@ -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);
|
||||
}
|
||||
}
|
||||
+78
@@ -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;
|
||||
}
|
||||
}
|
||||
+46
@@ -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;
|
||||
}
|
||||
}
|
||||
+281
@@ -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);
|
||||
}
|
||||
}
|
||||
+52
@@ -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;
|
||||
}
|
||||
}
|
||||
+67
@@ -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");
|
||||
}
|
||||
}
|
||||
+39
@@ -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");
|
||||
}
|
||||
}
|
||||
+21
@@ -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);
|
||||
}
|
||||
}
|
||||
+110
@@ -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);
|
||||
}
|
||||
}
|
||||
+426
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+161
@@ -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;
|
||||
}
|
||||
}
|
||||
+90
@@ -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);
|
||||
}
|
||||
+128
@@ -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
|
||||
}
|
||||
+14
@@ -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 + ")";
|
||||
}
|
||||
}
|
||||
+9
@@ -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());
|
||||
}
|
||||
}
|
||||
+210
@@ -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 */);
|
||||
}
|
||||
}
|
||||
+14
@@ -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);
|
||||
}
|
||||
+237
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -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);
|
||||
}
|
||||
}
|
||||
+43
@@ -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 + ")";
|
||||
}
|
||||
}
|
||||
+349
@@ -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 + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -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;
|
||||
}
|
||||
}
|
||||
+534
@@ -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;
|
||||
}
|
||||
}
|
||||
+1168
File diff suppressed because it is too large
Load Diff
+66
@@ -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();
|
||||
}
|
||||
}
|
||||
+464
@@ -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);
|
||||
}
|
||||
}
|
||||
+671
@@ -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);
|
||||
}
|
||||
}
|
||||
+575
@@ -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);
|
||||
}
|
||||
}
|
||||
+51
@@ -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));
|
||||
}
|
||||
}
|
||||
+302
@@ -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);
|
||||
}
|
||||
}
|
||||
+49
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
|
||||
*/
|
||||
package com.drtshock.playervaults.lib.com.typesafe.config.impl;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Interface that tags a ConfigValue that is not mergeable until after
|
||||
* substitutions are resolved. Basically these are special ConfigValue that
|
||||
* never appear in a resolved tree, like {@link ConfigSubstitution} and
|
||||
* {@link ConfigDelayedMerge}.
|
||||
*/
|
||||
interface Unmergeable {
|
||||
Collection<? extends AbstractConfigValue> unmergedValues();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user