Large configuration refactoring

This commit is contained in:
filoghost 2020-07-09 23:35:40 +02:00
parent dfc060fdcd
commit 58bdf388bf
40 changed files with 1135 additions and 464 deletions

View File

@ -17,12 +17,11 @@ package me.filoghost.chestcommands;
import me.filoghost.chestcommands.api.internal.BackendAPI;
import me.filoghost.chestcommands.command.CommandHandler;
import me.filoghost.chestcommands.command.framework.CommandFramework;
import me.filoghost.chestcommands.config.ConfigLoader;
import me.filoghost.chestcommands.config.framework.ConfigLoader;
import me.filoghost.chestcommands.config.ConfigManager;
import me.filoghost.chestcommands.config.files.CustomPlaceholders;
import me.filoghost.chestcommands.config.files.Lang;
import me.filoghost.chestcommands.parsing.menu.LoadedMenu;
import me.filoghost.chestcommands.config.files.Settings;
import me.filoghost.chestcommands.config.CustomPlaceholders;
import me.filoghost.chestcommands.config.Lang;
import me.filoghost.chestcommands.config.Settings;
import me.filoghost.chestcommands.hook.BarAPIHook;
import me.filoghost.chestcommands.hook.BungeeCordHook;
import me.filoghost.chestcommands.hook.PlaceholderAPIHook;
@ -34,10 +33,11 @@ import me.filoghost.chestcommands.listener.InventoryListener;
import me.filoghost.chestcommands.listener.JoinListener;
import me.filoghost.chestcommands.listener.SignListener;
import me.filoghost.chestcommands.menu.MenuManager;
import me.filoghost.chestcommands.parsing.menu.LoadedMenu;
import me.filoghost.chestcommands.task.RefreshMenusTask;
import me.filoghost.chestcommands.util.Log;
import me.filoghost.chestcommands.util.Utils;
import me.filoghost.chestcommands.util.collection.ErrorCollector;
import me.filoghost.chestcommands.util.Log;
import me.filoghost.updatechecker.UpdateChecker;
import org.bstats.bukkit.MetricsLite;
import org.bukkit.Bukkit;
@ -152,9 +152,9 @@ public class ChestCommands extends JavaPlugin {
public ErrorCollector load() {
ErrorCollector errors = new ErrorCollector();
menuManager.clear();
boolean isFreshInstall = !Files.isDirectory(configManager.getBaseDataPath());
boolean isFreshInstall = !Files.isDirectory(configManager.getRootDataFolder());
try {
Files.createDirectories(configManager.getBaseDataPath());
Files.createDirectories(configManager.getRootDataFolder());
} catch (IOException e) {
errors.addError("Plugin failed to load, couldn't create data folder.");
return errors;
@ -172,7 +172,7 @@ public class ChestCommands extends JavaPlugin {
// Create the menu folder with the example menu
if (!Files.isDirectory(configManager.getMenusFolder())) {
ConfigLoader exampleMenuLoader = new ConfigLoader(configManager.getMenusFolder().resolve("example.yml"));
ConfigLoader exampleMenuLoader = configManager.getConfigLoader(configManager.getMenusFolder().resolve("example.yml"));
configManager.tryCreateDefault(exampleMenuLoader);
}

View File

@ -1,126 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config;
import me.filoghost.chestcommands.util.Preconditions;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
public class ConfigLoader {
private final Path path;
public ConfigLoader(Path path) {
this.path = path;
}
public Path getPath() {
return path;
}
public void createDefault(Path baseDataPath) throws IOException {
if (!path.startsWith(baseDataPath)) {
throw new IOException("Config file " + path + " must be inside " + baseDataPath);
}
if (Files.exists(path)) {
return;
}
if (path.getParent() != null) {
Files.createDirectories(path.getParent());
}
Path absoluteDataPath = baseDataPath.toAbsolutePath();
Path absoluteConfigPath = path.toAbsolutePath();
if (absoluteConfigPath.startsWith(absoluteDataPath)) {
Path relativeConfigPath = absoluteDataPath.relativize(absoluteConfigPath);
String internalJarPath = toInternalJarPath(relativeConfigPath);
try (InputStream defaultFile = getResource(internalJarPath)) {
if (defaultFile != null) {
Files.copy(defaultFile, path);
return;
}
}
}
Files.createFile(path);
}
private String toInternalJarPath(Path path) {
return StreamSupport.stream(path.spliterator(), false)
.map(Path::toString)
.collect(Collectors.joining("/", "/", ""));
}
private InputStream getResource(String internalJarPath) throws IOException {
Preconditions.notNull(internalJarPath, "internalJarPath");
URL resourceURL = getClass().getResource(internalJarPath);
if (resourceURL == null) {
return null;
}
URLConnection connection = resourceURL.openConnection();
connection.setUseCaches(false);
return connection.getInputStream();
}
public Config load() throws IOException, InvalidConfigurationException {
YamlConfiguration yaml = new YamlConfiguration();
try (BufferedReader reader = Files.newBufferedReader(path)) {
yaml.load(reader);
}
return new Config(yaml, path);
}
public Config loadEmpty() {
return new Config(new YamlConfiguration(), path);
}
public void save(Config config) throws IOException {
if (path.getParent() != null) {
Files.createDirectories(path.getParent());
}
String data = config.saveToString();
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
writer.write(data);
}
}
public String getFileName() {
return path.getFileName().toString();
}
}

View File

@ -14,14 +14,17 @@
*/
package me.filoghost.chestcommands.config;
import me.filoghost.chestcommands.config.files.CustomPlaceholders;
import me.filoghost.chestcommands.config.files.Lang;
import me.filoghost.chestcommands.config.framework.BaseConfigManager;
import me.filoghost.chestcommands.config.framework.Config;
import me.filoghost.chestcommands.config.framework.ConfigLoader;
import me.filoghost.chestcommands.config.framework.exception.ConfigException;
import me.filoghost.chestcommands.config.framework.exception.ConfigSyntaxException;
import me.filoghost.chestcommands.config.framework.mapped.MappedConfigLoader;
import me.filoghost.chestcommands.parsing.menu.LoadedMenu;
import me.filoghost.chestcommands.config.files.Settings;
import me.filoghost.chestcommands.parsing.menu.MenuParser;
import me.filoghost.chestcommands.util.collection.ErrorCollector;
import me.filoghost.chestcommands.util.Log;
import org.bukkit.configuration.InvalidConfigurationException;
import me.filoghost.chestcommands.util.Preconditions;
import me.filoghost.chestcommands.util.collection.ErrorCollector;
import java.io.IOException;
import java.nio.file.FileVisitOption;
@ -33,55 +36,46 @@ import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ConfigManager {
public class ConfigManager extends BaseConfigManager {
private final Path baseDataPath;
private final ConfigLoader settingsConfigLoader;
private final MappedConfigLoader<Settings> settingsConfigLoader;
private final ConfigLoader placeholdersConfigLoader;
private final ConfigLoader langConfigLoader;
private final MappedConfigLoader<Lang> langConfigLoader;
public ConfigManager(Path baseDataPath) {
this.baseDataPath = baseDataPath;
settingsConfigLoader = new ConfigLoader(baseDataPath.resolve("config.yml"));
placeholdersConfigLoader = new ConfigLoader(baseDataPath.resolve("custom-placeholders.yml"));
langConfigLoader = new ConfigLoader(baseDataPath.resolve("lang.yml"));
public ConfigManager(Path rootDataFolder) {
super(rootDataFolder);
settingsConfigLoader = getMappedConfigLoader("config.yml", Settings::new);
placeholdersConfigLoader = getConfigLoader("custom-placeholders.yml");
langConfigLoader = getMappedConfigLoader("lang.yml", Lang::new);
}
public Settings tryLoadSettings() {
Settings settings = new Settings();
try {
settingsConfigLoader.createDefault(baseDataPath);
settings.load(settingsConfigLoader);
} catch (Throwable t) {
logConfigLoadException(settingsConfigLoader, t);
return settingsConfigLoader.init();
} catch (ConfigException e) {
logConfigInitException(settingsConfigLoader.getFileName(), e);
return new Settings();
}
return settings;
}
public Lang tryLoadLang() {
Lang lang = new Lang();
try {
langConfigLoader.createDefault(baseDataPath);
lang.load(langConfigLoader);
} catch (Throwable t) {
logConfigLoadException(langConfigLoader, t);
return langConfigLoader.init();
} catch (ConfigException e) {
logConfigInitException(langConfigLoader.getFileName(), e);
return new Lang();
}
return lang;
}
public CustomPlaceholders tryLoadCustomPlaceholders(ErrorCollector errorCollector) {
CustomPlaceholders placeholders = new CustomPlaceholders();
try {
placeholdersConfigLoader.createDefault(baseDataPath);
Config placeholdersConfig = placeholdersConfigLoader.load();
Config placeholdersConfig = placeholdersConfigLoader.init();
placeholders.load(placeholdersConfig, errorCollector);
} catch (Throwable t) {
logConfigLoadException(placeholdersConfigLoader, t);
} catch (ConfigException t) {
logConfigInitException(placeholdersConfigLoader.getFileName(), t);
}
return placeholders;
@ -89,20 +83,22 @@ public class ConfigManager {
public void tryCreateDefault(ConfigLoader configLoader) {
try {
configLoader.createDefault(baseDataPath);
} catch (Throwable t) {
logConfigLoadException(configLoader, t);
configLoader.createDefault();
} catch (ConfigException e) {
logConfigInitException(configLoader.getFileName(), e);
}
}
public Path getMenusFolder() {
return baseDataPath.resolve("menu");
return rootDataFolder.resolve("menu");
}
/**
* Returns a list of YML menu files.
*/
public List<Path> getMenuPaths() throws IOException {
Preconditions.checkState(Files.isDirectory(getMenusFolder()), "menus folder doesn't exist");
try (Stream<Path> paths = Files.walk(getMenusFolder(), FileVisitOption.FOLLOW_LINKS)) {
return paths.filter(Files::isRegularFile)
.filter(this::isYmlPath)
@ -110,39 +106,15 @@ public class ConfigManager {
}
}
private boolean isYmlPath(Path path) {
return path.getFileName().toString().toLowerCase().endsWith(".yml");
}
private void logConfigLoadException(ConfigLoader configLoader, Throwable t) {
t.printStackTrace();
if (t instanceof IOException) {
Log.warning("Error while reading the file \"" + configLoader.getFileName() + "\". Default values will be used.");
} else if (t instanceof InvalidConfigurationException) {
Log.warning("Invalid YAML syntax in the file \"" + configLoader.getFileName() + "\", please look at the error above. Default values will be used.");
private void logConfigInitException(String fileName, ConfigException e) {
if (e instanceof ConfigSyntaxException) {
Log.warning("Invalid YAML syntax in config file \"" + fileName + "\": " + e.getMessage());
} else {
Log.warning("Unhandled error while parsing the file \"" + configLoader.getFileName() + "\". Please inform the developer.");
e.printStackTrace();
Log.warning("Error while reading config file \"" + fileName + "\": " + e.getMessage());
}
}
public Path getBaseDataPath() {
return baseDataPath;
}
public ConfigLoader getSettingsConfigLoader() {
return settingsConfigLoader;
}
public ConfigLoader getPlaceholdersConfigLoader() {
return placeholdersConfigLoader;
}
public ConfigLoader getLangConfigLoader() {
return langConfigLoader;
}
public List<LoadedMenu> tryLoadMenus(ErrorCollector errorCollector) {
List<LoadedMenu> loadedMenus = new ArrayList<>();
List<Path> menuPaths;
@ -155,16 +127,21 @@ public class ConfigManager {
}
for (Path menuFile : menuPaths) {
ConfigLoader menuConfigLoader = new ConfigLoader(menuFile);
ConfigLoader menuConfigLoader = new ConfigLoader(rootDataFolder, menuFile);
try {
Config menuConfig = menuConfigLoader.load();
loadedMenus.add(MenuParser.loadMenu(menuConfig, errorCollector));
} catch (Throwable t) {
logConfigLoadException(menuConfigLoader, t);
} catch (ConfigException e) {
logConfigInitException(menuConfigLoader.getFileName(), e);
}
}
return loadedMenus;
}
private boolean isYmlPath(Path path) {
return path.getFileName().toString().toLowerCase().endsWith(".yml");
}
}

View File

@ -1,9 +0,0 @@
package me.filoghost.chestcommands.config;
public class ConfigValueException extends Exception {
public ConfigValueException(String message) {
super(message);
}
}

View File

@ -12,12 +12,12 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.files;
package me.filoghost.chestcommands.config;
import me.filoghost.chestcommands.config.Config;
import me.filoghost.chestcommands.util.collection.ErrorCollector;
import me.filoghost.chestcommands.config.framework.Config;
import me.filoghost.chestcommands.util.Colors;
import me.filoghost.chestcommands.util.collection.CollectionUtils;
import me.filoghost.chestcommands.util.collection.ErrorCollector;
import java.util.HashMap;
import java.util.List;
@ -36,12 +36,12 @@ public class CustomPlaceholders {
String replacement = Colors.addColors(config.getString(key));
if (placeholder.length() == 0) {
errorCollector.addError("Error in " + config.getFileName() + ": placeholder cannot be empty (skipped).");
errorCollector.addError("Error in " + config.getSourceFileName() + ": placeholder cannot be empty (skipped).");
continue;
}
if (placeholder.length() > 100) {
errorCollector.addError("Error in " + config.getFileName() + ": placeholder cannot be longer than 100 character (" + placeholder + ").");
errorCollector.addError("Error in " + config.getSourceFileName() + ": placeholder cannot be longer than 100 character (" + placeholder + ").");
continue;
}

View File

@ -12,11 +12,13 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.files;
package me.filoghost.chestcommands.config;
import me.filoghost.chestcommands.config.SpecialConfig;
import me.filoghost.chestcommands.config.framework.mapped.MappedConfig;
import me.filoghost.chestcommands.config.framework.mapped.modifier.ChatColors;
public class Lang extends SpecialConfig {
@ChatColors
public class Lang extends MappedConfig {
public String no_open_permission = "&cYou don't have permission &e{permission} &cto use this menu.";
public String default_no_icon_permission = "&cYou don't have permission for this icon.";

View File

@ -12,11 +12,13 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.files;
package me.filoghost.chestcommands.config;
import me.filoghost.chestcommands.config.SpecialConfig;
import me.filoghost.chestcommands.config.framework.mapped.MappedConfig;
import me.filoghost.chestcommands.config.framework.mapped.modifier.ChatColors;
public class Settings extends SpecialConfig {
@ChatColors
public class Settings extends MappedConfig {
public String default_color__name = "&f";
public String default_color__lore = "&7";
@ -25,8 +27,8 @@ public class Settings extends SpecialConfig {
public Settings() {
setHeader(
"ChestCommands main configuration file.\n" +
"Documentation: https://filoghost.me/docs/chest-commands\n");
"ChestCommands main configuration file.",
"Documentation: https://filoghost.me/docs/chest-commands");
}
}

View File

@ -1,128 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config;
import me.filoghost.chestcommands.util.Colors;
import me.filoghost.chestcommands.util.Log;
import org.bukkit.configuration.InvalidConfigurationException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* A special configuration wrapper that reads the values using reflection.
* It will also save default values if not set.
*/
public abstract class SpecialConfig {
private transient String header;
private transient Map<String, Object> defaultValuesMap;
public void setHeader(String header) {
this.header = header;
}
public void load(ConfigLoader loader) throws IOException, IllegalAccessException, InvalidConfigurationException {
Config config = loader.load();
// Check if the configuration was initialized
if (defaultValuesMap == null) {
defaultValuesMap = new HashMap<>();
// Put the values in the default values map
for (Field field : getClass().getDeclaredFields()) {
if (skipField(field)) continue;
field.setAccessible(true);
String configKey = getConfigNode(field);
try {
Object defaultValue = field.get(this);
if (defaultValue != null) {
defaultValuesMap.put(configKey, defaultValue);
} else {
Log.warning("The field " + field.getName() + " was not provided with a default value, please inform the developer.");
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
boolean needsSave = false;
// Save default values not set
for (Entry<String, Object> entry : defaultValuesMap.entrySet()) {
if (!config.isSet(entry.getKey())) {
needsSave = true;
config.set(entry.getKey(), entry.getValue());
}
}
if (needsSave) {
config.setHeader(header);
loader.save(config);
}
// Now read change the fields
for (Field field : getClass().getDeclaredFields()) {
if (skipField(field)) {
continue;
}
field.setAccessible(true);
String configNode = getConfigNode(field);
if (config.isSet(configNode)) {
Class<?> type = field.getType();
if (type == boolean.class || type == Boolean.class) {
field.set(this, config.getBoolean(configNode));
} else if (type == int.class || type == Integer.class) {
field.set(this, config.getInt(configNode));
} else if (type == double.class || type == Double.class) {
field.set(this, config.getDouble(configNode));
} else if (type == String.class) {
field.set(this, Colors.addColors(config.getString(configNode))); // Always add colors
} else {
Log.warning("Unknown field type: " + field.getType().getName() + " (" + field.getName() + "). Please inform the developer.");
}
} else {
field.set(this, defaultValuesMap.get(configNode));
}
}
}
private boolean skipField(Field field) {
int modifiers = field.getModifiers();
return Modifier.isTransient(modifiers) || Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers);
}
private String getConfigNode(Field field) {
return field.getName().replace("__", ".").replace("_", "-");
}
}

View File

@ -0,0 +1,51 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework;
import me.filoghost.chestcommands.config.framework.mapped.MappedConfig;
import me.filoghost.chestcommands.config.framework.mapped.MappedConfigLoader;
import java.nio.file.Path;
import java.util.function.Supplier;
public class BaseConfigManager {
protected final Path rootDataFolder;
public BaseConfigManager(Path rootDataFolder) {
this.rootDataFolder = rootDataFolder;
}
public Path getRootDataFolder() {
return rootDataFolder;
}
public ConfigLoader getConfigLoader(String fileName) {
return getConfigLoader(rootDataFolder.resolve(fileName));
}
public ConfigLoader getConfigLoader(Path configPath) {
return new ConfigLoader(rootDataFolder, configPath);
}
public <T extends MappedConfig> MappedConfigLoader<T> getMappedConfigLoader(String fileName, Supplier<T> mappedObjectConstructor) {
return getMappedConfigLoader(rootDataFolder.resolve(fileName), mappedObjectConstructor);
}
public <T extends MappedConfig> MappedConfigLoader<T> getMappedConfigLoader(Path configPath, Supplier<T> mappedObjectConstructor) {
return new MappedConfigLoader<>(rootDataFolder, configPath, mappedObjectConstructor);
}
}

View File

@ -12,7 +12,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config;
package me.filoghost.chestcommands.config.framework;
import org.bukkit.configuration.file.YamlConfiguration;
@ -21,16 +21,20 @@ import java.nio.file.Path;
public class Config extends ConfigSection {
private final YamlConfiguration yaml;
private final Path filePath;
private final Path sourceFilePath;
public Config(YamlConfiguration yaml, Path filePath) {
super(yaml);
this.yaml = yaml;
this.filePath = filePath;
public Config(Path sourceFilePath) {
this(new YamlConfiguration(), sourceFilePath);
}
public String getFileName() {
return filePath.getFileName().toString();
public Config(YamlConfiguration yaml, Path sourceFilePath) {
super(yaml);
this.yaml = yaml;
this.sourceFilePath = sourceFilePath;
}
public String getSourceFileName() {
return sourceFilePath.getFileName().toString();
}
public String saveToString() {

View File

@ -0,0 +1,143 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework;
import me.filoghost.chestcommands.config.framework.exception.ConfigLoadException;
import me.filoghost.chestcommands.config.framework.exception.ConfigSaveException;
import me.filoghost.chestcommands.config.framework.exception.ConfigSyntaxException;
import me.filoghost.chestcommands.util.Preconditions;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
public class ConfigLoader {
private final Path rootDataFolder;
private final Path configPath;
public ConfigLoader(Path rootDataFolder, Path configPath) {
Preconditions.checkArgument(configPath.startsWith(rootDataFolder), "config file " + configPath + " cannot be outside " + rootDataFolder);
this.rootDataFolder = rootDataFolder;
this.configPath = configPath;
}
public Config init() throws ConfigSaveException, ConfigLoadException {
createDefault();
return load();
}
public void createDefault() throws ConfigSaveException {
if (fileExists()) {
return;
}
createParentDirectory();
Path relativeConfigPath = rootDataFolder.relativize(configPath);
String internalJarPath = toInternalJarPath(relativeConfigPath);
try (InputStream defaultFile = getInternalResource(internalJarPath)) {
if (defaultFile != null) {
Files.copy(defaultFile, configPath);
} else {
Files.createFile(configPath);
}
} catch (IOException e) {
throw new ConfigSaveException("couldn't create default config file " + configPath, e);
}
}
private String toInternalJarPath(Path path) {
return StreamSupport.stream(path.spliterator(), false)
.map(Path::toString)
.collect(Collectors.joining("/", "/", ""));
}
private InputStream getInternalResource(String internalJarPath) throws IOException {
Preconditions.notNull(internalJarPath, "internalJarPath");
URL resourceURL = getClass().getResource(internalJarPath);
if (resourceURL == null) {
return null;
}
URLConnection connection = resourceURL.openConnection();
connection.setUseCaches(false);
return connection.getInputStream();
}
public boolean fileExists() {
return (Files.isRegularFile(configPath));
}
public Config load() throws ConfigLoadException {
Preconditions.checkState(fileExists(), configPath.getFileName() + " doesn't exist or is not a regular file");
YamlConfiguration yaml = new YamlConfiguration();
try (BufferedReader reader = Files.newBufferedReader(configPath)) {
yaml.load(reader);
} catch (IOException e) {
throw new ConfigLoadException("couldn't read config file " + configPath, e);
} catch (InvalidConfigurationException e) {
throw new ConfigSyntaxException(e.getMessage(), e);
}
return new Config(yaml, configPath);
}
public void save(Config config) throws ConfigSaveException {
createParentDirectory();
String data = config.saveToString();
try (BufferedWriter writer = Files.newBufferedWriter(configPath)) {
writer.write(data);
} catch (IOException e) {
throw new ConfigSaveException("couldn't write config data to " + configPath, e);
}
}
private void createParentDirectory() throws ConfigSaveException {
if (configPath.getParent() != null) {
try {
Files.createDirectories(configPath.getParent());
} catch (IOException e) {
throw new ConfigSaveException("couldn't create directory " + configPath.getParent(), e);
}
}
}
public Path getConfigPath() {
return configPath;
}
public String getFileName() {
return configPath.getFileName().toString();
}
}

View File

@ -1,5 +1,6 @@
package me.filoghost.chestcommands.config;
package me.filoghost.chestcommands.config.framework;
import me.filoghost.chestcommands.config.framework.exception.ConfigValueException;
import org.bukkit.configuration.ConfigurationSection;
import java.util.ArrayList;
@ -75,10 +76,6 @@ public class ConfigSection {
return yamlSection.getKeys(deep);
}
public boolean contains(String path) {
return yamlSection.contains(path);
}
public boolean isSet(String path) {
return yamlSection.isSet(path);
}
@ -91,10 +88,6 @@ public class ConfigSection {
return yamlSection.getString(path);
}
public String getString(String path, String def) {
return yamlSection.getString(path, def);
}
public int getInt(String path) {
return yamlSection.getInt(path);
}
@ -107,10 +100,6 @@ public class ConfigSection {
return yamlSection.getDouble(path);
}
public long getLong(String path) {
return yamlSection.getLong(path);
}
public List<String> getStringList(String path) {
return yamlSection.getStringList(path);
}

View File

@ -0,0 +1,13 @@
package me.filoghost.chestcommands.config.framework.exception;
public class ConfigException extends Exception {
public ConfigException(String message) {
super(message);
}
public ConfigException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,9 @@
package me.filoghost.chestcommands.config.framework.exception;
public class ConfigLoadException extends ConfigException {
public ConfigLoadException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,9 @@
package me.filoghost.chestcommands.config.framework.exception;
public class ConfigSaveException extends ConfigException {
public ConfigSaveException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,11 @@
package me.filoghost.chestcommands.config.framework.exception;
import org.bukkit.configuration.InvalidConfigurationException;
public class ConfigSyntaxException extends ConfigLoadException {
public ConfigSyntaxException(String message, InvalidConfigurationException cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,9 @@
package me.filoghost.chestcommands.config.framework.exception;
public class ConfigValueException extends ConfigException {
public ConfigValueException(String message) {
super(message);
}
}

View File

@ -0,0 +1,157 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework.mapped;
import com.google.common.collect.ImmutableList;
import me.filoghost.chestcommands.config.framework.ConfigSection;
import me.filoghost.chestcommands.config.framework.mapped.converter.BooleanConverter;
import me.filoghost.chestcommands.config.framework.mapped.converter.Converter;
import me.filoghost.chestcommands.config.framework.mapped.converter.DoubleConverter;
import me.filoghost.chestcommands.config.framework.mapped.converter.IntegerConverter;
import me.filoghost.chestcommands.config.framework.mapped.converter.ListConverter;
import me.filoghost.chestcommands.config.framework.mapped.converter.StringConverter;
import me.filoghost.chestcommands.config.framework.mapped.modifier.ChatColorsModifier;
import me.filoghost.chestcommands.config.framework.mapped.modifier.ValueModifier;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public class ConfigMapper {
private static final List<Converter> CONVERTERS = ImmutableList.of(
new DoubleConverter(),
new IntegerConverter(),
new BooleanConverter(),
new StringConverter(),
new ListConverter()
);
private static final List<ValueModifier<?, ?>> VALUE_MODIFIERS = ImmutableList.of(
new ChatColorsModifier()
);
private final MappedConfig mappedObject;
private final ConfigSection config;
private final List<MappedField> mappedFields;
public ConfigMapper(MappedConfig mappedObject, ConfigSection config) throws ReflectiveOperationException {
this.mappedObject = mappedObject;
this.config = config;
this.mappedFields = getMappableFields(mappedObject.getClass());
}
private List<MappedField> getMappableFields(Class<?> type) throws ReflectiveOperationException {
Field[] declaredFields;
try {
declaredFields = type.getDeclaredFields();
} catch (Throwable t) {
throw new ReflectiveOperationException(t);
}
return Arrays.stream(declaredFields)
.filter(this::isMappable)
.map(MappedField::new)
.collect(Collectors.toList());
}
public Map<MappedField, Object> getFieldValues() throws ReflectiveOperationException {
Map<MappedField, Object> mappedFieldValues = new HashMap<>();
for (MappedField mappedField : mappedFields) {
Object defaultValue = mappedField.getFromObject(mappedObject);
if (defaultValue == null) {
throw new IllegalArgumentException("mapped field \"" + mappedField.getFieldName() + "\" cannot be null by default");
}
mappedFieldValues.put(mappedField, defaultValue);
}
return mappedFieldValues;
}
public boolean addMissingConfigValues(Map<MappedField, Object> defaultValues) {
boolean modified = false;
// Add missing values from defaults
for (Entry<MappedField, Object> entry : defaultValues.entrySet()) {
MappedField mappedField = entry.getKey();
Object defaultValue = entry.getValue();
if (!config.isSet(mappedField.getConfigPath())) {
modified = true;
Converter converter = findConverter(mappedField.getFieldType());
converter.setConfigValue(config, mappedField.getConfigPath(), defaultValue);
}
}
return modified;
}
public void injectObjectFields() throws ReflectiveOperationException {
for (MappedField mappedField : mappedFields) {
injectObjectField(mappedField);
}
}
private void injectObjectField(MappedField mappedField) throws ReflectiveOperationException {
Type[] genericTypes = mappedField.getGenericTypes();
Converter converter = findConverter(mappedField.getFieldType());
Object fieldValue = converter.getFieldValue(config, mappedField.getConfigPath(), genericTypes);
for (Annotation annotation : mappedField.getAnnotations()) {
fieldValue = applyValueModifiers(fieldValue, annotation);
}
mappedField.setToObject(mappedObject, fieldValue);
}
private Object applyValueModifiers(Object fieldValue, Annotation annotation) {
for (ValueModifier<?, ?> modifier : VALUE_MODIFIERS) {
if (modifier.isApplicable(annotation, fieldValue)) {
fieldValue = modifier.transform(annotation, fieldValue);
}
}
return fieldValue;
}
private Converter findConverter(Class<?> type) {
return CONVERTERS.stream()
.filter(converter -> converter.matches(type))
.findFirst()
.orElseThrow(() -> new IllegalStateException("cannot find converter for type " + type));
}
private boolean isMappable(Field field) {
int modifiers = field.getModifiers();
boolean includeStatic = field.isAnnotationPresent(IncludeStatic.class)
|| field.getDeclaringClass().isAnnotationPresent(IncludeStatic.class);
return (!Modifier.isStatic(modifiers) || includeStatic)
|| !Modifier.isTransient(modifiers)
|| !Modifier.isFinal(modifiers);
}
}

View File

@ -0,0 +1,26 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework.mapped;
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, ElementType.TYPE})
public @interface IncludeStatic {
}

View File

@ -0,0 +1,33 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework.mapped;
import me.filoghost.chestcommands.config.framework.exception.ConfigLoadException;
public class MappedConfig {
private String header;
protected void setHeader(String... header) {
this.header = String.join("\n", header) + "\n";
}
public String getHeader() {
return header;
}
public void postLoad() throws ConfigLoadException {}
}

View File

@ -0,0 +1,78 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework.mapped;
import me.filoghost.chestcommands.config.framework.Config;
import me.filoghost.chestcommands.config.framework.ConfigLoader;
import me.filoghost.chestcommands.config.framework.exception.ConfigLoadException;
import me.filoghost.chestcommands.config.framework.exception.ConfigSaveException;
import java.nio.file.Path;
import java.util.Map;
import java.util.function.Supplier;
public class MappedConfigLoader<T extends MappedConfig> {
private final ConfigLoader configLoader;
private final Supplier<T> mappedObjectConstructor;
private Map<MappedField, Object> defaultValues;
public MappedConfigLoader(Path rootDataFolder, Path configPath, Supplier<T> mappedObjectConstructor) {
this.configLoader = new ConfigLoader(rootDataFolder, configPath);
this.mappedObjectConstructor = mappedObjectConstructor;
}
public T init() throws ConfigLoadException, ConfigSaveException {
Config config = configLoader.init();
T mappedObject = mappedObjectConstructor.get();
ConfigMapper mapper;
try {
mapper = new ConfigMapper(mappedObject, config);
} catch (ReflectiveOperationException e) {
throw new ConfigLoadException("couldn't initialize config mapper for class " + mappedObject.getClass(), e);
}
// Extract default values from fields
if (defaultValues == null) {
try {
defaultValues = mapper.getFieldValues();
} catch (ReflectiveOperationException e) {
throw new ConfigLoadException("couldn't read field values in class " + mappedObject.getClass(), e);
}
}
// Add missing values and save if necessary
boolean modified = mapper.addMissingConfigValues(defaultValues);
if (modified) {
config.setHeader(mappedObject.getHeader());
configLoader.save(config);
}
// Update the mapped object with the contents from the config
try {
mapper.injectObjectFields();
} catch (ReflectiveOperationException e) {
throw new ConfigLoadException("couldn't inject fields values in class " + mappedObject.getClass(), e);
}
mappedObject.postLoad();
return mappedObject;
}
public String getFileName() {
return configLoader.getFileName();
}
}

View File

@ -0,0 +1,88 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework.mapped;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MappedField {
private final Field field;
private final String configPath;
public MappedField(Field field) {
this.field = field;
this.configPath = field.getName()
.replace("__", ".")
.replace("_", "-");
}
public Object getFromObject(MappedConfig mappedObject) throws ReflectiveOperationException {
try {
field.setAccessible(true);
return field.get(mappedObject);
} catch (Throwable t) {
throw new ReflectiveOperationException(t);
}
}
public void setToObject(MappedConfig mappedObject, Object fieldValue) throws ReflectiveOperationException {
try {
field.setAccessible(true);
field.set(mappedObject, fieldValue);
} catch (Throwable t) {
throw new ReflectiveOperationException(t);
}
}
public Type[] getGenericTypes() throws ReflectiveOperationException {
try {
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
return ((ParameterizedType) genericType).getActualTypeArguments();
} else {
return null;
}
} catch (Throwable t) {
throw new ReflectiveOperationException(t);
}
}
public List<Annotation> getAnnotations() {
return Stream.concat(
Arrays.stream(field.getDeclaredAnnotations()),
Arrays.stream(field.getDeclaringClass().getDeclaredAnnotations()))
.collect(Collectors.toList());
}
public String getFieldName() {
return field.getName();
}
public Class<?> getFieldType() {
return field.getType();
}
public String getConfigPath() {
return configPath;
}
}

View File

@ -0,0 +1,38 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework.mapped.converter;
import me.filoghost.chestcommands.config.framework.ConfigSection;
import java.lang.reflect.Type;
public class BooleanConverter implements Converter {
@Override
public void setConfigValue(ConfigSection config, String path, Object value) {
config.set(path, value);
}
@Override
public Boolean getFieldValue(ConfigSection config, String path, Type[] parameterizedTypes) {
return config.getBoolean(path);
}
@Override
public boolean matches(Class<?> type) {
return type == Boolean.class || type == boolean.class;
}
}

View File

@ -0,0 +1,29 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework.mapped.converter;
import me.filoghost.chestcommands.config.framework.ConfigSection;
import java.lang.reflect.Type;
public interface Converter {
void setConfigValue(ConfigSection config, String path, Object value);
Object getFieldValue(ConfigSection config, String path, Type[] genericTypes);
boolean matches(Class<?> type);
}

View File

@ -0,0 +1,38 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework.mapped.converter;
import me.filoghost.chestcommands.config.framework.ConfigSection;
import java.lang.reflect.Type;
public class DoubleConverter implements Converter {
@Override
public void setConfigValue(ConfigSection config, String path, Object value) {
config.set(path, value);
}
@Override
public Double getFieldValue(ConfigSection config, String path, Type[] parameterizedTypes) {
return config.getDouble(path);
}
@Override
public boolean matches(Class<?> type) {
return type == Double.class || type == double.class;
}
}

View File

@ -0,0 +1,38 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework.mapped.converter;
import me.filoghost.chestcommands.config.framework.ConfigSection;
import java.lang.reflect.Type;
public class IntegerConverter implements Converter {
@Override
public void setConfigValue(ConfigSection config, String path, Object value) {
config.set(path, value);
}
@Override
public Integer getFieldValue(ConfigSection config, String path, Type[] parameterizedTypes) {
return config.getInt(path);
}
@Override
public boolean matches(Class<?> type) {
return type == Integer.class || type == int.class;
}
}

View File

@ -0,0 +1,51 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework.mapped.converter;
import me.filoghost.chestcommands.config.framework.ConfigSection;
import me.filoghost.chestcommands.util.Preconditions;
import java.lang.reflect.Type;
import java.util.List;
public class ListConverter implements Converter {
@Override
public void setConfigValue(ConfigSection config, String path, Object value) {
config.set(path, value);
}
@Override
public List<?> getFieldValue(ConfigSection config, String path, Type[] genericTypes) {
Preconditions.notNull(genericTypes, "genericTypes");
Preconditions.checkArgument(genericTypes.length == 1, "genericTypes length must be 1");
Type listType = genericTypes[0];
if (listType == Integer.class) {
return config.getIntegerList(path);
} else if (listType == String.class) {
return config.getStringList(path);
} else {
throw new IllegalArgumentException("unsupported list type: " + listType);
}
}
@Override
public boolean matches(Class<?> type) {
return type == List.class;
}
}

View File

@ -0,0 +1,38 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework.mapped.converter;
import me.filoghost.chestcommands.config.framework.ConfigSection;
import java.lang.reflect.Type;
public class StringConverter implements Converter {
@Override
public void setConfigValue(ConfigSection config, String path, Object value) {
config.set(path, value);
}
@Override
public String getFieldValue(ConfigSection config, String path, Type[] parameterizedTypes) {
return config.getString(path);
}
@Override
public boolean matches(Class<?> type) {
return type == String.class;
}
}

View File

@ -0,0 +1,26 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework.mapped.modifier;
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, ElementType.TYPE})
public @interface ChatColors {
}

View File

@ -0,0 +1,36 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework.mapped.modifier;
import me.filoghost.chestcommands.util.Colors;
public class ChatColorsModifier implements ValueModifier<String, ChatColors> {
@Override
public String transformChecked(ChatColors annotation, String value) {
return Colors.addColors(value);
}
@Override
public Class<ChatColors> getAnnotationType() {
return ChatColors.class;
}
@Override
public Class<String> getValueType() {
return String.class;
}
}

View File

@ -0,0 +1,38 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package me.filoghost.chestcommands.config.framework.mapped.modifier;
import me.filoghost.chestcommands.util.Preconditions;
import java.lang.annotation.Annotation;
public interface ValueModifier<V, A extends Annotation> {
V transformChecked(A annotation, V value);
Class<A> getAnnotationType();
Class<V> getValueType();
default boolean isApplicable(Annotation annotation, Object value) {
return getAnnotationType().isInstance(annotation) && getValueType().isInstance(value);
}
default Object transform(Annotation annotation, Object fieldValue) {
Preconditions.checkArgument(isApplicable(annotation, fieldValue), "modifier doesn't match given types");
return transformChecked(getAnnotationType().cast(annotation), getValueType().cast(fieldValue));
}
}

View File

@ -14,10 +14,9 @@
*/
package me.filoghost.chestcommands.legacy;
import me.filoghost.chestcommands.config.Config;
import me.filoghost.chestcommands.config.ConfigLoader;
import me.filoghost.chestcommands.config.framework.exception.ConfigLoadException;
import me.filoghost.chestcommands.config.framework.exception.ConfigSaveException;
import me.filoghost.chestcommands.util.Preconditions;
import org.bukkit.configuration.InvalidConfigurationException;
import java.io.IOException;
import java.nio.file.Files;
@ -40,7 +39,11 @@ public abstract class Upgrade {
Preconditions.checkState(!hasRun, "Upgrade can only be run once");
hasRun = true;
computeChanges();
try {
computeChanges();
} catch (ConfigLoadException e) {
throw new UpgradeException("couldn't load file to upgrade \"" + getOriginalFile().getFileName() + "\"", e);
}
if (modified) {
try {
@ -51,7 +54,7 @@ public abstract class Upgrade {
try {
saveChanges();
} catch (IOException e) {
} catch (ConfigSaveException e) {
throw new UpgradeException("couldn't save upgraded file \"" + getUpgradedFile().getFileName() + "\"", e);
}
}
@ -59,16 +62,6 @@ public abstract class Upgrade {
return modified;
}
protected Config loadConfig(ConfigLoader configLoader) throws UpgradeException {
try {
return configLoader.load();
} catch (IOException e) {
throw new UpgradeException("couldn't read configuration file \"" + configLoader.getFileName() + "\"", e);
} catch (InvalidConfigurationException e) {
throw new UpgradeException("couldn't parse YAML syntax of file \"" + configLoader.getFileName() + "\"", e);
}
}
private void createBackupFile(Path path) throws IOException {
String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd-HH.mm"));
String backupName = path.getFileName() + "_" + date + ".backup";
@ -80,8 +73,8 @@ public abstract class Upgrade {
public abstract Path getUpgradedFile();
protected abstract void computeChanges() throws UpgradeException;
protected abstract void computeChanges() throws ConfigLoadException;
protected abstract void saveChanges() throws IOException;
protected abstract void saveChanges() throws ConfigSaveException;
}

View File

@ -15,7 +15,7 @@
package me.filoghost.chestcommands.legacy;
import me.filoghost.chestcommands.config.ConfigManager;
import me.filoghost.chestcommands.config.ConfigLoader;
import me.filoghost.chestcommands.config.framework.ConfigLoader;
import me.filoghost.chestcommands.legacy.UpgradesDoneRegistry.UpgradeID;
import me.filoghost.chestcommands.legacy.upgrades.MenuUpgrade;
import me.filoghost.chestcommands.legacy.upgrades.PlaceholdersUpgrade;
@ -42,7 +42,7 @@ public class UpgradesExecutor {
public void run(boolean isFreshInstall) throws UpgradeExecutorException {
this.failedUpgrades = new ArrayList<>();
Path upgradesDoneFile = configManager.getBaseDataPath().resolve(".upgrades-done");
Path upgradesDoneFile = configManager.getRootDataFolder().resolve(".upgrades-done");
try {
upgradesDoneRegistry = new UpgradesDoneRegistry(upgradesDoneFile);
@ -56,18 +56,23 @@ public class UpgradesExecutor {
upgradesDoneRegistry.setAllDone();
} else {
String legacyCommandSeparator = readLegacyCommandSeparator();
String legacyCommandSeparator;
if (!upgradesDoneRegistry.isDone(UpgradeID.V4_MENUS)) {
legacyCommandSeparator = readLegacyCommandSeparator();
} else {
legacyCommandSeparator = null;
}
SettingsUpgrade settingsUpgrade = new SettingsUpgrade(configManager.getSettingsConfigLoader());
SettingsUpgrade settingsUpgrade = new SettingsUpgrade(configManager);
runIfNecessary(UpgradeID.V4_CONFIG, settingsUpgrade);
PlaceholdersUpgrade placeholdersUpgrade = new PlaceholdersUpgrade(configManager.getPlaceholdersConfigLoader(), configManager.getBaseDataPath());
PlaceholdersUpgrade placeholdersUpgrade = new PlaceholdersUpgrade(configManager);
runIfNecessary(UpgradeID.V4_PLACEHOLDERS, placeholdersUpgrade);
try {
List<MenuUpgrade> menuUpgrades = CollectionUtils.transform(
configManager.getMenuPaths(),
menuPath -> new MenuUpgrade(new ConfigLoader(menuPath), legacyCommandSeparator));
menuPath -> new MenuUpgrade(configManager.getConfigLoader(menuPath), legacyCommandSeparator));
runIfNecessary(UpgradeID.V4_MENUS, menuUpgrades);
} catch (IOException e) {
failedUpgrades.add(configManager.getMenusFolder());
@ -91,18 +96,18 @@ public class UpgradesExecutor {
}
private String readLegacyCommandSeparator() {
String legacyCommandSeparator;
ConfigLoader settingsConfigLoader = configManager.getSettingsConfigLoader();
ConfigLoader settingsConfigLoader = configManager.getConfigLoader("config.yml");
try {
legacyCommandSeparator = settingsConfigLoader.load().getString("multiple-commands-separator", ";");
} catch (Exception e) {
legacyCommandSeparator = ";";
Log.severe("Failed to load " + settingsConfigLoader.getFileName()
+ ", assuming default command separator \"" + legacyCommandSeparator + "\".");
if (!settingsConfigLoader.fileExists()) {
return null;
}
return legacyCommandSeparator;
try {
return settingsConfigLoader.load().getString("multiple-commands-separator");
} catch (Throwable t) {
Log.severe("Failed to load " + settingsConfigLoader.getFileName() + ", assuming default command separator \";\".");
return null;
}
}
@ -143,8 +148,7 @@ public class UpgradesExecutor {
private void logUpgradeException(Upgrade upgrade, UpgradeException upgradeException) {
Log.severe(
"Error while trying to automatically upgrade "
+ upgrade.getOriginalFile() + ": " + upgradeException.getMessage(),
"Error while trying to automatically upgrade " + upgrade.getOriginalFile() + ": " + upgradeException.getMessage(),
upgradeException.getCause());
}

View File

@ -14,16 +14,16 @@
*/
package me.filoghost.chestcommands.legacy.upgrades;
import me.filoghost.chestcommands.config.Config;
import me.filoghost.chestcommands.config.ConfigLoader;
import me.filoghost.chestcommands.config.ConfigSection;
import me.filoghost.chestcommands.config.framework.Config;
import me.filoghost.chestcommands.config.framework.ConfigLoader;
import me.filoghost.chestcommands.config.framework.ConfigSection;
import me.filoghost.chestcommands.config.framework.exception.ConfigLoadException;
import me.filoghost.chestcommands.config.framework.exception.ConfigSaveException;
import me.filoghost.chestcommands.legacy.Upgrade;
import me.filoghost.chestcommands.legacy.UpgradeException;
import me.filoghost.chestcommands.parsing.menu.MenuSettingsNode;
import me.filoghost.chestcommands.parsing.icon.IconSettingsNode;
import me.filoghost.chestcommands.parsing.menu.MenuSettingsNode;
import me.filoghost.chestcommands.util.Strings;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
@ -43,17 +43,17 @@ public class MenuUpgrade extends Upgrade {
@Override
public Path getOriginalFile() {
return menuConfigLoader.getPath();
return menuConfigLoader.getConfigPath();
}
@Override
public Path getUpgradedFile() {
return menuConfigLoader.getPath();
return menuConfigLoader.getConfigPath();
}
@Override
protected void computeChanges() throws UpgradeException {
Config menuConfig = loadConfig(menuConfigLoader);
protected void computeChanges() throws ConfigLoadException {
Config menuConfig = menuConfigLoader.load();
menuConfig.setHeader(null);
for (String key : menuConfig.getKeys(true)) {
@ -74,7 +74,7 @@ public class MenuUpgrade extends Upgrade {
}
@Override
protected void saveChanges() throws IOException {
protected void saveChanges() throws ConfigSaveException {
menuConfigLoader.save(updatedConfig);
}

View File

@ -14,10 +14,12 @@
*/
package me.filoghost.chestcommands.legacy.upgrades;
import me.filoghost.chestcommands.config.Config;
import me.filoghost.chestcommands.config.ConfigLoader;
import me.filoghost.chestcommands.config.ConfigManager;
import me.filoghost.chestcommands.config.framework.Config;
import me.filoghost.chestcommands.config.framework.ConfigLoader;
import me.filoghost.chestcommands.config.framework.exception.ConfigLoadException;
import me.filoghost.chestcommands.config.framework.exception.ConfigSaveException;
import me.filoghost.chestcommands.legacy.Upgrade;
import me.filoghost.chestcommands.legacy.UpgradeException;
import me.filoghost.chestcommands.util.Strings;
import org.apache.commons.lang.StringEscapeUtils;
@ -28,13 +30,13 @@ import java.util.List;
public class PlaceholdersUpgrade extends Upgrade {
private final ConfigLoader newPlaceholdersConfigLoader;
private final Path oldPlaceholdersFile;
private final ConfigLoader newPlaceholdersConfigLoader;
private Config updatedConfig;
public PlaceholdersUpgrade(ConfigLoader placeholdersConfigLoader, Path dataPath) {
this.newPlaceholdersConfigLoader = placeholdersConfigLoader;
this.oldPlaceholdersFile = dataPath.resolve("placeholders.yml");
public PlaceholdersUpgrade(ConfigManager configManager) {
this.oldPlaceholdersFile = configManager.getRootDataFolder().resolve("placeholders.yml");
this.newPlaceholdersConfigLoader = configManager.getConfigLoader("custom-placeholders.yml");
}
@Override
@ -44,22 +46,22 @@ public class PlaceholdersUpgrade extends Upgrade {
@Override
public Path getUpgradedFile() {
return newPlaceholdersConfigLoader.getPath();
return newPlaceholdersConfigLoader.getConfigPath();
}
@Override
protected void computeChanges() throws UpgradeException {
protected void computeChanges() throws ConfigLoadException {
if (!Files.isRegularFile(oldPlaceholdersFile)) {
return;
}
// Do NOT load the new placeholder configuration from disk, as it should only contain placeholders imported from the old file
Config newPlaceholdersConfig = newPlaceholdersConfigLoader.loadEmpty();
Config newPlaceholdersConfig = new Config(newPlaceholdersConfigLoader.getConfigPath());
List<String> lines;
try {
lines = Files.readAllLines(oldPlaceholdersFile);
} catch (IOException e) {
throw new UpgradeException("couldn't read file \"" + oldPlaceholdersFile.getFileName() + "\"", e);
throw new ConfigLoadException("couldn't read file \"" + oldPlaceholdersFile.getFileName() + "\"", e);
}
for (String line : lines) {
@ -85,7 +87,7 @@ public class PlaceholdersUpgrade extends Upgrade {
}
@Override
protected void saveChanges() throws IOException {
protected void saveChanges() throws ConfigSaveException {
try {
Files.deleteIfExists(oldPlaceholdersFile);
} catch (IOException ignored) {}

View File

@ -14,12 +14,13 @@
*/
package me.filoghost.chestcommands.legacy.upgrades;
import me.filoghost.chestcommands.config.Config;
import me.filoghost.chestcommands.config.ConfigLoader;
import me.filoghost.chestcommands.config.ConfigManager;
import me.filoghost.chestcommands.config.framework.Config;
import me.filoghost.chestcommands.config.framework.ConfigLoader;
import me.filoghost.chestcommands.config.framework.exception.ConfigLoadException;
import me.filoghost.chestcommands.config.framework.exception.ConfigSaveException;
import me.filoghost.chestcommands.legacy.Upgrade;
import me.filoghost.chestcommands.legacy.UpgradeException;
import java.io.IOException;
import java.nio.file.Path;
public class SettingsUpgrade extends Upgrade {
@ -27,23 +28,23 @@ public class SettingsUpgrade extends Upgrade {
private final ConfigLoader settingsConfigLoader;
private Config updatedConfig;
public SettingsUpgrade(ConfigLoader settingsConfigLoader) {
this.settingsConfigLoader = settingsConfigLoader;
public SettingsUpgrade(ConfigManager configManager) {
this.settingsConfigLoader = configManager.getConfigLoader("config.yml");
}
@Override
public Path getOriginalFile() {
return settingsConfigLoader.getPath();
return settingsConfigLoader.getConfigPath();
}
@Override
public Path getUpgradedFile() {
return settingsConfigLoader.getPath();
return settingsConfigLoader.getConfigPath();
}
@Override
protected void computeChanges() throws UpgradeException {
Config settingsConfig = loadConfig(settingsConfigLoader);
protected void computeChanges() throws ConfigLoadException {
Config settingsConfig = settingsConfigLoader.load();
removeNode(settingsConfig, "use-only-commands-without-args");
removeNode(settingsConfig, "use-console-colors");
@ -61,7 +62,7 @@ public class SettingsUpgrade extends Upgrade {
@Override
protected void saveChanges() throws IOException {
protected void saveChanges() throws ConfigSaveException {
settingsConfigLoader.save(updatedConfig);
}
}

View File

@ -1,19 +1,20 @@
package me.filoghost.chestcommands.parsing;
import me.filoghost.chestcommands.config.framework.Config;
import me.filoghost.chestcommands.parsing.icon.IconSettings;
public class ErrorFormat {
public static String invalidMenuSetting(String menuName, String invalidSetting, String errorMessage) {
return menuError(menuName, "has an invalid menu setting \"" + invalidSetting + "\": " + errorMessage);
public static String invalidMenuSetting(Config menuConfig, String invalidSetting, String errorMessage) {
return menuError(menuConfig, "has an invalid menu setting \"" + invalidSetting + "\": " + errorMessage);
}
public static String missingMenuSetting(String menuName, String missingSetting) {
return menuError(menuName, "is missing the menu setting \"" + missingSetting + "\"");
public static String missingMenuSetting(Config menuConfig, String missingSetting) {
return menuError(menuConfig, "is missing the menu setting \"" + missingSetting + "\"");
}
private static String menuError(String menuName, String errorMessage) {
return "The menu \"" + menuName + "\" " + errorMessage + ".";
private static String menuError(Config menuConfig, String errorMessage) {
return "The menu \"" + menuConfig.getSourceFileName() + "\" " + errorMessage + ".";
}
public static String invalidAttribute(IconSettings iconSettings, String attributeName, String errorMessage) {

View File

@ -14,8 +14,8 @@
*/
package me.filoghost.chestcommands.parsing.icon;
import me.filoghost.chestcommands.config.ConfigSection;
import me.filoghost.chestcommands.config.ConfigValueException;
import me.filoghost.chestcommands.config.framework.ConfigSection;
import me.filoghost.chestcommands.config.framework.exception.ConfigValueException;
import me.filoghost.chestcommands.icon.InternalConfigurableIcon;
import me.filoghost.chestcommands.parsing.ParseException;
import me.filoghost.chestcommands.parsing.icon.attributes.ActionsAttribute;

View File

@ -15,11 +15,11 @@
package me.filoghost.chestcommands.parsing.menu;
import me.filoghost.chestcommands.action.Action;
import me.filoghost.chestcommands.config.Config;
import me.filoghost.chestcommands.config.ConfigSection;
import me.filoghost.chestcommands.config.ConfigValueException;
import me.filoghost.chestcommands.menu.InternalIconMenu;
import me.filoghost.chestcommands.config.framework.Config;
import me.filoghost.chestcommands.config.framework.ConfigSection;
import me.filoghost.chestcommands.config.framework.exception.ConfigValueException;
import me.filoghost.chestcommands.icon.InternalConfigurableIcon;
import me.filoghost.chestcommands.menu.InternalIconMenu;
import me.filoghost.chestcommands.parsing.ActionParser;
import me.filoghost.chestcommands.parsing.ErrorFormat;
import me.filoghost.chestcommands.parsing.ItemStackParser;
@ -41,7 +41,7 @@ public class MenuParser {
MenuSettings menuSettings = loadMenuSettings(menuConfig, errorCollector);
List<IconSettings> iconSettingsList = loadIconSettingsList(menuConfig, errorCollector);
InternalIconMenu iconMenu = new InternalIconMenu(menuSettings.getTitle(), menuSettings.getRows(), menuConfig.getFileName());
InternalIconMenu iconMenu = new InternalIconMenu(menuSettings.getTitle(), menuSettings.getRows(), menuConfig.getSourceFileName());
for (IconSettings iconSettings : iconSettingsList) {
try {
@ -54,7 +54,7 @@ public class MenuParser {
iconMenu.setRefreshTicks(menuSettings.getRefreshTicks());
iconMenu.setOpenActions(menuSettings.getOpenActions());
return new LoadedMenu(iconMenu, menuConfig.getFileName(), menuSettings.getCommands(), menuSettings.getOpenTrigger());
return new LoadedMenu(iconMenu, menuConfig.getSourceFileName(), menuSettings.getCommands(), menuSettings.getOpenTrigger());
}
@ -105,7 +105,7 @@ public class MenuParser {
}
} catch (ConfigValueException e) {
title = ChatColor.DARK_RED + "No name set";
errorCollector.addError(ErrorFormat.missingMenuSetting(config.getFileName(), MenuSettingsNode.NAME));
errorCollector.addError(ErrorFormat.missingMenuSetting(config, MenuSettingsNode.NAME));
}
int rows;
@ -116,7 +116,7 @@ public class MenuParser {
}
} catch (ConfigValueException e) {
rows = 6; // Defaults to 6 rows
errorCollector.addError(ErrorFormat.missingMenuSetting(config.getFileName(), MenuSettingsNode.ROWS));
errorCollector.addError(ErrorFormat.missingMenuSetting(config, MenuSettingsNode.ROWS));
}
MenuSettings menuSettings = new MenuSettings(title, rows);
@ -157,7 +157,7 @@ public class MenuParser {
menuSettings.setOpenTrigger(openTrigger);
} catch (ParseException e) {
errorCollector.addError(ErrorFormat.invalidMenuSetting(config.getFileName(), MenuSettingsNode.OPEN_ITEM_MATERIAL, e.getMessage()));
errorCollector.addError(ErrorFormat.invalidMenuSetting(config, MenuSettingsNode.OPEN_ITEM_MATERIAL, e.getMessage()));
}
}
}
@ -183,7 +183,7 @@ public class MenuParser {
}
ConfigSection iconSection = config.getConfigSection(iconSectionName);
IconSettings iconSettings = new IconSettings(config.getFileName(), iconSectionName);
IconSettings iconSettings = new IconSettings(config.getSourceFileName(), iconSectionName);
iconSettings.loadFrom(iconSection, errorCollector);
iconSettingsList.add(iconSettings);
}

View File

@ -14,10 +14,10 @@
*/
package me.filoghost.chestcommands.util;
import java.util.Collection;
import org.bukkit.Material;
import java.util.Collection;
public final class Preconditions {
private Preconditions() {}