mirror of
https://github.com/songoda/SongodaCore.git
synced 2025-01-23 16:01:21 +01:00
Revert all the SongodaYamlConfig related commits
Revert "Ensures usage of UTF-8 in SongodaYamlConfig" This reverts commit339a4d6f6c
. Revert "Improve test coverage + stability of configuration.yaml/songoda classes" This reverts commitef6c37b80c
. Revert "Adds ConfigEntry#withDefaultValue for easier chaining" This reverts commit88e28689f7
. Revert "Code cleanup (rename e->ex in catch; better type for #withUpgradeStep)" This reverts commit7eff3c86ec
. Revert "Rename constant into upper case to match code conventions" This reverts commit4d194ed92b
. Revert "Fix typo in JavaDoc" This reverts commit0b2a253014
. Revert "Makes SongodaYamlConfig#cannotCreateBackupCopyExceptionPrefix static" This reverts commit8e91cc18eb
. Revert "Make unit tests in LocaleFileManagerTest deterministic" This reverts commit67a69e34e8
. Revert "Add unit test for SongodaYamlConfig persisting comments on key-upgrades" This reverts commitd710b2d2d5
. Revert "Improve temporary file deletion in YamlConfig and FileManager tests" This reverts commit02330b5ca7
. Revert "Adds hyphen before timestamp in file name, when creating backup YamlCfg" This reverts commitf8b3942de2
. Revert "Fix YamlConfiguration not dumping comments" This reverts commite7da328dc6
. Revert "Provisional first implementation of the new localization system" This reverts commitb168ad0738
. Revert "Fix error handling of SongodaYamlConfig#load(Reader)" This reverts commit163e4d9eaf
. Partially reverts "Adds some deprecation notices to configuration.editor classes" This partially reverts commiteea951ecc6
. Revert "Redo ConfigEntry abstraction" This reverts commit20b44327e0
. Revert "Migrate CustomizableGui from old Config to SongodaYamlConfig" This reverts commitd5ddde3e08
. Revert "Adds SongodaYamlConfig#getAsEntry(String) for convenience" This reverts commit20b7a353b8
. Revert "Add contract to `ConfigEntry#getString(String)` for non-null-argument" This reverts commit78b6039d39
. Revert "Adds getter to ConfigEntry for List<String>" This reverts commit3a09c19dbb
. Revert "Remove usage of Locale classes" This reverts commitda3c89450e
. Revert "Mark overwritten and empty config methods in SongodaPlugin as deprecated" This reverts commit73685b62dd
. Revert "Adjust log levels in SongodaYamlConfig" This reverts commit7ef00bb8f9
. Revert "Fix SongodaYamlConfig not creating parent directory when saving" This reverts commitb0f006aed0
. Revert "Fix SongodaYamlConfigTest leaving created backup files in tmp dir" This reverts commitc9a48387de
. Revert "Remove Config related methods in SongodaPlugin" This reverts commitfce5c5c6a1
. Revert "Introduce new SongodaYamlConfig and ConfigEntry classes" This reverts commiteb10b3f70a
. Revert "Fix YamlConfiguration dumping null values and empty tree nodes" This reverts commit02ab8d4bb2
. Revert "Fix `YamlConfiruration#getKeys("")` not returning root node keys" This reverts commit885cc9a87e
. Revert "Fix exception on loading empty file in YamlConfiguration" This reverts commit2683bc12c0
. Revert "Make YamlConfiguration insertion-sorted" This reverts commit2262652577
. Revert "Rename `IConfiguration#getOrDefault` to `#getOr`" This reverts commitf6e207cdda
. Revert "Adds Enum support to YamlConfiguration class (#41)" This reverts commit41bd5c633a
. Revert "Removes the default implementations for #save(File) and #load(File)" This reverts commit8f15df3601
. Revert "Replace Songoda's YAML Configuration wrapper with an own implementation" This reverts commit6d6fa7210a
.
This commit is contained in:
parent
748f10b77b
commit
c725ea69d6
@ -3,6 +3,7 @@ package com.songoda.core;
|
||||
import com.songoda.core.commands.CommandManager;
|
||||
import com.songoda.core.compatibility.ClientVersion;
|
||||
import com.songoda.core.compatibility.CompatibleMaterial;
|
||||
import com.songoda.core.core.LocaleModule;
|
||||
import com.songoda.core.core.PluginInfo;
|
||||
import com.songoda.core.core.PluginInfoModule;
|
||||
import com.songoda.core.core.SongodaCoreCommand;
|
||||
@ -233,7 +234,7 @@ public class SongodaCore {
|
||||
PluginInfo info = new PluginInfo(plugin, pluginID, icon, libraryVersion);
|
||||
|
||||
// don't forget to check for language pack updates ;)
|
||||
// info.addModule(new LocaleModule());
|
||||
info.addModule(new LocaleModule());
|
||||
registeredPlugins.add(info);
|
||||
tasks.add(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> update(info), 60L));
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
package com.songoda.core;
|
||||
|
||||
import com.songoda.core.configuration.songoda.SongodaYamlConfig;
|
||||
import com.songoda.core.configuration.Config;
|
||||
import com.songoda.core.database.DataManagerAbstract;
|
||||
import com.songoda.core.locale.Locale;
|
||||
import com.songoda.core.utils.Metrics;
|
||||
import com.songoda.core.utils.SongodaAuth;
|
||||
import de.tr7zw.changeme.nbtapi.utils.MinecraftVersion;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -21,7 +21,8 @@ import java.util.logging.Level;
|
||||
* Must not have two instances of Metrics enabled!
|
||||
*/
|
||||
public abstract class SongodaPlugin extends JavaPlugin {
|
||||
// protected Locale locale;
|
||||
protected Locale locale;
|
||||
protected Config config = new Config(this);
|
||||
protected long dataLoadDelay = 20L;
|
||||
|
||||
private boolean emergencyStop = false;
|
||||
@ -41,15 +42,37 @@ public abstract class SongodaPlugin extends JavaPlugin {
|
||||
public abstract void onDataLoad();
|
||||
|
||||
/**
|
||||
* All the configuration files belonging to the plugin.<br>
|
||||
* This might for example be used for the ingame config editor.<br>
|
||||
* <br>
|
||||
* Do not include *storage* files here or anything similar that does not intend external modification and access.<br>
|
||||
* <br>
|
||||
* Do not include language files if you are using the Core's localization system.
|
||||
* Called after reloadConfig() is called
|
||||
*/
|
||||
public abstract @NotNull List<SongodaYamlConfig> getConfigs();
|
||||
|
||||
/**
|
||||
* Any other plugin configuration files used by the plugin.
|
||||
*
|
||||
* @return a list of Configs that are used in addition to the main config.
|
||||
*/
|
||||
public abstract List<Config> getExtraConfig();
|
||||
|
||||
@Override
|
||||
public FileConfiguration getConfig() {
|
||||
return config.getFileConfig();
|
||||
}
|
||||
|
||||
public Config getCoreConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadConfig() {
|
||||
config.load();
|
||||
onConfigReload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveConfig() {
|
||||
config.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onLoad() {
|
||||
try {
|
||||
@ -103,7 +126,7 @@ public abstract class SongodaPlugin extends JavaPlugin {
|
||||
ChatColor.GREEN, "Enabling", ChatColor.GRAY));
|
||||
|
||||
try {
|
||||
// this.locale = Locale.loadDefaultLocale(this, "en_US");
|
||||
this.locale = Locale.loadDefaultLocale(this, "en_US");
|
||||
|
||||
// plugin setup
|
||||
onPluginEnable();
|
||||
@ -157,32 +180,32 @@ public abstract class SongodaPlugin extends JavaPlugin {
|
||||
console.sendMessage(" "); // blank line to separate chatter
|
||||
}
|
||||
|
||||
// public Locale getLocale() {
|
||||
// return this.locale;
|
||||
// }
|
||||
public Locale getLocale() {
|
||||
return this.locale;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Set the plugin's locale to a specific language
|
||||
// *
|
||||
// * @param localeName locale to use, eg "en_US"
|
||||
// * @param reload optionally reload the loaded locale if the locale didn't
|
||||
// * change
|
||||
// *
|
||||
// * @return true if the locale exists and was loaded successfully
|
||||
// */
|
||||
// public boolean setLocale(String localeName, boolean reload) {
|
||||
// if (this.locale != null && this.locale.getName().equals(localeName)) {
|
||||
// return !reload || this.locale.reloadMessages();
|
||||
// }
|
||||
//
|
||||
// Locale l = Locale.loadLocale(this, localeName);
|
||||
// if (l != null) {
|
||||
// this.locale = l;
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
/**
|
||||
* Set the plugin's locale to a specific language
|
||||
*
|
||||
* @param localeName locale to use, eg "en_US"
|
||||
* @param reload optionally reload the loaded locale if the locale didn't
|
||||
* change
|
||||
*
|
||||
* @return true if the locale exists and was loaded successfully
|
||||
*/
|
||||
public boolean setLocale(String localeName, boolean reload) {
|
||||
if (this.locale != null && this.locale.getName().equals(localeName)) {
|
||||
return !reload || this.locale.reloadMessages();
|
||||
}
|
||||
|
||||
Locale l = Locale.loadLocale(this, localeName);
|
||||
if (l != null) {
|
||||
this.locale = l;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void shutdownDataManager(DataManagerAbstract dataManager) {
|
||||
// 3 minutes is overkill, but we just want to make sure
|
||||
@ -243,29 +266,4 @@ public abstract class SongodaPlugin extends JavaPlugin {
|
||||
|
||||
emergencyStop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link SongodaYamlConfig} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public @NotNull FileConfiguration getConfig() {
|
||||
return super.getConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link SongodaYamlConfig} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void reloadConfig() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link SongodaYamlConfig} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public void saveConfig() {
|
||||
}
|
||||
}
|
||||
|
114
Core/src/main/java/com/songoda/core/configuration/Comment.java
Normal file
114
Core/src/main/java/com/songoda/core/configuration/Comment.java
Normal file
@ -0,0 +1,114 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import com.songoda.core.configuration.ConfigFormattingRules.CommentStyle;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A comment for a configuration key
|
||||
*/
|
||||
public class Comment {
|
||||
final List<String> lines = new ArrayList<>();
|
||||
CommentStyle commentStyle = null;
|
||||
|
||||
public Comment() {
|
||||
}
|
||||
|
||||
public Comment(String... lines) {
|
||||
this(null, Arrays.asList(lines));
|
||||
}
|
||||
|
||||
public Comment(List<String> lines) {
|
||||
this(null, lines);
|
||||
}
|
||||
|
||||
public Comment(CommentStyle commentStyle, String... lines) {
|
||||
this(commentStyle, Arrays.asList(lines));
|
||||
}
|
||||
|
||||
public Comment(CommentStyle commentStyle, List<String> lines) {
|
||||
this.commentStyle = commentStyle;
|
||||
|
||||
if (lines != null) {
|
||||
lines.forEach(s -> this.lines.addAll(Arrays.asList(s.split("\n"))));
|
||||
}
|
||||
}
|
||||
|
||||
public CommentStyle getCommentStyle() {
|
||||
return commentStyle;
|
||||
}
|
||||
|
||||
public void setCommentStyle(CommentStyle commentStyle) {
|
||||
this.commentStyle = commentStyle;
|
||||
}
|
||||
|
||||
public List<String> getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lines.isEmpty() ? "" : String.join("\n", lines);
|
||||
}
|
||||
|
||||
public static Comment loadComment(List<String> lines) {
|
||||
CommentStyle style = ConfigFormattingRules.parseStyle(lines);
|
||||
|
||||
int linePad = (style.drawBorder ? 1 : 0) + (style.drawSpace ? 1 : 0);
|
||||
int prefix = style.commentPrefix.length();
|
||||
int suffix = style.commentSuffix.length();
|
||||
|
||||
return new Comment(style, lines.subList(linePad, lines.size() - linePad).stream().map(s -> s.substring(prefix, s.length() - suffix).trim()).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public void writeComment(Writer output, int offset, CommentStyle defaultStyle) throws IOException {
|
||||
CommentStyle style = commentStyle != null ? commentStyle : defaultStyle;
|
||||
int minSpacing = 0, borderSpacing = 0;
|
||||
|
||||
// first draw the top of the comment
|
||||
if (style.drawBorder) {
|
||||
// grab the longest line in the list of lines
|
||||
minSpacing = lines.stream().max(Comparator.comparingInt(String::length)).orElse("").length();
|
||||
borderSpacing = minSpacing + style.commentPrefix.length() + style.commentSuffix.length();
|
||||
|
||||
// draw the first line
|
||||
output.write((new String(new char[offset])).replace('\0', ' ') + (new String(new char[borderSpacing + 2])).replace('\0', '#') + "\n");
|
||||
|
||||
if (style.drawSpace) {
|
||||
output.write((new String(new char[offset])).replace('\0', ' ')
|
||||
+ "#" + style.spacePrefixTop
|
||||
+ (new String(new char[borderSpacing - style.spacePrefixTop.length() - style.spaceSuffixTop.length()])).replace('\0', style.spaceCharTop)
|
||||
+ style.spaceSuffixTop + "#\n");
|
||||
}
|
||||
} else if (style.drawSpace) {
|
||||
output.write((new String(new char[offset])).replace('\0', ' ') + "#\n");
|
||||
}
|
||||
|
||||
// then the actual comment lines
|
||||
for (String line : lines) {
|
||||
// todo? should we auto-wrap comment lines that are longer than 80 characters?
|
||||
output.write((new String(new char[offset])).replace('\0', ' ') + "#" + style.commentPrefix
|
||||
+ (minSpacing == 0 ? line : line + (new String(new char[minSpacing - line.length()])).replace('\0', ' ')) + style.commentSuffix + (style.drawBorder ? "#\n" : "\n"));
|
||||
}
|
||||
|
||||
// now draw the bottom of the comment border
|
||||
if (style.drawBorder) {
|
||||
if (style.drawSpace) {
|
||||
output.write((new String(new char[offset])).replace('\0', ' ')
|
||||
+ "#" + style.spacePrefixBottom
|
||||
+ (new String(new char[borderSpacing - style.spacePrefixBottom.length() - style.spaceSuffixBottom.length()])).replace('\0', style.spaceCharBottom)
|
||||
+ style.spaceSuffixBottom + "#\n");
|
||||
}
|
||||
|
||||
output.write((new String(new char[offset])).replace('\0', ' ') + (new String(new char[borderSpacing + 2])).replace('\0', '#') + "\n");
|
||||
} else if (style.drawSpace) {
|
||||
output.write((new String(new char[offset])).replace('\0', ' ') + "#\n");
|
||||
}
|
||||
}
|
||||
}
|
793
Core/src/main/java/com/songoda/core/configuration/Config.java
Normal file
793
Core/src/main/java/com/songoda/core/configuration/Config.java
Normal file
@ -0,0 +1,793 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import com.songoda.core.utils.TextUtils;
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConstructor;
|
||||
import org.bukkit.configuration.file.YamlRepresenter;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.error.YAMLException;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Configuration settings for a plugin
|
||||
*/
|
||||
public class Config extends ConfigSection {
|
||||
/*
|
||||
Serialization notes:
|
||||
// implements ConfigurationSerializable:
|
||||
//public Map<String, Object> serialize();
|
||||
|
||||
// Class must contain one of:
|
||||
// public static Object deserialize(@NotNull Map<String, ?> args);
|
||||
// public static valueOf(Map<String, ?> args);
|
||||
// public new (Map<String, ?> args)
|
||||
*/
|
||||
protected static final String BLANK_CONFIG = "{}\n";
|
||||
|
||||
protected File file;
|
||||
protected final ConfigFileConfigurationAdapter config = new ConfigFileConfigurationAdapter(this);
|
||||
protected Comment headerComment = null;
|
||||
protected Comment footerComment = null;
|
||||
final String dirName, fileName;
|
||||
final Plugin plugin;
|
||||
final DumperOptions yamlOptions = new DumperOptions();
|
||||
final Representer yamlRepresenter = new YamlRepresenter();
|
||||
final Yaml yaml = new Yaml(new YamlConstructor(), yamlRepresenter, yamlOptions);
|
||||
Charset defaultCharset = StandardCharsets.UTF_8;
|
||||
SaveTask saveTask;
|
||||
Timer autosaveTimer;
|
||||
|
||||
////////////// Config settings ////////////////
|
||||
/**
|
||||
* save file whenever a change is made
|
||||
*/
|
||||
boolean autosave = false;
|
||||
|
||||
/**
|
||||
* time in seconds to start a save after a change is made
|
||||
*/
|
||||
int autosaveInterval = 60;
|
||||
|
||||
/**
|
||||
* remove nodes not defined in defaults
|
||||
*/
|
||||
boolean autoremove = false;
|
||||
|
||||
/**
|
||||
* load comments when loading the file
|
||||
*/
|
||||
boolean loadComments = true;
|
||||
|
||||
/**
|
||||
* Default comment applied to config nodes
|
||||
*/
|
||||
ConfigFormattingRules.CommentStyle defaultNodeCommentFormat = ConfigFormattingRules.CommentStyle.SIMPLE;
|
||||
|
||||
/**
|
||||
* Default comment applied to section nodes
|
||||
*/
|
||||
ConfigFormattingRules.CommentStyle defaultSectionCommentFormat = ConfigFormattingRules.CommentStyle.SPACED;
|
||||
|
||||
/**
|
||||
* Extra lines to put between root nodes
|
||||
*/
|
||||
int rootNodeSpacing = 1;
|
||||
|
||||
/**
|
||||
* Extra lines to put in front of comments. <br>
|
||||
* This is separate from rootNodeSpacing, if applicable.
|
||||
*/
|
||||
int commentSpacing = 1;
|
||||
|
||||
public Config() {
|
||||
this.plugin = null;
|
||||
this.file = null;
|
||||
|
||||
dirName = null;
|
||||
fileName = null;
|
||||
}
|
||||
|
||||
public Config(@NotNull File file) {
|
||||
this.plugin = null;
|
||||
this.file = file.getAbsoluteFile();
|
||||
|
||||
dirName = null;
|
||||
fileName = null;
|
||||
}
|
||||
|
||||
public Config(@NotNull Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
|
||||
dirName = null;
|
||||
fileName = null;
|
||||
}
|
||||
|
||||
public Config(@NotNull Plugin plugin, @NotNull String file) {
|
||||
this.plugin = plugin;
|
||||
|
||||
dirName = null;
|
||||
fileName = file;
|
||||
}
|
||||
|
||||
public Config(@NotNull Plugin plugin, @Nullable String directory, @NotNull String file) {
|
||||
this.plugin = plugin;
|
||||
|
||||
dirName = directory;
|
||||
fileName = file;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigFileConfigurationAdapter getFileConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public File getFile() {
|
||||
if (file == null) {
|
||||
if (dirName != null) {
|
||||
this.file = new File(plugin.getDataFolder() + dirName, fileName != null ? fileName : "config.yml");
|
||||
} else {
|
||||
this.file = new File(plugin.getDataFolder(), fileName != null ? fileName : "config.yml");
|
||||
}
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
public Charset getDefaultCharset() {
|
||||
return defaultCharset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Charset that will be used to save this config
|
||||
*
|
||||
* @param defaultCharset Charset to use
|
||||
*
|
||||
* @return this class
|
||||
*/
|
||||
public Config setDefaultCharset(Charset defaultCharset) {
|
||||
this.defaultCharset = defaultCharset;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default charset to use UTF-16
|
||||
*
|
||||
* @return this class
|
||||
*/
|
||||
public Config setUseUTF16() {
|
||||
this.defaultCharset = StandardCharsets.UTF_16;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean getLoadComments() {
|
||||
return loadComments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should comments from the config file be loaded when loading?
|
||||
*
|
||||
* @param loadComments set to false if you do not want to preserve node comments
|
||||
*/
|
||||
public void setLoadComments(boolean loadComments) {
|
||||
this.loadComments = loadComments;
|
||||
}
|
||||
|
||||
public boolean getAutosave() {
|
||||
return autosave;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the configuration automatically save whenever it's been changed? <br>
|
||||
* All saves are done asynchronously, so this should not impact server performance.
|
||||
*
|
||||
* @param autosave set to true if autosaving is enabled.
|
||||
*
|
||||
* @return this class
|
||||
*/
|
||||
@NotNull
|
||||
public Config setAutosave(boolean autosave) {
|
||||
this.autosave = autosave;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getAutosaveInterval() {
|
||||
return autosaveInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* If autosave is enabled, this is the delay between a change and when the save is started. <br>
|
||||
* If the configuration is changed within this period, the timer is not reset.
|
||||
*
|
||||
* @param autosaveInterval time in seconds
|
||||
*
|
||||
* @return this class
|
||||
*/
|
||||
@NotNull
|
||||
public Config setAutosaveInterval(int autosaveInterval) {
|
||||
this.autosaveInterval = autosaveInterval;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean getAutoremove() {
|
||||
return autoremove;
|
||||
}
|
||||
|
||||
/**
|
||||
* This setting is used to prevent users to from adding extraneous settings
|
||||
* to the config and to remove deprecated settings. <br>
|
||||
* If this is enabled, the config will delete any nodes that are not defined
|
||||
* as a default setting.
|
||||
*
|
||||
* @param autoremove Remove settings that don't exist as defaults
|
||||
*
|
||||
* @return this class
|
||||
*/
|
||||
@NotNull
|
||||
public Config setAutoremove(boolean autoremove) {
|
||||
this.autoremove = autoremove;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default comment applied to config nodes
|
||||
*/
|
||||
@Nullable
|
||||
public ConfigFormattingRules.CommentStyle getDefaultNodeCommentFormat() {
|
||||
return defaultNodeCommentFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default comment applied to config nodes
|
||||
*
|
||||
* @return this config
|
||||
*/
|
||||
@NotNull
|
||||
public Config setDefaultNodeCommentFormat(@Nullable ConfigFormattingRules.CommentStyle defaultNodeCommentFormat) {
|
||||
this.defaultNodeCommentFormat = defaultNodeCommentFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default comment applied to section nodes
|
||||
*/
|
||||
@Nullable
|
||||
public ConfigFormattingRules.CommentStyle getDefaultSectionCommentFormat() {
|
||||
return defaultSectionCommentFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default comment applied to section nodes
|
||||
*
|
||||
* @return this config
|
||||
*/
|
||||
@NotNull
|
||||
public Config setDefaultSectionCommentFormat(@Nullable ConfigFormattingRules.CommentStyle defaultSectionCommentFormat) {
|
||||
this.defaultSectionCommentFormat = defaultSectionCommentFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra lines to put between root nodes
|
||||
*/
|
||||
public int getRootNodeSpacing() {
|
||||
return rootNodeSpacing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra lines to put between root nodes
|
||||
*
|
||||
* @return this config
|
||||
*/
|
||||
@NotNull
|
||||
public Config setRootNodeSpacing(int rootNodeSpacing) {
|
||||
this.rootNodeSpacing = rootNodeSpacing;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra lines to put in front of comments. <br>
|
||||
* This is separate from rootNodeSpacing, if applicable.
|
||||
*/
|
||||
public int getCommentSpacing() {
|
||||
return commentSpacing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra lines to put in front of comments. <br>
|
||||
* This is separate from rootNodeSpacing, if applicable.
|
||||
*
|
||||
* @return this config
|
||||
*/
|
||||
@NotNull
|
||||
public Config setCommentSpacing(int commentSpacing) {
|
||||
this.commentSpacing = commentSpacing;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Config setHeader(@NotNull String... description) {
|
||||
if (description.length == 0) {
|
||||
headerComment = null;
|
||||
} else {
|
||||
headerComment = new Comment(description);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Config setHeader(@Nullable ConfigFormattingRules.CommentStyle commentStyle, @NotNull String... description) {
|
||||
if (description.length == 0) {
|
||||
headerComment = null;
|
||||
} else {
|
||||
headerComment = new Comment(commentStyle, description);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Config setHeader(@Nullable List<String> description) {
|
||||
if (description == null || description.isEmpty()) {
|
||||
headerComment = null;
|
||||
} else {
|
||||
headerComment = new Comment(description);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Config setHeader(@Nullable ConfigFormattingRules.CommentStyle commentStyle, @Nullable List<String> description) {
|
||||
if (description == null || description.isEmpty()) {
|
||||
headerComment = null;
|
||||
} else {
|
||||
headerComment = new Comment(commentStyle, description);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<String> getHeader() {
|
||||
if (headerComment != null) {
|
||||
return headerComment.getLines();
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public Config clearConfig(boolean clearDefaults) {
|
||||
root.values.clear();
|
||||
root.configComments.clear();
|
||||
|
||||
if (clearDefaults) {
|
||||
root.defaultComments.clear();
|
||||
root.defaults.clear();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Config clearDefaults() {
|
||||
root.defaultComments.clear();
|
||||
root.defaults.clear();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean load() {
|
||||
return load(getFile());
|
||||
}
|
||||
|
||||
public boolean load(@NotNull File file) {
|
||||
Validate.notNull(file, "File cannot be null");
|
||||
if (file.exists()) {
|
||||
try (BufferedInputStream stream = new BufferedInputStream(new FileInputStream(file))) {
|
||||
Charset charset = TextUtils.detectCharset(stream, StandardCharsets.UTF_8);
|
||||
|
||||
// upgrade charset if file was saved in a more complex format
|
||||
if (charset == StandardCharsets.UTF_16BE || charset == StandardCharsets.UTF_16LE) {
|
||||
defaultCharset = StandardCharsets.UTF_16;
|
||||
}
|
||||
|
||||
this.load(new InputStreamReader(stream, charset));
|
||||
|
||||
return true;
|
||||
} catch (IOException | InvalidConfigurationException ex) {
|
||||
(plugin != null ? plugin.getLogger() : Bukkit.getLogger()).log(Level.SEVERE, "Failed to load config file: " + file.getName(), ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void load(@NotNull Reader reader) throws IOException, InvalidConfigurationException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
try (BufferedReader input = reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader)) {
|
||||
String line;
|
||||
boolean firstLine = true;
|
||||
while ((line = input.readLine()) != null) {
|
||||
if (firstLine) {
|
||||
line = line.replaceAll("[\uFEFF\uFFFE\u200B]", ""); // clear BOM markers
|
||||
firstLine = false;
|
||||
}
|
||||
builder.append(line).append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
this.loadFromString(builder.toString());
|
||||
}
|
||||
|
||||
public void loadFromString(@NotNull String contents) throws InvalidConfigurationException {
|
||||
Map<?, ?> input;
|
||||
|
||||
try {
|
||||
input = this.yaml.load(contents);
|
||||
} catch (YAMLException e2) {
|
||||
throw new InvalidConfigurationException(e2);
|
||||
} catch (ClassCastException e3) {
|
||||
throw new InvalidConfigurationException("Top level is not a Map.");
|
||||
}
|
||||
|
||||
if (input != null) {
|
||||
if (loadComments) {
|
||||
this.parseComments(contents, input);
|
||||
}
|
||||
|
||||
this.convertMapsToSections(input, this);
|
||||
}
|
||||
}
|
||||
|
||||
protected void convertMapsToSections(@NotNull Map<?, ?> input, @NotNull ConfigSection section) {
|
||||
// TODO: make this non-recursive
|
||||
for (Map.Entry<?, ?> entry : input.entrySet()) {
|
||||
String key = entry.getKey().toString();
|
||||
Object value = entry.getValue();
|
||||
|
||||
if (value instanceof Map) {
|
||||
this.convertMapsToSections((Map<?, ?>) value, section.createSection(key));
|
||||
continue;
|
||||
}
|
||||
|
||||
section.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
protected void parseComments(@NotNull String contents, @NotNull Map<?, ?> input) {
|
||||
// if starts with a comment, load all nonbreaking comments as a header
|
||||
// then load all comments and assign to the next valid node loaded
|
||||
// (Only load comments that are on their own line)
|
||||
|
||||
BufferedReader in = new BufferedReader(new StringReader(contents));
|
||||
String line;
|
||||
boolean insideScalar = false;
|
||||
boolean firstNode = true;
|
||||
int index = 0;
|
||||
LinkedList<String> currentPath = new LinkedList<>();
|
||||
ArrayList<String> commentBlock = new ArrayList<>();
|
||||
|
||||
try {
|
||||
while ((line = in.readLine()) != null) {
|
||||
if (line.isEmpty()) {
|
||||
if (firstNode && !commentBlock.isEmpty()) {
|
||||
// header comment
|
||||
firstNode = false;
|
||||
headerComment = Comment.loadComment(commentBlock);
|
||||
commentBlock.clear();
|
||||
}
|
||||
continue;
|
||||
} else if (line.trim().startsWith("#")) {
|
||||
// only load full-line comments
|
||||
commentBlock.add(line.trim());
|
||||
continue;
|
||||
}
|
||||
|
||||
// check to see if this is a line that we can process
|
||||
int lineOffset = getOffset(line);
|
||||
insideScalar &= lineOffset <= index;
|
||||
Matcher m;
|
||||
if (!insideScalar && (m = yamlNode.matcher(line)).find()) {
|
||||
// we found a config node! ^.^
|
||||
// check to see what the full path is
|
||||
int depth = (m.group(1).length() / indentation);
|
||||
while (depth < currentPath.size()) {
|
||||
currentPath.removeLast();
|
||||
}
|
||||
currentPath.add(m.group(2));
|
||||
|
||||
// do we have a comment for this node?
|
||||
if (!commentBlock.isEmpty()) {
|
||||
String path = currentPath.stream().collect(Collectors.joining(String.valueOf(pathChar)));
|
||||
Comment comment = Comment.loadComment(commentBlock);
|
||||
commentBlock.clear();
|
||||
setComment(path, comment);
|
||||
}
|
||||
|
||||
firstNode = false; // we're no longer on the first node
|
||||
|
||||
// ignore scalars
|
||||
index = lineOffset;
|
||||
if (m.group(3).trim().equals("|") || m.group(3).trim().equals(">")) {
|
||||
insideScalar = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!commentBlock.isEmpty()) {
|
||||
footerComment = Comment.loadComment(commentBlock);
|
||||
commentBlock.clear();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(Config.class.getName()).log(Level.SEVERE, "Error parsing config comment", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteNonDefaultSettings() {
|
||||
// Delete old config values (thread-safe)
|
||||
List<String> defaultKeys = Arrays.asList(defaults.keySet().toArray(new String[0]));
|
||||
|
||||
for (String key : values.keySet().toArray(new String[0])) {
|
||||
if (!defaultKeys.contains(key)) {
|
||||
values.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onChange() {
|
||||
if (autosave) {
|
||||
delaySave();
|
||||
}
|
||||
}
|
||||
|
||||
public void delaySave() {
|
||||
// save async even if no plugin or if plugin disabled
|
||||
if (saveTask == null && (changed || hasNewDefaults())) {
|
||||
autosaveTimer = new Timer((plugin != null ? plugin.getName() + "-ConfigSave-" : "ConfigSave-") + getFile().getName());
|
||||
autosaveTimer.schedule(saveTask = new SaveTask(), autosaveInterval * 1000L);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean saveChanges() {
|
||||
boolean saved = true;
|
||||
|
||||
if (changed || hasNewDefaults()) {
|
||||
saved = save();
|
||||
}
|
||||
|
||||
if (saveTask != null) {
|
||||
//Close Threads
|
||||
saveTask.cancel();
|
||||
autosaveTimer.cancel();
|
||||
saveTask = null;
|
||||
autosaveTimer = null;
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
boolean hasNewDefaults() {
|
||||
if (file != null && !file.exists()) return true;
|
||||
|
||||
for (String def : defaults.keySet()) {
|
||||
if (!values.containsKey(def)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean save() {
|
||||
if (saveTask != null) {
|
||||
//Close Threads
|
||||
saveTask.cancel();
|
||||
autosaveTimer.cancel();
|
||||
saveTask = null;
|
||||
autosaveTimer = null;
|
||||
}
|
||||
|
||||
return save(getFile());
|
||||
}
|
||||
|
||||
public boolean save(@NotNull String file) {
|
||||
Validate.notNull(file, "File cannot be null");
|
||||
return this.save(new File(file));
|
||||
}
|
||||
|
||||
public boolean save(@NotNull File file) {
|
||||
Validate.notNull(file, "File cannot be null");
|
||||
|
||||
if (file.getParentFile() != null && !file.getParentFile().exists()) {
|
||||
file.getParentFile().mkdirs();
|
||||
}
|
||||
|
||||
String data = this.saveToString();
|
||||
try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), defaultCharset)) {
|
||||
writer.write(data);
|
||||
} catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String saveToString() {
|
||||
try {
|
||||
if (autoremove) {
|
||||
deleteNonDefaultSettings();
|
||||
}
|
||||
|
||||
yamlOptions.setIndent(indentation);
|
||||
yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
yamlOptions.setSplitLines(false);
|
||||
yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
|
||||
StringWriter str = new StringWriter();
|
||||
|
||||
if (headerComment != null) {
|
||||
headerComment.writeComment(str, 0, ConfigFormattingRules.CommentStyle.BLOCKED);
|
||||
str.write("\n"); // add one space after the header
|
||||
}
|
||||
|
||||
String dump = yaml.dump(this.getValues(false));
|
||||
if (!dump.equals(BLANK_CONFIG)) {
|
||||
writeComments(dump, str);
|
||||
}
|
||||
|
||||
if (footerComment != null) {
|
||||
str.write("\n");
|
||||
footerComment.writeComment(str, 0, ConfigFormattingRules.CommentStyle.BLOCKED);
|
||||
}
|
||||
|
||||
return str.toString();
|
||||
} catch (Throwable ex) {
|
||||
Logger.getLogger(Config.class.getName()).log(Level.SEVERE, "Error saving config", ex);
|
||||
delaySave();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
protected final Pattern yamlNode = Pattern.compile("^( *)([^:{}\\[\\],&*#?|\\-<>=!%@`]+):(.*)$");
|
||||
|
||||
protected void writeComments(String data, Writer out) throws IOException {
|
||||
// line-by-line apply line spacing formatting and comments per-node
|
||||
BufferedReader in = new BufferedReader(new StringReader(data));
|
||||
|
||||
String line;
|
||||
boolean insideScalar = false;
|
||||
boolean firstNode = true;
|
||||
int index = 0;
|
||||
|
||||
LinkedList<String> currentPath = new LinkedList<>();
|
||||
while ((line = in.readLine()) != null) {
|
||||
// ignore comments and empty lines (there shouldn't be any, but just in case)
|
||||
if (line.trim().startsWith("#") || line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check to see if this is a line that we can process
|
||||
int lineOffset = getOffset(line);
|
||||
insideScalar &= lineOffset <= index;
|
||||
Matcher m;
|
||||
if (!insideScalar && (m = yamlNode.matcher(line)).find()) {
|
||||
// we found a config node! ^.^
|
||||
// check to see what the full path is
|
||||
int depth = (m.group(1).length() / indentation);
|
||||
while (depth < currentPath.size()) {
|
||||
currentPath.removeLast();
|
||||
}
|
||||
|
||||
currentPath.add(m.group(2));
|
||||
String path = currentPath.stream().collect(Collectors.joining(String.valueOf(pathChar)));
|
||||
|
||||
// if this is a root-level node, apply extra spacing if we aren't the first node
|
||||
if (!firstNode && depth == 0 && rootNodeSpacing > 0) {
|
||||
out.write((new String(new char[rootNodeSpacing])).replace("\0", "\n")); // yes it's silly, but it works :>
|
||||
}
|
||||
firstNode = false; // we're no longer on the first node
|
||||
|
||||
// insert the relavant comment
|
||||
Comment comment = getComment(path);
|
||||
if (comment != null) {
|
||||
// add spacing between previous nodes and comments
|
||||
if (depth != 0) {
|
||||
out.write((new String(new char[commentSpacing])).replace("\0", "\n"));
|
||||
}
|
||||
|
||||
// formatting style for this node
|
||||
ConfigFormattingRules.CommentStyle style = comment.getCommentStyle();
|
||||
if (style == null) {
|
||||
// check to see what type of node this is
|
||||
if (!m.group(3).trim().isEmpty()) {
|
||||
// setting node
|
||||
style = defaultNodeCommentFormat;
|
||||
} else {
|
||||
// probably a section? (need to peek ahead to check if this is a list)
|
||||
in.mark(1000);
|
||||
String nextLine = in.readLine().trim();
|
||||
in.reset();
|
||||
if (nextLine.startsWith("-")) {
|
||||
// not a section :P
|
||||
style = defaultNodeCommentFormat;
|
||||
} else {
|
||||
style = defaultSectionCommentFormat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write it down!
|
||||
comment.writeComment(out, lineOffset, style);
|
||||
}
|
||||
|
||||
// ignore scalars
|
||||
index = lineOffset;
|
||||
if (m.group(3).trim().equals("|") || m.group(3).trim().equals(">")) {
|
||||
insideScalar = true;
|
||||
}
|
||||
}
|
||||
|
||||
out.write(line);
|
||||
out.write("\n");
|
||||
}
|
||||
}
|
||||
|
||||
protected static int getOffset(String s) {
|
||||
char[] chars = s.toCharArray();
|
||||
for (int i = 0; i < chars.length; ++i) {
|
||||
if (chars[i] != ' ') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
class SaveTask extends TimerTask {
|
||||
@Override
|
||||
public void run() {
|
||||
saveChanges();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,189 +0,0 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import com.songoda.core.compatibility.CompatibleMaterial;
|
||||
import com.songoda.core.utils.Pair;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface ConfigEntry {
|
||||
@NotNull String getKey();
|
||||
|
||||
@NotNull IConfiguration getConfig();
|
||||
|
||||
default boolean has() {
|
||||
return getConfig().has(getKey());
|
||||
}
|
||||
|
||||
void set(@Nullable Object value);
|
||||
|
||||
default @Nullable Object get() {
|
||||
return getConfig().get(getKey());
|
||||
}
|
||||
|
||||
default @Nullable Object getOr(@Nullable Object fallbackValue) {
|
||||
return getConfig().getOr(getKey(), fallbackValue);
|
||||
}
|
||||
|
||||
@Nullable Object getDefaultValue();
|
||||
|
||||
void setDefaultValue(@Nullable Object defaultValue);
|
||||
|
||||
@Contract("_ -> this")
|
||||
ConfigEntry withDefaultValue(@Nullable Object defaultValue);
|
||||
|
||||
/**
|
||||
* @see #withComment(Supplier)
|
||||
*/
|
||||
@Contract("_ -> this")
|
||||
default ConfigEntry withComment(String comment) {
|
||||
return this.withComment(() -> comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see NodeCommentable#setNodeComment(String, Supplier)
|
||||
*/
|
||||
@Contract("_ -> this")
|
||||
ConfigEntry withComment(Supplier<String> comment);
|
||||
|
||||
/**
|
||||
* @return <code><configVersion, Pair<keyInGivenVersion, valueConverter>></code>
|
||||
*/
|
||||
@Nullable Map<Integer, Pair<@Nullable String, @Nullable Function<@Nullable Object, @Nullable Object>>> getUpgradeSteps();
|
||||
|
||||
/**
|
||||
* @see #withUpgradeStep(int, String, Function)
|
||||
*/
|
||||
@Contract("_, _ -> this")
|
||||
default ConfigEntry withUpgradeStep(int version, @NotNull String keyInGivenVersion) {
|
||||
return withUpgradeStep(version, keyInGivenVersion, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param version The version to upgrade from (e.g. 1 for the upgrade from 1 to 2)
|
||||
* @param keyInGivenVersion The old key in the given version or null if it didn't change
|
||||
* @param valueConverter A function that converts the old version's value to a new one, or null if it didn't change
|
||||
*/
|
||||
@Contract("_, null, null -> fail; _, _, _ -> this")
|
||||
ConfigEntry withUpgradeStep(int version, @Nullable String keyInGivenVersion, @Nullable Function<@Nullable Object, @Nullable Object> valueConverter);
|
||||
|
||||
default @Nullable String getString() {
|
||||
return getStringOr(null);
|
||||
}
|
||||
|
||||
@Contract("!null -> !null")
|
||||
default @Nullable String getStringOr(String fallbackValue) {
|
||||
Object value = get();
|
||||
|
||||
return value == null ? fallbackValue : value.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getIntOr(int)
|
||||
*/
|
||||
default int getInt() {
|
||||
return getIntOr(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the values parsed as an integer.<br>
|
||||
* If it is a floating point number, it will be rounded down.
|
||||
*
|
||||
* @see Double#valueOf(String)
|
||||
*/
|
||||
default int getIntOr(int fallbackValue) {
|
||||
String value = getString();
|
||||
|
||||
if (value == null) {
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
return Double.valueOf(value).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getDoubleOr(double)
|
||||
*/
|
||||
default double getDouble() {
|
||||
return getDoubleOr(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the values parsed as a double.
|
||||
*
|
||||
* @see Double#parseDouble(String)
|
||||
*/
|
||||
default double getDoubleOr(double fallbackValue) {
|
||||
String value = getString();
|
||||
|
||||
if (value == null) {
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
return Double.parseDouble(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getBooleanOr(boolean)
|
||||
*/
|
||||
default boolean getBoolean() {
|
||||
return getBooleanOr(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the values parsed as a boolean.
|
||||
*
|
||||
* @see Boolean#parseBoolean(String)
|
||||
*/
|
||||
default boolean getBooleanOr(boolean fallbackValue) {
|
||||
String value = getString();
|
||||
|
||||
if (value == null) {
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
default @Nullable List<String> getStringList() {
|
||||
return getStringListOr(null);
|
||||
}
|
||||
|
||||
@Contract("!null -> !null")
|
||||
default @Nullable List<String> getStringListOr(List<String> fallbackValue) {
|
||||
Object value = get();
|
||||
|
||||
if (value instanceof List) {
|
||||
//noinspection unchecked
|
||||
return (List<String>) value;
|
||||
}
|
||||
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #getMaterialOr(CompatibleMaterial)
|
||||
*/
|
||||
default CompatibleMaterial getMaterial() {
|
||||
return getMaterialOr(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CompatibleMaterial#getMaterial(String)
|
||||
*/
|
||||
@Contract("!null -> !null")
|
||||
default @Nullable CompatibleMaterial getMaterialOr(@Nullable CompatibleMaterial defaultValue) {
|
||||
String value = getString();
|
||||
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return CompatibleMaterial.getMaterial(value);
|
||||
}
|
||||
}
|
@ -0,0 +1,272 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import com.songoda.core.compatibility.CompatibleMaterial;
|
||||
import org.bukkit.configuration.Configuration;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class ConfigFileConfigurationAdapter extends FileConfiguration {
|
||||
final Config config;
|
||||
|
||||
public ConfigFileConfigurationAdapter(Config config) {
|
||||
super(config);
|
||||
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public Config getCoreConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String saveToString() {
|
||||
return config.saveToString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFromString(String string) throws InvalidConfigurationException {
|
||||
config.loadFromString(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String buildHeader() {
|
||||
return "#" + String.join("\n#", config.getHeader());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigOptionsAdapter options() {
|
||||
return new ConfigOptionsAdapter(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getKeys(boolean deep) {
|
||||
return config.getKeys(deep);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getValues(boolean deep) {
|
||||
return config.getValues(deep);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String path) {
|
||||
return config.contains(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSet(String path) {
|
||||
return config.isSet(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentPath() {
|
||||
return config.getCurrentPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return config.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Configuration getRoot() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationSection getParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDefault(String path, Object value) {
|
||||
config.addDefault(path, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationSection getDefaultSection() {
|
||||
return config.getDefaultSection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(String path, Object value) {
|
||||
config.set(path, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(String path) {
|
||||
return config.get(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(String path, Object def) {
|
||||
return config.get(path, def);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationSection createSection(String path) {
|
||||
return config.createSection(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationSection createSection(String path, Map<?, ?> map) {
|
||||
return config.createSection(path, map);
|
||||
}
|
||||
|
||||
// Other non-FileConfiguration methods
|
||||
|
||||
@NotNull
|
||||
public ConfigSection createDefaultSection(@NotNull String path) {
|
||||
return config.createDefaultSection(path);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection createDefaultSection(@NotNull String path, String... comment) {
|
||||
return config.createDefaultSection(path, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection createDefaultSection(@NotNull String path, ConfigFormattingRules.CommentStyle commentStyle, String... comment) {
|
||||
return config.createDefaultSection(path, commentStyle, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setComment(@NotNull String path, @Nullable ConfigFormattingRules.CommentStyle commentStyle, String... lines) {
|
||||
return config.setComment(path, commentStyle, lines);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setComment(@NotNull String path, @Nullable ConfigFormattingRules.CommentStyle commentStyle, @Nullable List<String> lines) {
|
||||
return config.setComment(path, commentStyle, lines);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefaultComment(@NotNull String path, String... lines) {
|
||||
return config.setDefaultComment(path, lines);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefaultComment(@NotNull String path, @Nullable List<String> lines) {
|
||||
return config.setDefaultComment(path, lines);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefaultComment(@NotNull String path, ConfigFormattingRules.CommentStyle commentStyle, String... lines) {
|
||||
return config.setDefaultComment(path, commentStyle, lines);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefaultComment(@NotNull String path, ConfigFormattingRules.CommentStyle commentStyle, @Nullable List<String> lines) {
|
||||
return config.setDefaultComment(path, commentStyle, lines);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Comment getComment(@NotNull String path) {
|
||||
return config.getComment(path);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getCommentString(@NotNull String path) {
|
||||
return config.getCommentString(path);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<ConfigSection> getSections(String path) {
|
||||
return config.getSections(path);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection set(@NotNull String path, @Nullable Object value, String... comment) {
|
||||
return config.set(path, value, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection set(@NotNull String path, @Nullable Object value, List<String> comment) {
|
||||
return config.set(path, value, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection set(@NotNull String path, @Nullable Object value, @Nullable ConfigFormattingRules.CommentStyle commentStyle, String... comment) {
|
||||
return config.set(path, value, commentStyle, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection set(@NotNull String path, @Nullable Object value, @Nullable ConfigFormattingRules.CommentStyle commentStyle, List<String> comment) {
|
||||
return config.set(path, value, commentStyle, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefault(@NotNull String path, @Nullable Object value) {
|
||||
return config.setDefault(path, value);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefault(@NotNull String path, @Nullable Object value, String... comment) {
|
||||
return config.setDefault(path, value, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefault(@NotNull String path, @Nullable Object value, List<String> comment) {
|
||||
return config.setDefault(path, value, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefault(@NotNull String path, @Nullable Object value, ConfigFormattingRules.CommentStyle commentStyle, String... comment) {
|
||||
return config.setDefault(path, value, commentStyle, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefault(@NotNull String path, @Nullable Object value, ConfigFormattingRules.CommentStyle commentStyle, List<String> comment) {
|
||||
return config.setDefault(path, value, commentStyle, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection createSection(@NotNull String path, String... comment) {
|
||||
return config.createSection(path, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection createSection(@NotNull String path, @Nullable List<String> comment) {
|
||||
return config.createSection(path, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection createSection(@NotNull String path, @Nullable ConfigFormattingRules.CommentStyle commentStyle, String... comment) {
|
||||
return config.createSection(path, commentStyle, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection createSection(@NotNull String path, @Nullable ConfigFormattingRules.CommentStyle commentStyle, @Nullable List<String> comment) {
|
||||
return config.createSection(path, commentStyle, comment);
|
||||
}
|
||||
|
||||
public char getChar(@NotNull String path) {
|
||||
return config.getChar(path);
|
||||
}
|
||||
|
||||
public char getChar(@NotNull String path, char def) {
|
||||
return config.getChar(path, def);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CompatibleMaterial getMaterial(@NotNull String path) {
|
||||
return config.getMaterial(path);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CompatibleMaterial getMaterial(@NotNull String path, @Nullable CompatibleMaterial def) {
|
||||
return config.getMaterial(path, def);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection getOrCreateConfigurationSection(@NotNull String path) {
|
||||
return config.getOrCreateConfigurationSection(path);
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ConfigFormattingRules {
|
||||
int spacesBetweenMainCategories;
|
||||
int spacesBetweenValues;
|
||||
CommentStyle rootCommentStyle = CommentStyle.BLOCKSPACED;
|
||||
CommentStyle mainCategoryCommentStyle = CommentStyle.SPACED;
|
||||
|
||||
public enum CommentStyle {
|
||||
/**
|
||||
* # Comment
|
||||
*/
|
||||
SIMPLE(false, false, " ", ""),
|
||||
/**
|
||||
* # <br />
|
||||
* # Comment <br />
|
||||
* # <br />
|
||||
*/
|
||||
SPACED(false, true, " ", ""),
|
||||
/**
|
||||
* ########### <br />
|
||||
* # Comment # <br />
|
||||
* ########### <br />
|
||||
*/
|
||||
BLOCKED(true, false, " ", " "),
|
||||
/**
|
||||
* ############# <br />
|
||||
* #|¯¯¯¯¯¯¯¯¯|# <br />
|
||||
* #| Comment |# <br />
|
||||
* #|_________|# <br />
|
||||
* ############# <br />
|
||||
*/
|
||||
BLOCKSPACED(true, true, "|\u00AF", '\u00AF', "\u00AF|", "| ", " |", "|_", '_', "_|");
|
||||
|
||||
final boolean drawBorder, drawSpace;
|
||||
final String commentPrefix, spacePrefixTop, spacePrefixBottom;
|
||||
final String commentSuffix, spaceSuffixTop, spaceSuffixBottom;
|
||||
final char spaceCharTop, spaceCharBottom;
|
||||
|
||||
CommentStyle(boolean drawBorder, boolean drawSpace,
|
||||
String spacePrefixTop, char spaceCharTop, String spaceSuffixTop,
|
||||
String commentPrefix, String commentSuffix,
|
||||
String spacePrefixBottom, char spaceCharBottom, String spaceSuffixBottom) {
|
||||
this.drawBorder = drawBorder;
|
||||
this.drawSpace = drawSpace;
|
||||
this.commentPrefix = commentPrefix;
|
||||
this.spacePrefixTop = spacePrefixTop;
|
||||
this.spacePrefixBottom = spacePrefixBottom;
|
||||
this.commentSuffix = commentSuffix;
|
||||
this.spaceSuffixTop = spaceSuffixTop;
|
||||
this.spaceSuffixBottom = spaceSuffixBottom;
|
||||
this.spaceCharTop = spaceCharTop;
|
||||
this.spaceCharBottom = spaceCharBottom;
|
||||
}
|
||||
|
||||
CommentStyle(boolean drawBorder, boolean drawSpace, String commentPrefix, String commentSuffix) {
|
||||
this.drawBorder = drawBorder;
|
||||
this.drawSpace = drawSpace;
|
||||
this.commentPrefix = commentPrefix;
|
||||
this.commentSuffix = commentSuffix;
|
||||
this.spacePrefixTop = this.spacePrefixBottom = "";
|
||||
this.spaceCharTop = this.spaceCharBottom = ' ';
|
||||
this.spaceSuffixTop = this.spaceSuffixBottom = "";
|
||||
}
|
||||
}
|
||||
|
||||
public static CommentStyle parseStyle(List<String> lines) {
|
||||
if (lines == null || lines.size() <= 2) {
|
||||
return CommentStyle.SIMPLE;
|
||||
}
|
||||
|
||||
if (lines.get(0).trim().equals("#") && lines.get(lines.size() - 1).trim().equals("#")) {
|
||||
return CommentStyle.SPACED;
|
||||
}
|
||||
|
||||
boolean hasBorders = lines.get(0).trim().matches("^##+$") && lines.get(lines.size() - 1).trim().matches("^##+$");
|
||||
if (!hasBorders) {
|
||||
// default return
|
||||
return CommentStyle.SIMPLE;
|
||||
}
|
||||
|
||||
// now need to figure out if this is blocked or not
|
||||
if (lines.size() > 4 && lines.get(1).trim().matches(("^#"
|
||||
+ CommentStyle.BLOCKSPACED.spacePrefixTop + CommentStyle.BLOCKSPACED.spaceCharTop + "+"
|
||||
+ CommentStyle.BLOCKSPACED.spaceSuffixTop + "#$").replace("|", "\\|"))
|
||||
&& lines.get(1).trim().matches(("^#"
|
||||
+ CommentStyle.BLOCKSPACED.spacePrefixTop + CommentStyle.BLOCKSPACED.spaceCharTop + "+"
|
||||
+ CommentStyle.BLOCKSPACED.spaceSuffixTop + "#$").replace("|", "\\|"))) {
|
||||
return CommentStyle.BLOCKSPACED;
|
||||
}
|
||||
|
||||
return CommentStyle.BLOCKED;
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import org.bukkit.configuration.file.FileConfigurationOptions;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ConfigOptionsAdapter extends FileConfigurationOptions {
|
||||
final ConfigSection config;
|
||||
|
||||
public ConfigOptionsAdapter(ConfigSection config) {
|
||||
super(config);
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public Config getConfig() {
|
||||
return (Config) config.root;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ConfigFileConfigurationAdapter configuration() {
|
||||
return new ConfigFileConfigurationAdapter((Config) config.root);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ConfigOptionsAdapter copyDefaults(boolean value) {
|
||||
// we always copy new values
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ConfigOptionsAdapter pathSeparator(char value) {
|
||||
(config.root).setPathSeparator(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ConfigOptionsAdapter header(@Nullable String value) {
|
||||
if (value == null) {
|
||||
((Config) config.root).setHeader((List) null);
|
||||
} else {
|
||||
((Config) config.root).setHeader(value.split("\n"));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ConfigOptionsAdapter copyHeader(boolean value) {
|
||||
if (!value) {
|
||||
((Config) config.root).setHeader((List) null);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public int indent() {
|
||||
return config.root.getIndent();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigOptionsAdapter indent(int value) {
|
||||
config.root.setIndent(value);
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,761 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import com.songoda.core.compatibility.CompatibleMaterial;
|
||||
import org.bukkit.configuration.Configuration;
|
||||
import org.bukkit.configuration.MemoryConfiguration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Configuration for a specific node
|
||||
*/
|
||||
public class ConfigSection extends MemoryConfiguration {
|
||||
final String fullPath, nodeKey;
|
||||
final ConfigSection root;
|
||||
final ConfigSection parent;
|
||||
protected int indentation = 2; // between 2 and 9 (inclusive)
|
||||
protected char pathChar = '.';
|
||||
final HashMap<String, Comment> configComments;
|
||||
final HashMap<String, Comment> defaultComments;
|
||||
final LinkedHashMap<String, Object> defaults;
|
||||
final LinkedHashMap<String, Object> values;
|
||||
/**
|
||||
* Internal root state: if any configuration value has changed from file state
|
||||
*/
|
||||
boolean changed = false;
|
||||
final boolean isDefault;
|
||||
final Object lock = new Object();
|
||||
|
||||
ConfigSection() {
|
||||
this.root = this;
|
||||
this.parent = null;
|
||||
isDefault = false;
|
||||
nodeKey = fullPath = "";
|
||||
|
||||
configComments = new HashMap<>();
|
||||
defaultComments = new HashMap<>();
|
||||
defaults = new LinkedHashMap<>();
|
||||
values = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
ConfigSection(ConfigSection root, ConfigSection parent, String nodeKey, boolean isDefault) {
|
||||
this.root = root;
|
||||
this.parent = parent;
|
||||
this.nodeKey = nodeKey;
|
||||
this.fullPath = nodeKey != null ? parent.fullPath + nodeKey + root.pathChar : parent.fullPath;
|
||||
this.isDefault = isDefault;
|
||||
configComments = defaultComments = null;
|
||||
defaults = null;
|
||||
values = null;
|
||||
}
|
||||
|
||||
public int getIndent() {
|
||||
return root.indentation;
|
||||
}
|
||||
|
||||
public void setIndent(int indentation) {
|
||||
root.indentation = indentation;
|
||||
}
|
||||
|
||||
protected void onChange() {
|
||||
if (parent != null) {
|
||||
root.onChange();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the character used to separate configuration nodes. <br>
|
||||
* IMPORTANT: Do not change this after loading or adding ConfigurationSections!
|
||||
*
|
||||
* @param pathChar character to use
|
||||
*/
|
||||
public void setPathSeparator(char pathChar) {
|
||||
if (!root.values.isEmpty() || !root.defaults.isEmpty()) {
|
||||
throw new RuntimeException("Path change after config initialization");
|
||||
}
|
||||
|
||||
root.pathChar = pathChar;
|
||||
}
|
||||
|
||||
public char getPathSeparator() {
|
||||
return root.pathChar;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The full key for this section node
|
||||
*/
|
||||
public String getKey() {
|
||||
return !fullPath.endsWith(String.valueOf(root.pathChar)) ? fullPath : fullPath.substring(0, fullPath.length() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The specific key that was used from the last node to get to this node
|
||||
*/
|
||||
public String getNodeKey() {
|
||||
return nodeKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the path required for this node to exist. <br />
|
||||
* <b>DO NOT USE THIS IN A SYNCHRONIZED LOCK</b>
|
||||
*
|
||||
* @param path full path of the node required. Eg, for foo.bar.node, this will create sections for foo and foo.bar
|
||||
* @param useDefault set to true if this is a default value
|
||||
*/
|
||||
protected void createNodePath(@NotNull String path, boolean useDefault) {
|
||||
if (path.indexOf(root.pathChar) != -1) {
|
||||
// if any intermediate nodes don't exist, create them
|
||||
String[] pathParts = path.split(Pattern.quote(String.valueOf(root.pathChar)));
|
||||
StringBuilder nodePath = new StringBuilder(fullPath);
|
||||
LinkedHashMap<String, Object> writeTo = useDefault ? root.defaults : root.values;
|
||||
ConfigSection travelNode = this;
|
||||
|
||||
synchronized (root.lock) {
|
||||
for (int i = 0; i < pathParts.length - 1; ++i) {
|
||||
final String node = (i != 0 ? nodePath.append(root.pathChar) : nodePath).append(pathParts[i]).toString();
|
||||
|
||||
if (!(writeTo.get(node) instanceof ConfigSection)) {
|
||||
writeTo.put(node, travelNode = new ConfigSection(root, travelNode, pathParts[i], useDefault));
|
||||
} else {
|
||||
travelNode = (ConfigSection) writeTo.get(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection createDefaultSection(@NotNull String path) {
|
||||
createNodePath(path, true);
|
||||
ConfigSection section = new ConfigSection(root, this, path, true);
|
||||
|
||||
synchronized (root.lock) {
|
||||
root.defaults.put(fullPath + path, section);
|
||||
}
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection createDefaultSection(@NotNull String path, String... comment) {
|
||||
createNodePath(path, true);
|
||||
ConfigSection section = new ConfigSection(root, this, path, true);
|
||||
|
||||
synchronized (root.lock) {
|
||||
root.defaults.put(fullPath + path, section);
|
||||
root.defaultComments.put(fullPath + path, new Comment(comment));
|
||||
}
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection createDefaultSection(@NotNull String path, ConfigFormattingRules.CommentStyle commentStyle, String... comment) {
|
||||
createNodePath(path, true);
|
||||
ConfigSection section = new ConfigSection(root, this, path, true);
|
||||
|
||||
synchronized (root.lock) {
|
||||
root.defaults.put(fullPath + path, section);
|
||||
root.defaultComments.put(fullPath + path, new Comment(commentStyle, comment));
|
||||
}
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setComment(@NotNull String path, @Nullable ConfigFormattingRules.CommentStyle commentStyle, String... lines) {
|
||||
return setComment(path, lines != null ? new Comment(commentStyle, lines) : null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setComment(@NotNull String path, @Nullable ConfigFormattingRules.CommentStyle commentStyle, @Nullable List<String> lines) {
|
||||
return setComment(path, lines != null ? new Comment(commentStyle, lines) : null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setComment(@NotNull String path, @Nullable Comment comment) {
|
||||
synchronized (root.lock) {
|
||||
if (isDefault) {
|
||||
root.defaultComments.put(fullPath + path, comment);
|
||||
} else {
|
||||
root.configComments.put(fullPath + path, comment);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefaultComment(@NotNull String path, String... lines) {
|
||||
return setDefaultComment(path, lines.length == 0 ? null : Arrays.asList(lines));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefaultComment(@NotNull String path, @Nullable List<String> lines) {
|
||||
synchronized (root.lock) {
|
||||
root.defaultComments.put(fullPath + path, new Comment(lines));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefaultComment(@NotNull String path, ConfigFormattingRules.CommentStyle commentStyle, String... lines) {
|
||||
return setDefaultComment(path, commentStyle, lines.length == 0 ? null : Arrays.asList(lines));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefaultComment(@NotNull String path, ConfigFormattingRules.CommentStyle commentStyle, @Nullable List<String> lines) {
|
||||
synchronized (root.lock) {
|
||||
root.defaultComments.put(fullPath + path, new Comment(commentStyle, lines));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefaultComment(@NotNull String path, @Nullable Comment comment) {
|
||||
synchronized (root.lock) {
|
||||
root.defaultComments.put(fullPath + path, comment);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Comment getComment(@NotNull String path) {
|
||||
Comment result = root.configComments.get(fullPath + path);
|
||||
|
||||
if (result == null) {
|
||||
result = root.defaultComments.get(fullPath + path);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getCommentString(@NotNull String path) {
|
||||
Comment result = root.configComments.get(fullPath + path);
|
||||
|
||||
if (result == null) {
|
||||
result = root.defaultComments.get(fullPath + path);
|
||||
}
|
||||
|
||||
return result != null ? result.toString() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDefault(@NotNull String path, @Nullable Object value) {
|
||||
createNodePath(path, true);
|
||||
|
||||
synchronized (root.lock) {
|
||||
root.defaults.put(fullPath + path, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDefaults(@NotNull Map<String, Object> defaults) {
|
||||
//defaults.entrySet().stream().forEach(m -> root.defaults.put(fullPath + m.getKey(), m.getValue()));
|
||||
defaults.entrySet().forEach(m -> addDefault(m.getKey(), m.getValue()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaults(Configuration c) {
|
||||
if (fullPath.isEmpty()) {
|
||||
root.defaults.clear();
|
||||
} else {
|
||||
root.defaults.keySet().stream()
|
||||
.filter(k -> k.startsWith(fullPath))
|
||||
.forEach(root.defaults::remove);
|
||||
}
|
||||
|
||||
addDefaults(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigSection getDefaults() {
|
||||
return new ConfigSection(root, this, null, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigSection getDefaultSection() {
|
||||
return new ConfigSection(root, this, null, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigOptionsAdapter options() {
|
||||
return new ConfigOptionsAdapter(root);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<String> getKeys(boolean deep) {
|
||||
LinkedHashSet<String> result = new LinkedHashSet<>();
|
||||
int pathIndex = fullPath.lastIndexOf(root.pathChar);
|
||||
|
||||
if (deep) {
|
||||
result.addAll(root.defaults.keySet().stream()
|
||||
.filter(k -> k.startsWith(fullPath))
|
||||
.map(k -> !k.endsWith(String.valueOf(root.pathChar)) ? k.substring(pathIndex + 1) : k.substring(pathIndex + 1, k.length() - 1))
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new)));
|
||||
result.addAll(root.values.keySet().stream()
|
||||
.filter(k -> k.startsWith(fullPath))
|
||||
.map(k -> !k.endsWith(String.valueOf(root.pathChar)) ? k.substring(pathIndex + 1) : k.substring(pathIndex + 1, k.length() - 1))
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new)));
|
||||
} else {
|
||||
result.addAll(root.defaults.keySet().stream()
|
||||
.filter(k -> k.startsWith(fullPath) && k.lastIndexOf(root.pathChar) == pathIndex)
|
||||
.map(k -> !k.endsWith(String.valueOf(root.pathChar)) ? k.substring(pathIndex + 1) : k.substring(pathIndex + 1, k.length() - 1))
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new)));
|
||||
result.addAll(root.values.keySet().stream()
|
||||
.filter(k -> k.startsWith(fullPath) && k.lastIndexOf(root.pathChar) == pathIndex)
|
||||
.map(k -> !k.endsWith(String.valueOf(root.pathChar)) ? k.substring(pathIndex + 1) : k.substring(pathIndex + 1, k.length() - 1))
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<String, Object> getValues(boolean deep) {
|
||||
LinkedHashMap<String, Object> result = new LinkedHashMap<>();
|
||||
int pathIndex = fullPath.lastIndexOf(root.pathChar);
|
||||
|
||||
if (deep) {
|
||||
result.putAll((Map<String, Object>) root.defaults.entrySet().stream()
|
||||
.filter(k -> k.getKey().startsWith(fullPath))
|
||||
.collect(Collectors.toMap(
|
||||
e -> !e.getKey().endsWith(String.valueOf(root.pathChar)) ? e.getKey().substring(pathIndex + 1) : e.getKey().substring(pathIndex + 1, e.getKey().length() - 1),
|
||||
Map.Entry::getValue,
|
||||
(v1, v2) -> {
|
||||
throw new IllegalStateException();
|
||||
}, // never going to be merging keys
|
||||
LinkedHashMap::new)));
|
||||
|
||||
result.putAll((Map<String, Object>) root.values.entrySet().stream()
|
||||
.filter(k -> k.getKey().startsWith(fullPath))
|
||||
.collect(Collectors.toMap(
|
||||
e -> !e.getKey().endsWith(String.valueOf(root.pathChar)) ? e.getKey().substring(pathIndex + 1) : e.getKey().substring(pathIndex + 1, e.getKey().length() - 1),
|
||||
Map.Entry::getValue,
|
||||
(v1, v2) -> {
|
||||
throw new IllegalStateException();
|
||||
}, // never going to be merging keys
|
||||
LinkedHashMap::new)));
|
||||
} else {
|
||||
result.putAll((Map<String, Object>) root.defaults.entrySet().stream()
|
||||
.filter(k -> k.getKey().startsWith(fullPath) && k.getKey().lastIndexOf(root.pathChar) == pathIndex)
|
||||
.collect(Collectors.toMap(
|
||||
e -> !e.getKey().endsWith(String.valueOf(root.pathChar)) ? e.getKey().substring(pathIndex + 1) : e.getKey().substring(pathIndex + 1, e.getKey().length() - 1),
|
||||
Map.Entry::getValue,
|
||||
(v1, v2) -> {
|
||||
throw new IllegalStateException();
|
||||
}, // never going to be merging keys
|
||||
LinkedHashMap::new)));
|
||||
|
||||
result.putAll((Map<String, Object>) root.values.entrySet().stream()
|
||||
.filter(k -> k.getKey().startsWith(fullPath) && k.getKey().lastIndexOf(root.pathChar) == pathIndex)
|
||||
.collect(Collectors.toMap(
|
||||
e -> !e.getKey().endsWith(String.valueOf(root.pathChar)) ? e.getKey().substring(pathIndex + 1) : e.getKey().substring(pathIndex + 1, e.getKey().length() - 1),
|
||||
Map.Entry::getValue,
|
||||
(v1, v2) -> {
|
||||
throw new IllegalStateException();
|
||||
}, // never going to be merging keys
|
||||
LinkedHashMap::new)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<ConfigSection> getSections(String path) {
|
||||
ConfigSection rootSection = getConfigurationSection(path);
|
||||
|
||||
if (rootSection == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
ArrayList<ConfigSection> result = new ArrayList<>();
|
||||
rootSection.getKeys(false).stream()
|
||||
.map(rootSection::get)
|
||||
.filter(ConfigSection.class::isInstance)
|
||||
.forEachOrdered(object -> result.add((ConfigSection) object));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(@NotNull String path) {
|
||||
return root.defaults.containsKey(fullPath + path) || root.values.containsKey(fullPath + path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(@NotNull String path, boolean ignoreDefault) {
|
||||
return (!ignoreDefault && root.defaults.containsKey(fullPath + path)) || root.values.containsKey(fullPath + path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSet(@NotNull String path) {
|
||||
return root.defaults.get(fullPath + path) != null || root.values.get(fullPath + path) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentPath() {
|
||||
return fullPath.isEmpty() ? "" : fullPath.substring(0, fullPath.length() - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
if (fullPath.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String[] parts = fullPath.split(Pattern.quote(String.valueOf(root.pathChar)));
|
||||
return parts[parts.length - 1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigSection getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigSection getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object get(@NotNull String path) {
|
||||
Object result = root.values.get(fullPath + path);
|
||||
|
||||
if (result == null) {
|
||||
result = root.defaults.get(fullPath + path);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object get(@NotNull String path, @Nullable Object def) {
|
||||
Object result = root.values.get(fullPath + path);
|
||||
|
||||
return result != null ? result : def;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(@NotNull String path, @Nullable Object value) {
|
||||
if (isDefault) {
|
||||
addDefault(path, value);
|
||||
return;
|
||||
}
|
||||
|
||||
createNodePath(path, false);
|
||||
Object last;
|
||||
synchronized (root.lock) {
|
||||
if (value != null) {
|
||||
root.changed |= (last = root.values.put(fullPath + path, value)) != value;
|
||||
} else {
|
||||
root.changed |= (last = root.values.remove(fullPath + path)) != null;
|
||||
}
|
||||
}
|
||||
|
||||
if (last != value && last instanceof ConfigSection) {
|
||||
// clean up orphaned nodes
|
||||
final String trim = fullPath + path + root.pathChar;
|
||||
synchronized (root.lock) {
|
||||
root.values.keySet().stream()
|
||||
.filter(k -> k.startsWith(trim))
|
||||
.collect(Collectors.toSet())
|
||||
.forEach(root.values::remove);
|
||||
}
|
||||
}
|
||||
|
||||
onChange();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection set(@NotNull String path, @Nullable Object value, String... comment) {
|
||||
set(path, value);
|
||||
return setComment(path, null, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection set(@NotNull String path, @Nullable Object value, List<String> comment) {
|
||||
set(path, value);
|
||||
return setComment(path, null, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection set(@NotNull String path, @Nullable Object value, @Nullable ConfigFormattingRules.CommentStyle commentStyle, String... comment) {
|
||||
set(path, value);
|
||||
return setComment(path, commentStyle, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection set(@NotNull String path, @Nullable Object value, @Nullable ConfigFormattingRules.CommentStyle commentStyle, List<String> comment) {
|
||||
set(path, value);
|
||||
return setComment(path, commentStyle, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefault(@NotNull String path, @Nullable Object value) {
|
||||
addDefault(path, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefault(@NotNull String path, @Nullable Object value, String... comment) {
|
||||
addDefault(path, value);
|
||||
return setDefaultComment(path, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefault(@NotNull String path, @Nullable Object value, List<String> comment) {
|
||||
addDefault(path, value);
|
||||
return setDefaultComment(path, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefault(@NotNull String path, @Nullable Object value, ConfigFormattingRules.CommentStyle commentStyle, String... comment) {
|
||||
addDefault(path, value);
|
||||
return setDefaultComment(path, commentStyle, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection setDefault(@NotNull String path, @Nullable Object value, ConfigFormattingRules.CommentStyle commentStyle, List<String> comment) {
|
||||
addDefault(path, value);
|
||||
return setDefaultComment(path, commentStyle, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ConfigSection createSection(@NotNull String path) {
|
||||
createNodePath(path, false);
|
||||
ConfigSection section = new ConfigSection(root, this, path, false);
|
||||
|
||||
synchronized (root.lock) {
|
||||
root.values.put(fullPath + path, section);
|
||||
}
|
||||
|
||||
root.changed = true;
|
||||
onChange();
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection createSection(@NotNull String path, String... comment) {
|
||||
return createSection(path, null, comment.length == 0 ? null : Arrays.asList(comment));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection createSection(@NotNull String path, @Nullable List<String> comment) {
|
||||
return createSection(path, null, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection createSection(@NotNull String path, @Nullable ConfigFormattingRules.CommentStyle commentStyle, String... comment) {
|
||||
return createSection(path, commentStyle, comment.length == 0 ? null : Arrays.asList(comment));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection createSection(@NotNull String path, @Nullable ConfigFormattingRules.CommentStyle commentStyle, @Nullable List<String> comment) {
|
||||
createNodePath(path, false);
|
||||
ConfigSection section = new ConfigSection(root, this, path, false);
|
||||
|
||||
synchronized (root.lock) {
|
||||
root.values.put(fullPath + path, section);
|
||||
}
|
||||
|
||||
setComment(path, commentStyle, comment);
|
||||
root.changed = true;
|
||||
onChange();
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ConfigSection createSection(@NotNull String path, Map<?, ?> map) {
|
||||
createNodePath(path, false);
|
||||
ConfigSection section = new ConfigSection(root, this, path, false);
|
||||
|
||||
synchronized (root.lock) {
|
||||
root.values.put(fullPath + path, section);
|
||||
}
|
||||
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
if (entry.getValue() instanceof Map) {
|
||||
section.createSection(entry.getKey().toString(), (Map<?, ?>) entry.getValue());
|
||||
continue;
|
||||
}
|
||||
|
||||
section.set(entry.getKey().toString(), entry.getValue());
|
||||
}
|
||||
|
||||
root.changed = true;
|
||||
onChange();
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getString(@NotNull String path) {
|
||||
Object result = get(path);
|
||||
|
||||
return result != null ? result.toString() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getString(@NotNull String path, @Nullable String def) {
|
||||
Object result = get(path);
|
||||
|
||||
return result != null ? result.toString() : def;
|
||||
}
|
||||
|
||||
public char getChar(@NotNull String path) {
|
||||
Object result = get(path);
|
||||
|
||||
return result != null && !result.toString().isEmpty() ? result.toString().charAt(0) : '\0';
|
||||
}
|
||||
|
||||
public char getChar(@NotNull String path, char def) {
|
||||
Object result = get(path);
|
||||
|
||||
return result != null && !result.toString().isEmpty() ? result.toString().charAt(0) : def;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(@NotNull String path) {
|
||||
Object result = get(path);
|
||||
|
||||
return result instanceof Number ? ((Number) result).intValue() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(@NotNull String path, int def) {
|
||||
Object result = get(path);
|
||||
|
||||
return result instanceof Number ? ((Number) result).intValue() : def;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(@NotNull String path) {
|
||||
Object result = get(path);
|
||||
|
||||
return result instanceof Boolean ? (Boolean) result : false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(@NotNull String path, boolean def) {
|
||||
Object result = get(path);
|
||||
|
||||
return result instanceof Boolean ? (Boolean) result : def;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(@NotNull String path) {
|
||||
Object result = get(path);
|
||||
|
||||
return result instanceof Number ? ((Number) result).doubleValue() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(@NotNull String path, double def) {
|
||||
Object result = get(path);
|
||||
|
||||
return result instanceof Number ? ((Number) result).doubleValue() : def;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(@NotNull String path) {
|
||||
Object result = get(path);
|
||||
|
||||
return result instanceof Number ? ((Number) result).longValue() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(@NotNull String path, long def) {
|
||||
Object result = get(path);
|
||||
|
||||
return result instanceof Number ? ((Number) result).longValue() : def;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<?> getList(@NotNull String path) {
|
||||
Object result = get(path);
|
||||
|
||||
return result instanceof List ? (List<?>) result : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<?> getList(@NotNull String path, @Nullable List<?> def) {
|
||||
Object result = get(path);
|
||||
|
||||
return result instanceof List ? (List<?>) result : def;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CompatibleMaterial getMaterial(@NotNull String path) {
|
||||
String val = getString(path);
|
||||
|
||||
return val != null ? CompatibleMaterial.getMaterial(val) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CompatibleMaterial getMaterial(@NotNull String path, @Nullable CompatibleMaterial def) {
|
||||
String val = getString(path);
|
||||
|
||||
CompatibleMaterial mat = val != null ? CompatibleMaterial.getMaterial(val) : null;
|
||||
|
||||
return mat != null ? mat : def;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T getObject(@NotNull String path, @NotNull Class<T> clazz) {
|
||||
Object result = get(path);
|
||||
|
||||
return clazz.isInstance(result) ? clazz.cast(result) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T getObject(@NotNull String path, @NotNull Class<T> clazz, @Nullable T def) {
|
||||
Object result = get(path);
|
||||
|
||||
return clazz.isInstance(result) ? clazz.cast(result) : def;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigSection getConfigurationSection(@NotNull String path) {
|
||||
Object result = get(path);
|
||||
|
||||
return result instanceof ConfigSection ? (ConfigSection) result : null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigSection getOrCreateConfigurationSection(@NotNull String path) {
|
||||
Object result = get(path);
|
||||
|
||||
return result instanceof ConfigSection ? (ConfigSection) result : createSection(path);
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import com.songoda.core.SongodaCore;
|
||||
import com.songoda.core.compatibility.CompatibleMaterial;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class ConfigSetting {
|
||||
final Config config;
|
||||
final String key;
|
||||
|
||||
public ConfigSetting(@NotNull Config config, @NotNull String key) {
|
||||
this.config = config;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public ConfigSetting(@NotNull Config config, @NotNull String key, @NotNull Object defaultValue, String... comment) {
|
||||
this.config = config;
|
||||
this.key = key;
|
||||
|
||||
config.setDefault(key, defaultValue, comment);
|
||||
}
|
||||
|
||||
public ConfigSetting(@NotNull Config config, @NotNull String key, @NotNull Object defaultValue, ConfigFormattingRules.CommentStyle commentStyle, String... comment) {
|
||||
this.config = config;
|
||||
this.key = key;
|
||||
|
||||
config.setDefault(key, defaultValue, commentStyle, comment);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public List<Integer> getIntegerList() {
|
||||
return config.getIntegerList(key);
|
||||
}
|
||||
|
||||
public List<String> getStringList() {
|
||||
return config.getStringList(key);
|
||||
}
|
||||
|
||||
public boolean getBoolean() {
|
||||
return config.getBoolean(key);
|
||||
}
|
||||
|
||||
public boolean getBoolean(boolean def) {
|
||||
return config.getBoolean(key, def);
|
||||
}
|
||||
|
||||
public int getInt() {
|
||||
return config.getInt(key);
|
||||
}
|
||||
|
||||
public int getInt(int def) {
|
||||
return config.getInt(key, def);
|
||||
}
|
||||
|
||||
public long getLong() {
|
||||
return config.getLong(key);
|
||||
}
|
||||
|
||||
public long getLong(long def) {
|
||||
return config.getLong(key, def);
|
||||
}
|
||||
|
||||
public double getDouble() {
|
||||
return config.getDouble(key);
|
||||
}
|
||||
|
||||
public double getDouble(double def) {
|
||||
return config.getDouble(key, def);
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return config.getString(key);
|
||||
}
|
||||
|
||||
public String getString(String def) {
|
||||
return config.getString(key, def);
|
||||
}
|
||||
|
||||
public Object getObject() {
|
||||
return config.get(key);
|
||||
}
|
||||
|
||||
public Object getObject(Object def) {
|
||||
return config.get(key, def);
|
||||
}
|
||||
|
||||
public <T> T getObject(@NotNull Class<T> clazz) {
|
||||
return config.getObject(key, clazz);
|
||||
}
|
||||
|
||||
public <T> T getObject(@NotNull Class<T> clazz, @Nullable T def) {
|
||||
return config.getObject(key, clazz, def);
|
||||
}
|
||||
|
||||
public char getChar() {
|
||||
return config.getChar(key);
|
||||
}
|
||||
|
||||
public char getChar(char def) {
|
||||
return config.getChar(key, def);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CompatibleMaterial getMaterial() {
|
||||
String val = config.getString(key);
|
||||
CompatibleMaterial mat = CompatibleMaterial.getMaterial(config.getString(key));
|
||||
|
||||
if (mat == null) {
|
||||
SongodaCore.getLogger().log(Level.WARNING, String.format("Config value \"%s\" has an invalid material name: \"%s\"", key, val));
|
||||
}
|
||||
|
||||
return mat != null ? mat : CompatibleMaterial.STONE;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CompatibleMaterial getMaterial(@NotNull CompatibleMaterial def) {
|
||||
//return config.getMaterial(key, def);
|
||||
String val = config.getString(key);
|
||||
CompatibleMaterial mat = val != null ? CompatibleMaterial.getMaterial(val) : null;
|
||||
|
||||
if (mat == null) {
|
||||
SongodaCore.getLogger().log(Level.WARNING, String.format("Config value \"%s\" has an invalid material name: \"%s\"", key, val));
|
||||
}
|
||||
|
||||
return mat != null ? mat : def;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
public interface DataStoreObject<T> {
|
||||
/**
|
||||
* @return a unique hashable instance of T to store this value under
|
||||
*/
|
||||
T getKey();
|
||||
|
||||
/**
|
||||
* @return a unique identifier for saving this value with
|
||||
*/
|
||||
String getConfigKey();
|
||||
|
||||
/**
|
||||
* Save this data to a ConfigurationSection
|
||||
*/
|
||||
void saveToSection(ConfigurationSection sec);
|
||||
|
||||
/**
|
||||
* @return true if this data has changed from the state saved to file
|
||||
*/
|
||||
boolean hasChanged();
|
||||
|
||||
/**
|
||||
* Mark this data as needing a save or not
|
||||
*/
|
||||
void setChanged(boolean isChanged);
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface HeaderCommentable {
|
||||
void setHeaderComment(@Nullable Supplier<String> comment);
|
||||
|
||||
default void setHeaderComment(@Nullable String comment) {
|
||||
setHeaderComment(() -> comment);
|
||||
}
|
||||
|
||||
@Nullable Supplier<String> getHeaderComment();
|
||||
|
||||
@NotNull String generateHeaderCommentLines();
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
|
||||
public interface IConfiguration {
|
||||
/**
|
||||
* This method returns whether a given key is set memory, ignoring its possibly null value.
|
||||
*
|
||||
* {@link #set(String, Object)}
|
||||
* {@link #unset(String)}
|
||||
*/
|
||||
boolean has(String key);
|
||||
|
||||
/**
|
||||
* This method returns the value for a given key.
|
||||
* A value of null can mean that the key does not exist or that the value is null.
|
||||
*
|
||||
* @see #has(String)
|
||||
*/
|
||||
@Nullable
|
||||
Object get(String key);
|
||||
|
||||
/**
|
||||
* This method is mostly identical to {@link #get(String)}
|
||||
* but returns the given default value if the key doesn't exist or the value is null.
|
||||
*/
|
||||
@Nullable
|
||||
Object getOr(String key, @Nullable Object defaultValue);
|
||||
|
||||
/**
|
||||
* This method sets a given key to a given value in memory.
|
||||
*
|
||||
* @return The previous value associated with key, or null if there was no mapping for key
|
||||
*
|
||||
* @see #save(Writer)
|
||||
*/
|
||||
Object set(@NotNull String key, @Nullable Object value);
|
||||
|
||||
/**
|
||||
* This method removes the given key from memory together with its value.
|
||||
*
|
||||
* @return The previous value associated with key, or null if there was no mapping for key
|
||||
*/
|
||||
Object unset(String key);
|
||||
|
||||
/**
|
||||
* This method clears all the configuration values from memory that have been loaded or set.
|
||||
*
|
||||
* @see #load(Reader)
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* This method parses and loads the configuration and stores them as key-value pairs in memory.
|
||||
* Keys that are not loaded with this call but still exist in memory, are removed.
|
||||
* Additional data may be read depending on the implementation (e.g. comments).
|
||||
*
|
||||
* @see #reset()
|
||||
*/
|
||||
void load(Reader reader) throws IOException;
|
||||
|
||||
/**
|
||||
* This method serializes the key-value pairs in memory and writes them to the given writer.
|
||||
* Additional data may be written depending on the implementation (e.g. comments).
|
||||
*/
|
||||
void save(Writer writer) throws IOException;
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface NodeCommentable {
|
||||
void setNodeComment(@NotNull String key, @Nullable Supplier<String> comment);
|
||||
|
||||
default void setNodeComment(@NotNull String key, @Nullable String comment) {
|
||||
setNodeComment(key, () -> comment);
|
||||
}
|
||||
|
||||
@Nullable Supplier<String> getNodeComment(@Nullable String key);
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import com.songoda.core.utils.Pair;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ReadOnlyConfigEntry implements ConfigEntry {
|
||||
protected final @NotNull IConfiguration config;
|
||||
protected final @NotNull String key;
|
||||
|
||||
public ReadOnlyConfigEntry(@NotNull IConfiguration config, @NotNull String key) {
|
||||
this.config = config;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull IConfiguration getConfig() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Contract(" -> null")
|
||||
public @Nullable Object getDefaultValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Contract("_ -> fail")
|
||||
public void setDefaultValue(@Nullable Object defaultValue) {
|
||||
throw new UnsupportedOperationException("Cannot set defaultValue on a read-only config entry");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Contract("_ -> fail")
|
||||
public ConfigEntry withDefaultValue(@Nullable Object defaultValue) {
|
||||
throw new UnsupportedOperationException("Cannot set defaultValue on a read-only config entry");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Contract("_ -> fail")
|
||||
public ConfigEntry withComment(Supplier<String> comment) {
|
||||
throw new UnsupportedOperationException("Cannot set comment on a read-only config entry");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Contract(" -> null")
|
||||
public Map<Integer, Pair<String, Function<Object, Object>>> getUpgradeSteps() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Contract("_, _, _ -> fail")
|
||||
public ConfigEntry withUpgradeStep(int version, @Nullable String keyInGivenVersion, @Nullable Function<Object, Object> valueConverter) {
|
||||
throw new UnsupportedOperationException("Cannot set upgrade step on a read-only config entry");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Contract("_ -> fail")
|
||||
public void set(@Nullable Object value) {
|
||||
throw new UnsupportedOperationException("Cannot set value on a read-only config entry");
|
||||
}
|
||||
}
|
@ -0,0 +1,284 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Used to easily store a set of one data value
|
||||
*
|
||||
* @param <T> DataObject class that is used to store the data
|
||||
*/
|
||||
public class SimpleDataStore<T extends DataStoreObject> {
|
||||
protected final Plugin plugin;
|
||||
protected final String filename, dirName;
|
||||
private final Function<ConfigurationSection, T> getFromSection;
|
||||
protected final HashMap<Object, T> data = new HashMap<>();
|
||||
private File file;
|
||||
private final Object lock = new Object();
|
||||
SaveTask saveTask;
|
||||
Timer autosaveTimer;
|
||||
/**
|
||||
* time in seconds to start a save after a change is made
|
||||
*/
|
||||
int autosaveInterval = 60;
|
||||
|
||||
public SimpleDataStore(@NotNull Plugin plugin, @NotNull String filename, @NotNull Function<ConfigurationSection, T> loadFunction) {
|
||||
this.plugin = plugin;
|
||||
this.filename = filename;
|
||||
dirName = null;
|
||||
this.getFromSection = loadFunction;
|
||||
}
|
||||
|
||||
public SimpleDataStore(@NotNull Plugin plugin, @Nullable String directory, @NotNull String filename, @NotNull Function<ConfigurationSection, T> loadFunction) {
|
||||
this.plugin = plugin;
|
||||
this.filename = filename;
|
||||
this.dirName = directory;
|
||||
this.getFromSection = loadFunction;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public File getFile() {
|
||||
if (file == null) {
|
||||
if (dirName != null) {
|
||||
this.file = new File(plugin.getDataFolder() + dirName, filename != null ? filename : "data.yml");
|
||||
} else {
|
||||
this.file = new File(plugin.getDataFolder(), filename != null ? filename : "data.yml");
|
||||
}
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a directly-modifiable instance of the data mapping for this
|
||||
* storage
|
||||
*/
|
||||
public Map<Object, T> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped, or {@code null}
|
||||
* if this map contains no mapping for the key.
|
||||
*
|
||||
* @param key key whose mapping is to be retrieved from this storage
|
||||
*
|
||||
* @return the value associated with <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
*/
|
||||
@Nullable
|
||||
public T get(Object key) {
|
||||
return data.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping for the specified key from this storage if present.
|
||||
*
|
||||
* @param key key whose mapping is to be removed from this storage
|
||||
*
|
||||
* @return the previous value associated with <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
*/
|
||||
@Nullable
|
||||
public T remove(@NotNull Object key) {
|
||||
T temp;
|
||||
|
||||
synchronized (lock) {
|
||||
temp = data.remove(key);
|
||||
}
|
||||
|
||||
save();
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping for the specified key from this storage if present.
|
||||
*
|
||||
* @param value value whose mapping is to be removed from this storage
|
||||
*
|
||||
* @return the previous value associated with <tt>key</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
|
||||
*/
|
||||
@Nullable
|
||||
public T remove(@NotNull T value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
T temp;
|
||||
|
||||
synchronized (lock) {
|
||||
temp = data.remove(value.getKey());
|
||||
}
|
||||
|
||||
save();
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified value in this storage. If the map previously contained
|
||||
* a mapping for the key, the old value is replaced.
|
||||
*
|
||||
* @param value value to be added
|
||||
*
|
||||
* @return the previous value associated with <tt>value.getKey()</tt>, or
|
||||
* <tt>null</tt> if there was no mapping for <tt>value.getKey()</tt>.
|
||||
*/
|
||||
@Nullable
|
||||
public T add(@NotNull T value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
T temp;
|
||||
|
||||
synchronized (lock) {
|
||||
temp = data.put(value.getKey(), value);
|
||||
}
|
||||
|
||||
save();
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified value in this storage. If the map previously contained
|
||||
* a mapping for the key, the old value is replaced.
|
||||
*
|
||||
* @param value values to be added
|
||||
*/
|
||||
public void addAll(@NotNull T[] value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
for (T t : value) {
|
||||
if (t != null) {
|
||||
data.put(t.getKey(), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified value in this storage. If the map previously contained
|
||||
* a mapping for the key, the old value is replaced.
|
||||
*
|
||||
* @param value values to be added
|
||||
*/
|
||||
@Nullable
|
||||
public void addAll(@NotNull Collection<T> value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
for (T v : value) {
|
||||
if (v != null) {
|
||||
data.put(v.getKey(), v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data from the associated file
|
||||
*/
|
||||
public void load() {
|
||||
if (!getFile().exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
YamlConfiguration f = new YamlConfiguration();
|
||||
f.options().pathSeparator('\0');
|
||||
f.load(file);
|
||||
|
||||
synchronized (lock) {
|
||||
data.clear();
|
||||
|
||||
f.getValues(false).values().stream()
|
||||
.filter(ConfigurationSection.class::isInstance)
|
||||
.map(v -> getFromSection.apply((ConfigurationSection) v))
|
||||
.forEach(v -> data.put(v.getKey(), v));
|
||||
}
|
||||
} catch (IOException | InvalidConfigurationException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to load data from " + file.getName(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally save this storage's data to file if there have been changes
|
||||
* made
|
||||
*/
|
||||
public void saveChanges() {
|
||||
if (saveTask != null || data.values().stream().anyMatch(DataStoreObject::hasChanged)) {
|
||||
flushSave();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save this file data. This saves later asynchronously.
|
||||
*/
|
||||
public void save() {
|
||||
// save async even if no plugin or if plugin disabled
|
||||
if (saveTask == null) {
|
||||
autosaveTimer = new Timer((plugin != null ? plugin.getName() + "-DataStoreSave-" : "DataStoreSave-") + getFile().getName());
|
||||
autosaveTimer.schedule(saveTask = new SaveTask(), autosaveInterval * 1000L);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a new save of this storage's data
|
||||
*/
|
||||
public void flushSave() {
|
||||
if (saveTask != null) {
|
||||
//Close Threads
|
||||
saveTask.cancel();
|
||||
autosaveTimer.cancel();
|
||||
saveTask = null;
|
||||
autosaveTimer = null;
|
||||
}
|
||||
|
||||
YamlConfiguration f = new YamlConfiguration();
|
||||
|
||||
synchronized (lock) {
|
||||
data.values().forEach(e -> e.saveToSection(f.createSection(e.getConfigKey())));
|
||||
}
|
||||
|
||||
try {
|
||||
f.save(getFile());
|
||||
data.values().forEach(e -> e.setChanged(false));
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to save data to " + file.getName(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
class SaveTask extends TimerTask {
|
||||
@Override
|
||||
public void run() {
|
||||
flushSave();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface WriteableConfigEntry extends ConfigEntry {
|
||||
@Override
|
||||
default void set(@Nullable Object value) {
|
||||
getConfig().set(getKey(), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ConfigEntry withComment(Supplier<String> comment) {
|
||||
((NodeCommentable) getConfig()).setNodeComment(getKey(), comment);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package com.songoda.core.configuration.editor;
|
||||
|
||||
import com.songoda.core.compatibility.CompatibleMaterial;
|
||||
import com.songoda.core.configuration.Config;
|
||||
import com.songoda.core.gui.Gui;
|
||||
import com.songoda.core.gui.GuiUtils;
|
||||
import com.songoda.core.gui.SimplePagedGui;
|
||||
@ -26,10 +27,7 @@ import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Edit a configuration file for a specific plugin
|
||||
*
|
||||
* @deprecated Needs a recode in another package
|
||||
*/
|
||||
@Deprecated
|
||||
public class ConfigEditorGui extends SimplePagedGui {
|
||||
final JavaPlugin plugin;
|
||||
final String file;
|
||||
@ -275,9 +273,9 @@ public class ConfigEditorGui extends SimplePagedGui {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to save config changes to " + file, ex);
|
||||
return;
|
||||
}
|
||||
}/* else if (config instanceof Config) {
|
||||
} else if (config instanceof Config) {
|
||||
((Config) config).save();
|
||||
}*/ else {
|
||||
} else {
|
||||
player.sendMessage(ChatColor.RED + "Unknown configuration type '" + config.getClass().getName() + "' - Please report this error!");
|
||||
plugin.getLogger().log(Level.WARNING, "Unknown configuration type '" + config.getClass().getName() + "' - Please report this error!");
|
||||
return;
|
||||
|
@ -12,10 +12,7 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* Edit a string list
|
||||
*
|
||||
* @deprecated Needs a recode in another package
|
||||
*/
|
||||
@Deprecated
|
||||
public class ConfigEditorListEditorGui extends SimplePagedGui {
|
||||
final ConfigEditorGui current;
|
||||
|
||||
|
@ -2,6 +2,7 @@ package com.songoda.core.configuration.editor;
|
||||
|
||||
import com.songoda.core.SongodaPlugin;
|
||||
import com.songoda.core.compatibility.CompatibleMaterial;
|
||||
import com.songoda.core.configuration.Config;
|
||||
import com.songoda.core.gui.Gui;
|
||||
import com.songoda.core.gui.GuiUtils;
|
||||
import com.songoda.core.gui.SimplePagedGui;
|
||||
@ -18,10 +19,7 @@ import java.util.Map;
|
||||
|
||||
/**
|
||||
* Edit all configuration files for a specific plugin
|
||||
*
|
||||
* @deprecated Needs a recode in another package
|
||||
*/
|
||||
@Deprecated
|
||||
public class PluginConfigGui extends SimplePagedGui {
|
||||
final JavaPlugin plugin;
|
||||
LinkedHashMap<String, MemoryConfiguration> configs = new LinkedHashMap<>();
|
||||
@ -35,18 +33,14 @@ public class PluginConfigGui extends SimplePagedGui {
|
||||
|
||||
this.plugin = plugin;
|
||||
|
||||
// FIXME: Add SongodaCore config
|
||||
// FIXME: Add plugin configs
|
||||
plugin.getLogger().warning("Loading configs for " + plugin.getName() + " is not supported at the moment.");
|
||||
|
||||
// collect list of plugins
|
||||
// configs.put(plugin.getCoreConfig().getFile().getName(), plugin.getCoreConfig());
|
||||
// List<Config> more = plugin.getConfigs();
|
||||
// if (more != null && !more.isEmpty()) {
|
||||
// for (Config cfg : more) {
|
||||
// configs.put(cfg.getFile().getName(), cfg);
|
||||
// }
|
||||
// }
|
||||
configs.put(plugin.getCoreConfig().getFile().getName(), plugin.getCoreConfig());
|
||||
List<Config> more = plugin.getExtraConfig();
|
||||
if (more != null && !more.isEmpty()) {
|
||||
for (Config cfg : more) {
|
||||
configs.put(cfg.getFile().getName(), cfg);
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
|
@ -1,245 +0,0 @@
|
||||
package com.songoda.core.configuration.songoda;
|
||||
|
||||
import com.songoda.core.configuration.ConfigEntry;
|
||||
import com.songoda.core.configuration.ReadOnlyConfigEntry;
|
||||
import com.songoda.core.configuration.yaml.YamlConfigEntry;
|
||||
import com.songoda.core.configuration.yaml.YamlConfiguration;
|
||||
import com.songoda.core.utils.Pair;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
// TODO: replace all config related exceptions with custom exceptions
|
||||
// TODO: Allow registering load-Listeners
|
||||
// TODO: Provide method to only save if changed
|
||||
public class SongodaYamlConfig extends YamlConfiguration {
|
||||
protected static final String CANNOT_CREATE_BACKUP_COPY_EXCEPTION_PREFIX = "Unable to create backup copy of config file: ";
|
||||
|
||||
public final @NotNull File file;
|
||||
protected final @NotNull Logger logger;
|
||||
|
||||
private int targetVersion;
|
||||
private ConfigEntry versionEntry;
|
||||
|
||||
protected final Map<String, ConfigEntry> configEntries = new LinkedHashMap<>(0);
|
||||
|
||||
public SongodaYamlConfig(@NotNull JavaPlugin plugin, @NotNull File file) {
|
||||
this(file, plugin.getLogger());
|
||||
}
|
||||
|
||||
public SongodaYamlConfig(@NotNull JavaPlugin plugin, @NotNull String fileName) {
|
||||
this(new File(plugin.getDataFolder(), fileName), plugin.getLogger());
|
||||
}
|
||||
|
||||
public SongodaYamlConfig(@NotNull File file) {
|
||||
this(file, null);
|
||||
}
|
||||
|
||||
public SongodaYamlConfig(@NotNull File file, @Nullable Logger logger) {
|
||||
super();
|
||||
|
||||
this.file = Objects.requireNonNull(file);
|
||||
this.logger = logger != null ? logger : Logger.getLogger(getClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #load()} and then {@link #save()}.<br>
|
||||
* <br>
|
||||
* As this is intended to keep the {@link org.bukkit.plugin.java.JavaPlugin#onEnable()} method clean,
|
||||
* it catches all exceptions and logs them instead.<br>
|
||||
* <br>
|
||||
* If this method returns false, the plugins should be disabled.
|
||||
*
|
||||
* @return true if the load and save were successful, false if an exception was thrown.
|
||||
*
|
||||
* @see #save()
|
||||
* @see #load()
|
||||
*/
|
||||
public boolean init() {
|
||||
try {
|
||||
this.load();
|
||||
this.save();
|
||||
|
||||
return true;
|
||||
} catch (IOException ex) {
|
||||
this.logger.log(Level.SEVERE, "Failed to load config file: " + this.file.getPath(), ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public ReadOnlyConfigEntry getReadEntry(@NotNull String key) {
|
||||
return new ReadOnlyConfigEntry(this, key);
|
||||
}
|
||||
|
||||
public ConfigEntry createEntry(@NotNull String key) {
|
||||
return createEntry(key, null);
|
||||
}
|
||||
|
||||
public ConfigEntry createEntry(@NotNull String key, @Nullable Object defaultValue) {
|
||||
ConfigEntry entry = new YamlConfigEntry(this, key, defaultValue);
|
||||
|
||||
if (this.configEntries.putIfAbsent(key, entry) != null) {
|
||||
throw new IllegalArgumentException("Entry already exists for key: " + key);
|
||||
}
|
||||
|
||||
if (entry.get() == null) {
|
||||
entry.set(defaultValue);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
public SongodaYamlConfig withVersion(int version) {
|
||||
return withVersion("version", version, () -> "Don't touch this – it's used to track the version of the config.");
|
||||
}
|
||||
|
||||
public SongodaYamlConfig withVersion(@NotNull String key, int version, @Nullable Supplier<String> comment) {
|
||||
if (version < 0) {
|
||||
throw new IllegalArgumentException("Version must be positive");
|
||||
}
|
||||
|
||||
if (this.versionEntry != null) {
|
||||
this.versionEntry.set(null);
|
||||
}
|
||||
|
||||
this.targetVersion = version;
|
||||
|
||||
this.versionEntry = new YamlConfigEntry(this, key, 0);
|
||||
this.versionEntry.withComment(comment);
|
||||
this.versionEntry.set(this.targetVersion);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void load() throws IOException {
|
||||
try (Reader reader = Files.newBufferedReader(this.file.toPath(), StandardCharsets.UTF_8)) {
|
||||
load(reader);
|
||||
} catch (FileNotFoundException ignore) {
|
||||
} catch (IOException ex) {
|
||||
throw new IOException("Unable to load '" + this.file.getPath() + "'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
Files.createDirectories(this.file.toPath().getParent());
|
||||
|
||||
|
||||
try (Writer writer = Files.newBufferedWriter(this.file.toPath(), StandardCharsets.UTF_8)) {
|
||||
super.save(writer);
|
||||
} catch (IOException ex) {
|
||||
throw new IOException("Unable to save '" + this.file.getPath() + "'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(Reader reader) throws IOException {
|
||||
super.load(reader);
|
||||
|
||||
upgradeOldConfigVersion();
|
||||
|
||||
for (ConfigEntry entry : this.configEntries.values()) {
|
||||
if (entry.get() == null && entry.getDefaultValue() != null) {
|
||||
entry.set(entry.getDefaultValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false, if no config version is set or no upgrade is needed
|
||||
*/
|
||||
protected boolean upgradeOldConfigVersion() throws IOException {
|
||||
if (this.versionEntry == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.versionEntry.getInt() > this.targetVersion) {
|
||||
throw new IllegalStateException("Cannot upgrade a config version that is higher than the target version");
|
||||
}
|
||||
if (this.versionEntry.getInt() == this.targetVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
createBackupCopyFile();
|
||||
|
||||
while (this.versionEntry.getInt() < this.targetVersion) {
|
||||
upgradeOldConfigVersionByOne();
|
||||
}
|
||||
|
||||
cleanValuesMap(this.values);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void upgradeOldConfigVersionByOne() {
|
||||
int currentVersion = this.versionEntry.getInt();
|
||||
int targetVersion = currentVersion + 1;
|
||||
|
||||
if (targetVersion > this.targetVersion) {
|
||||
throw new IllegalStateException("Cannot upgrade a config version that is higher than the target version");
|
||||
}
|
||||
|
||||
for (ConfigEntry entry : this.configEntries.values()) {
|
||||
if (entry.getUpgradeSteps() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Pair<@Nullable String, @Nullable Function<Object, Object>> upgradeStep = entry.getUpgradeSteps().get(currentVersion);
|
||||
if (upgradeStep == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String oldEntryKey = upgradeStep.getFirst();
|
||||
if (oldEntryKey == null) {
|
||||
oldEntryKey = entry.getKey();
|
||||
}
|
||||
|
||||
Object newValue = get(oldEntryKey);
|
||||
if (upgradeStep.getSecond() != null) {
|
||||
newValue = upgradeStep.getSecond().apply(newValue);
|
||||
}
|
||||
|
||||
set(oldEntryKey, null);
|
||||
entry.set(newValue);
|
||||
}
|
||||
|
||||
this.versionEntry.set(targetVersion);
|
||||
}
|
||||
|
||||
protected void createBackupCopyFile() throws IOException {
|
||||
if (!this.file.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Path targetPath = this.file.toPath().resolveSibling(this.file.getPath() + ".backup-" + System.currentTimeMillis());
|
||||
|
||||
Files.copy(
|
||||
this.file.toPath(),
|
||||
targetPath,
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
);
|
||||
|
||||
this.logger.info("Created backup copy of config file '" + this.file.getPath() + "' to '" + targetPath + "'");
|
||||
} catch (IOException ex) {
|
||||
throw new IOException(CANNOT_CREATE_BACKUP_COPY_EXCEPTION_PREFIX + this.file.getPath(), ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
package com.songoda.core.configuration.yaml;
|
||||
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.comments.CommentLine;
|
||||
import org.yaml.snakeyaml.comments.CommentType;
|
||||
import org.yaml.snakeyaml.events.CommentEvent;
|
||||
import org.yaml.snakeyaml.nodes.MappingNode;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
import org.yaml.snakeyaml.nodes.NodeTuple;
|
||||
import org.yaml.snakeyaml.nodes.ScalarNode;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class YamlCommentRepresenter extends Representer {
|
||||
private final Map<String, Supplier<String>> nodeComments;
|
||||
|
||||
public YamlCommentRepresenter(DumperOptions dumperOptions, Map<String, Supplier<String>> nodeComments) {
|
||||
super(dumperOptions);
|
||||
this.nodeComments = nodeComments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node represent(Object data) {
|
||||
Node rootNode = super.represent(data);
|
||||
|
||||
if (!(rootNode instanceof MappingNode)) {
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
for (NodeTuple nodeTuple : ((MappingNode) rootNode).getValue()) {
|
||||
if (!(nodeTuple.getKeyNode() instanceof ScalarNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
applyComment((ScalarNode) nodeTuple.getKeyNode(), ((ScalarNode) nodeTuple.getKeyNode()).getValue());
|
||||
|
||||
if (nodeTuple.getValueNode() instanceof MappingNode) {
|
||||
String key = ((ScalarNode) nodeTuple.getKeyNode()).getValue();
|
||||
|
||||
resolveSubNodes(((MappingNode) nodeTuple.getValueNode()), key);
|
||||
}
|
||||
}
|
||||
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
protected void resolveSubNodes(MappingNode mappingNode, String key) {
|
||||
for (NodeTuple nodeTuple : mappingNode.getValue()) {
|
||||
if (!(nodeTuple.getKeyNode() instanceof ScalarNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String newKey = key + "." + ((ScalarNode) nodeTuple.getKeyNode()).getValue();
|
||||
|
||||
applyComment((ScalarNode) nodeTuple.getKeyNode(), newKey);
|
||||
|
||||
if (nodeTuple.getValueNode() instanceof MappingNode) {
|
||||
resolveSubNodes(((MappingNode) nodeTuple.getValueNode()), newKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyComment(ScalarNode scalarNode, String key) {
|
||||
Supplier<String> innerValue = this.nodeComments.get(key);
|
||||
|
||||
if (innerValue != null) {
|
||||
scalarNode.setBlockComments(Collections.singletonList(new CommentLine(new CommentEvent(CommentType.BLOCK, " " + innerValue.get(), null, null))));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package com.songoda.core.configuration.yaml;
|
||||
|
||||
import com.songoda.core.configuration.ConfigEntry;
|
||||
import com.songoda.core.configuration.IConfiguration;
|
||||
import com.songoda.core.configuration.WriteableConfigEntry;
|
||||
import com.songoda.core.utils.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class YamlConfigEntry implements WriteableConfigEntry {
|
||||
protected final @NotNull YamlConfiguration config;
|
||||
protected final @NotNull String key;
|
||||
protected @Nullable Object defaultValue;
|
||||
|
||||
protected @Nullable Map<Integer, Pair<@Nullable String, @Nullable Function<@Nullable Object, @Nullable Object>>> upgradeSteps;
|
||||
|
||||
public YamlConfigEntry(@NotNull YamlConfiguration config, @NotNull String key, @Nullable Object defaultValue) {
|
||||
this.config = config;
|
||||
this.key = key;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull IConfiguration getConfig() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object getDefaultValue() {
|
||||
return this.defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Map<Integer, Pair<@Nullable String, @Nullable Function<@Nullable Object, @Nullable Object>>> getUpgradeSteps() {
|
||||
return this.upgradeSteps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultValue(@Nullable Object defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigEntry withDefaultValue(@Nullable Object defaultValue) {
|
||||
this.setDefaultValue(defaultValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigEntry withUpgradeStep(int version, @Nullable String keyInGivenVersion, @Nullable Function<@Nullable Object, @Nullable Object> valueConverter) {
|
||||
if (keyInGivenVersion == null && valueConverter == null) {
|
||||
throw new IllegalArgumentException("You must provide either a key or a value converter");
|
||||
}
|
||||
|
||||
if (this.upgradeSteps == null) {
|
||||
this.upgradeSteps = new HashMap<>(1);
|
||||
}
|
||||
|
||||
this.upgradeSteps.put(version, new Pair<>(keyInGivenVersion, valueConverter));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
YamlConfigEntry that = (YamlConfigEntry) o;
|
||||
return this.config.equals(that.config) &&
|
||||
this.key.equals(that.key) &&
|
||||
Objects.equals(this.defaultValue, that.defaultValue) &&
|
||||
Objects.equals(this.upgradeSteps, that.upgradeSteps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.config, this.key, this.defaultValue, this.upgradeSteps);
|
||||
}
|
||||
}
|
@ -1,371 +0,0 @@
|
||||
package com.songoda.core.configuration.yaml;
|
||||
|
||||
import com.songoda.core.configuration.HeaderCommentable;
|
||||
import com.songoda.core.configuration.IConfiguration;
|
||||
import com.songoda.core.configuration.NodeCommentable;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
// TODO: Allow registering own custom value converter (e.g. Bukkit-Location to Map and back)
|
||||
// + move the huge block from #set into such a converter and register it by default
|
||||
public class YamlConfiguration implements IConfiguration, HeaderCommentable, NodeCommentable {
|
||||
protected final @NotNull Yaml yaml;
|
||||
protected final @NotNull DumperOptions yamlDumperOptions;
|
||||
protected final @NotNull YamlCommentRepresenter yamlCommentRepresenter;
|
||||
|
||||
protected final @NotNull Map<String, Object> values;
|
||||
protected final @NotNull Map<String, Supplier<String>> nodeComments;
|
||||
protected @Nullable Supplier<String> headerComment;
|
||||
|
||||
public YamlConfiguration() {
|
||||
this(new LinkedHashMap<>(), new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
protected YamlConfiguration(@NotNull Map<String, Object> values, @NotNull Map<String, Supplier<String>> nodeComments) {
|
||||
this.values = Objects.requireNonNull(values);
|
||||
this.nodeComments = Objects.requireNonNull(nodeComments);
|
||||
|
||||
this.yamlDumperOptions = createDefaultYamlDumperOptions();
|
||||
this.yamlCommentRepresenter = new YamlCommentRepresenter(this.yamlDumperOptions, this.nodeComments);
|
||||
this.yaml = createDefaultYaml(this.yamlDumperOptions, this.yamlCommentRepresenter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Contract(pure = true, value = "null -> false")
|
||||
public boolean has(String key) {
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] fullKeyPath = key.split("\\.");
|
||||
|
||||
Map<String, ?> innerMap = getInnerMap(this.values, Arrays.copyOf(fullKeyPath, fullKeyPath.length - 1), false);
|
||||
|
||||
if (innerMap != null) {
|
||||
return innerMap.containsKey(fullKeyPath[fullKeyPath.length - 1]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Contract(pure = true, value = "null -> null")
|
||||
public @Nullable Object get(String key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return getInnerValueForKey(this.values, key);
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Contract(pure = true, value = "null,_ -> param2")
|
||||
public @Nullable Object getOr(String key, @Nullable Object fallbackValue) {
|
||||
Object value = get(key);
|
||||
|
||||
return value == null ? fallbackValue : value;
|
||||
}
|
||||
|
||||
public @NotNull Set<String> getKeys(String key) {
|
||||
if (key == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
if (key.equals("")) {
|
||||
return Collections.unmodifiableSet(this.values.keySet());
|
||||
}
|
||||
|
||||
Map<String, ?> innerMap = null;
|
||||
|
||||
try {
|
||||
innerMap = getInnerMap(this.values, key.split("\\."), false);
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
}
|
||||
|
||||
if (innerMap != null) {
|
||||
return Collections.unmodifiableSet(innerMap.keySet());
|
||||
}
|
||||
|
||||
return Collections.emptySet();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object set(@NotNull String key, @Nullable Object value) {
|
||||
if (value != null) {
|
||||
if (value instanceof Float) {
|
||||
value = ((Float) value).doubleValue();
|
||||
} else if (value instanceof Character) {
|
||||
value = ((Character) value).toString();
|
||||
} else if (value.getClass().isEnum()) {
|
||||
value = ((Enum<?>) value).name();
|
||||
} else if (value.getClass().isArray()) {
|
||||
if (value instanceof int[]) {
|
||||
value = Arrays.asList(ArrayUtils.toObject((int[]) value));
|
||||
} else if (value instanceof long[]) {
|
||||
value = Arrays.asList(ArrayUtils.toObject((long[]) value));
|
||||
} else if (value instanceof short[]) {
|
||||
List<Integer> newValue = new ArrayList<>(((short[]) value).length);
|
||||
for (Short s : (short[]) value) {
|
||||
newValue.add(s.intValue());
|
||||
}
|
||||
value = newValue;
|
||||
} else if (value instanceof byte[]) {
|
||||
List<Integer> newValue = new ArrayList<>(((byte[]) value).length);
|
||||
for (Byte b : (byte[]) value) {
|
||||
newValue.add(b.intValue());
|
||||
}
|
||||
value = newValue;
|
||||
} else if (value instanceof double[]) {
|
||||
value = Arrays.asList(ArrayUtils.toObject((double[]) value));
|
||||
} else if (value instanceof float[]) {
|
||||
List<Double> newValue = new ArrayList<>(((float[]) value).length);
|
||||
for (float f : (float[]) value) {
|
||||
newValue.add(new Float(f).doubleValue());
|
||||
}
|
||||
value = newValue;
|
||||
} else if (value instanceof boolean[]) {
|
||||
value = Arrays.asList(ArrayUtils.toObject((boolean[]) value));
|
||||
} else if (value instanceof char[]) {
|
||||
List<String> newValue = new ArrayList<>(((char[]) value).length);
|
||||
for (char c : (char[]) value) {
|
||||
newValue.add(String.valueOf(c));
|
||||
}
|
||||
value = newValue;
|
||||
} else {
|
||||
value = Arrays.asList((Object[]) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return setInnerValueForKey(this.values, key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object unset(String key) {
|
||||
String[] fullKeyPath = key.split("\\.");
|
||||
|
||||
Map<String, ?> innerMap = getInnerMap(this.values, Arrays.copyOf(fullKeyPath, fullKeyPath.length - 1), false);
|
||||
|
||||
if (innerMap != null) {
|
||||
return innerMap.remove(fullKeyPath[fullKeyPath.length - 1]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.values.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(Reader reader) throws IOException {
|
||||
Object yamlData = this.yaml.load(reader);
|
||||
if (yamlData == null) {
|
||||
yamlData = Collections.emptyMap();
|
||||
}
|
||||
|
||||
if (!(yamlData instanceof Map)) {
|
||||
throw new IllegalStateException("The YAML file does not have the expected tree structure: " + yamlData.getClass().getName());
|
||||
}
|
||||
|
||||
synchronized (this.values) {
|
||||
this.values.clear();
|
||||
|
||||
for (Map.Entry<?, ?> yamlEntry : ((Map<?, ?>) yamlData).entrySet()) {
|
||||
this.values.put(yamlEntry.getKey().toString(), yamlEntry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(Writer writer) throws IOException {
|
||||
String headerCommentLines = generateHeaderCommentLines();
|
||||
writer.write(headerCommentLines);
|
||||
|
||||
cleanValuesMap(this.values);
|
||||
|
||||
if (this.values.size() > 0) {
|
||||
if (headerCommentLines.length() > 0) {
|
||||
writer.write(this.yamlDumperOptions.getLineBreak().getString());
|
||||
}
|
||||
|
||||
this.yaml.dump(this.values, writer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeaderComment(@Nullable Supplier<String> comment) {
|
||||
this.headerComment = comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Supplier<String> getHeaderComment() {
|
||||
return this.headerComment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String generateHeaderCommentLines() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
String headerCommentString = this.headerComment == null ? null : this.headerComment.get();
|
||||
if (headerCommentString != null) {
|
||||
for (String commentLine : headerCommentString.split("\r?\n")) {
|
||||
sb.append("# ")
|
||||
.append(commentLine)
|
||||
.append(this.yamlDumperOptions.getLineBreak().getString());
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNodeComment(@NotNull String key, @Nullable Supplier<String> comment) {
|
||||
this.nodeComments.put(key, comment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Supplier<String> getNodeComment(@Nullable String key) {
|
||||
return this.nodeComments.get(key);
|
||||
}
|
||||
|
||||
public String toYamlString() throws IOException {
|
||||
StringWriter writer = new StringWriter();
|
||||
save(writer);
|
||||
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "YamlConfiguration{" +
|
||||
"values=" + this.values +
|
||||
", headerComment=" + this.headerComment +
|
||||
'}';
|
||||
}
|
||||
|
||||
protected static DumperOptions createDefaultYamlDumperOptions() {
|
||||
DumperOptions dumperOptions = new DumperOptions();
|
||||
dumperOptions.setProcessComments(true);
|
||||
|
||||
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
dumperOptions.setIndentWithIndicator(true);
|
||||
dumperOptions.setIndicatorIndent(2);
|
||||
|
||||
return dumperOptions;
|
||||
}
|
||||
|
||||
protected static Yaml createDefaultYaml(DumperOptions dumperOptions, Representer representer) {
|
||||
LoaderOptions yamlOptions = new LoaderOptions();
|
||||
yamlOptions.setAllowDuplicateKeys(false);
|
||||
|
||||
return new Yaml(new Constructor(yamlOptions), representer, dumperOptions, yamlOptions);
|
||||
}
|
||||
|
||||
protected static Object setInnerValueForKey(@NotNull Map<String, Object> map, @NotNull String key, @Nullable Object value) {
|
||||
String[] fullKeyPath = key.split("\\.");
|
||||
|
||||
Map<String, ?> innerMap = getInnerMap(map, Arrays.copyOf(fullKeyPath, fullKeyPath.length - 1), true);
|
||||
|
||||
return ((Map<String, Object>) innerMap).put(fullKeyPath[fullKeyPath.length - 1], value);
|
||||
}
|
||||
|
||||
protected static Object getInnerValueForKey(@NotNull Map<String, Object> map, @NotNull String key) {
|
||||
String[] fullKeyPath = key.split("\\.");
|
||||
|
||||
Map<String, ?> innerMap = getInnerMap(map, Arrays.copyOf(fullKeyPath, fullKeyPath.length - 1), false);
|
||||
|
||||
if (innerMap != null) {
|
||||
return innerMap.get(fullKeyPath[fullKeyPath.length - 1]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Contract("_,_,true -> !null")
|
||||
protected static Map<String, ?> getInnerMap(@NotNull Map<String, ?> map, @NotNull String[] keys, boolean createMissingMaps) {
|
||||
if (keys.length == 0) {
|
||||
return map;
|
||||
}
|
||||
|
||||
int currentKeyIndex = 0;
|
||||
Map<String, ?> currentMap = map;
|
||||
|
||||
while (true) {
|
||||
Object currentValue = currentMap.get(keys[currentKeyIndex]);
|
||||
|
||||
if (currentValue == null) {
|
||||
if (!createMissingMaps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
currentValue = new HashMap<>();
|
||||
((Map<String, Object>) currentMap).put(keys[currentKeyIndex], currentValue);
|
||||
}
|
||||
|
||||
if (!(currentValue instanceof Map)) {
|
||||
if (!createMissingMaps) {
|
||||
throw new IllegalArgumentException("Expected a Map when resolving key '" + String.join(".", keys) + "' at '" + String.join(".", Arrays.copyOf(keys, currentKeyIndex + 1)) + "'");
|
||||
}
|
||||
|
||||
currentValue = new HashMap<>();
|
||||
((Map<String, Object>) currentMap).put(keys[currentKeyIndex], currentValue);
|
||||
}
|
||||
|
||||
if (currentKeyIndex == keys.length - 1) {
|
||||
return (Map<String, ?>) currentValue;
|
||||
}
|
||||
|
||||
currentMap = (Map<String, ?>) currentValue;
|
||||
++currentKeyIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This takes a map and removes all keys that have a value of null.<br>
|
||||
* Additionally, if the value is a {@link Map}, it will be recursively cleaned too.<br>
|
||||
* {@link Map}s that are or get empty, will be removed (recursively).<br>
|
||||
*/
|
||||
protected void cleanValuesMap(Map<?, ?> map) {
|
||||
for (Object key : map.keySet().toArray()) {
|
||||
Object value = map.get(key);
|
||||
|
||||
if (value instanceof Map) {
|
||||
cleanValuesMap((Map<?, ?>) value);
|
||||
}
|
||||
|
||||
if (value == null || (value instanceof Map && ((Map<?, ?>) value).isEmpty())) {
|
||||
map.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,64 +1,64 @@
|
||||
//package com.songoda.core.core;
|
||||
//
|
||||
//import com.songoda.core.locale.Locale;
|
||||
//import org.json.simple.JSONArray;
|
||||
//import org.json.simple.JSONObject;
|
||||
//
|
||||
//import java.io.IOException;
|
||||
//import java.net.HttpURLConnection;
|
||||
//import java.net.URL;
|
||||
//import java.util.logging.Level;
|
||||
//import java.util.logging.Logger;
|
||||
//
|
||||
//public class LocaleModule implements PluginInfoModule {
|
||||
// @Override
|
||||
// public void run(PluginInfo plugin) {
|
||||
// if (plugin.getJavaPlugin() == null || plugin.getSongodaId() <= 0) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// JSONObject json = plugin.getJson();
|
||||
// JSONArray files = (JSONArray) json.get("neededFiles");
|
||||
//
|
||||
// for (Object o : files) {
|
||||
// JSONObject file = (JSONObject) o;
|
||||
//
|
||||
// if (file.get("type").equals("locale")) {
|
||||
// downloadLocale(plugin, (String) file.get("link"), (String) file.get("name"));
|
||||
// }
|
||||
// }
|
||||
// } catch (IOException ex) {
|
||||
// Logger.getLogger(LocaleModule.class.getName()).log(Level.INFO, "Failed to check for locale files: " + ex.getMessage());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// void downloadLocale(PluginInfo plugin, String link, String fileName) throws IOException {
|
||||
// URL url = new URL(link);
|
||||
//
|
||||
// HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||
// urlConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11");
|
||||
// urlConnection.setRequestProperty("Accept", "*/*");
|
||||
// urlConnection.setInstanceFollowRedirects(true);
|
||||
// urlConnection.setConnectTimeout(5000);
|
||||
//
|
||||
// // do we need to follow a redirect?
|
||||
// int status = urlConnection.getResponseCode();
|
||||
// if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM || status == HttpURLConnection.HTTP_SEE_OTHER) {
|
||||
// // get redirect url from "location" header field
|
||||
// String newUrl = urlConnection.getHeaderField("Location");
|
||||
// // get the cookie if needed
|
||||
// String cookies = urlConnection.getHeaderField("Set-Cookie");
|
||||
// // open the new connnection again
|
||||
// urlConnection = (HttpURLConnection) new URL(newUrl).openConnection();
|
||||
// urlConnection.setRequestProperty("Cookie", cookies);
|
||||
// urlConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11");
|
||||
// urlConnection.setRequestProperty("Accept", "*/*");
|
||||
// urlConnection.setConnectTimeout(5000);
|
||||
// }
|
||||
//
|
||||
// Locale.saveLocale(plugin.getJavaPlugin(), urlConnection.getInputStream(), fileName);
|
||||
//
|
||||
// urlConnection.disconnect();
|
||||
// }
|
||||
//}
|
||||
package com.songoda.core.core;
|
||||
|
||||
import com.songoda.core.locale.Locale;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class LocaleModule implements PluginInfoModule {
|
||||
@Override
|
||||
public void run(PluginInfo plugin) {
|
||||
if (plugin.getJavaPlugin() == null || plugin.getSongodaId() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject json = plugin.getJson();
|
||||
JSONArray files = (JSONArray) json.get("neededFiles");
|
||||
|
||||
for (Object o : files) {
|
||||
JSONObject file = (JSONObject) o;
|
||||
|
||||
if (file.get("type").equals("locale")) {
|
||||
downloadLocale(plugin, (String) file.get("link"), (String) file.get("name"));
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(LocaleModule.class.getName()).log(Level.INFO, "Failed to check for locale files: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
void downloadLocale(PluginInfo plugin, String link, String fileName) throws IOException {
|
||||
URL url = new URL(link);
|
||||
|
||||
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||
urlConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11");
|
||||
urlConnection.setRequestProperty("Accept", "*/*");
|
||||
urlConnection.setInstanceFollowRedirects(true);
|
||||
urlConnection.setConnectTimeout(5000);
|
||||
|
||||
// do we need to follow a redirect?
|
||||
int status = urlConnection.getResponseCode();
|
||||
if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM || status == HttpURLConnection.HTTP_SEE_OTHER) {
|
||||
// get redirect url from "location" header field
|
||||
String newUrl = urlConnection.getHeaderField("Location");
|
||||
// get the cookie if needed
|
||||
String cookies = urlConnection.getHeaderField("Set-Cookie");
|
||||
// open the new connnection again
|
||||
urlConnection = (HttpURLConnection) new URL(newUrl).openConnection();
|
||||
urlConnection.setRequestProperty("Cookie", cookies);
|
||||
urlConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11");
|
||||
urlConnection.setRequestProperty("Accept", "*/*");
|
||||
urlConnection.setConnectTimeout(5000);
|
||||
}
|
||||
|
||||
Locale.saveLocale(plugin.getJavaPlugin(), urlConnection.getInputStream(), fileName);
|
||||
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ package com.songoda.core.gui;
|
||||
|
||||
import com.songoda.core.compatibility.CompatibleMaterial;
|
||||
import com.songoda.core.compatibility.ServerVersion;
|
||||
import com.songoda.core.configuration.ConfigEntry;
|
||||
import com.songoda.core.configuration.songoda.SongodaYamlConfig;
|
||||
import com.songoda.core.configuration.Config;
|
||||
import com.songoda.core.configuration.ConfigSection;
|
||||
import com.songoda.core.gui.methods.Clickable;
|
||||
import com.songoda.core.utils.TextUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
@ -16,7 +16,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@ -46,89 +45,60 @@ public class CustomizableGui extends Gui {
|
||||
localeFolder.mkdir();
|
||||
}
|
||||
|
||||
SongodaYamlConfig config = new SongodaYamlConfig(new File(plugin.getDataFolder(), "gui/" + guiKey + ".yml"));
|
||||
try {
|
||||
config.load();
|
||||
} catch (IOException ex) {
|
||||
// FIXME
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
Config config = new Config(plugin, "gui/" + guiKey + ".yml");
|
||||
config.load();
|
||||
|
||||
config.setNodeComment("overrides", "For information on how to apply overrides please visit\n" +
|
||||
"https://wiki.craftaro.com/index.php/Gui");
|
||||
config.setNodeComment("overrides.example", "This is just an example and does not override to any items in this GUI.");
|
||||
|
||||
config.createEntry("overrides.example.item", CompatibleMaterial.STONE)
|
||||
.withComment("This is the icon material you would like to replace\n" +
|
||||
"the current material with.");
|
||||
config.createEntry("overrides.example.position", 5)
|
||||
.withComment("This is the current position of the icon you would like to move.\n" +
|
||||
"The number represents the cell the icon currently resides in.");
|
||||
config.saveChanges();
|
||||
}
|
||||
|
||||
ConfigEntry disabledGuis = config.createEntry("disabled", Arrays.asList("example3", "example4", "example5"))
|
||||
.withComment("All keys on this list will be disabled. You can add any items key here\n" +
|
||||
"if you no longer want that item in the GUI.");
|
||||
if (!config.isConfigurationSection("disabled")) {
|
||||
config.setDefault("disabled", Arrays.asList("example3", "example4", "example5"),
|
||||
"All keys on this list will be disabled. You can add any items key here",
|
||||
"if you no longer want that item in the GUI.");
|
||||
|
||||
try {
|
||||
config.save();
|
||||
} catch (IOException ex) {
|
||||
// FIXME
|
||||
throw new RuntimeException(ex);
|
||||
config.saveChanges();
|
||||
}
|
||||
|
||||
CustomContent customContent = loadedGuis.computeIfAbsent(guiKey, g -> new CustomContent(guiKey));
|
||||
loadedGuis.put(guiKey, customContent);
|
||||
this.customContent = customContent;
|
||||
|
||||
int rows = config.getReadEntry("overrides.__ROWS__").getIntOr(-1);
|
||||
int rows = config.getInt("overrides.__ROWS__", -1);
|
||||
if (rows != -1) {
|
||||
customContent.setRows(rows);
|
||||
}
|
||||
|
||||
for (String overrideKey : config.getKeys("overrides")) {
|
||||
String keyPrefix = "overrides." + overrideKey;
|
||||
|
||||
ConfigEntry title = config.getReadEntry(keyPrefix + ".title");
|
||||
|
||||
ConfigEntry position = config.getReadEntry(keyPrefix + ".position");
|
||||
|
||||
ConfigEntry row = config.getReadEntry(keyPrefix + ".row");
|
||||
ConfigEntry col = config.getReadEntry(keyPrefix + ".col");
|
||||
|
||||
ConfigEntry mirrorRow = config.getReadEntry(keyPrefix + ".mirrorrow");
|
||||
ConfigEntry mirrorCol = config.getReadEntry(keyPrefix + ".mirrorcol");
|
||||
|
||||
ConfigEntry item = config.getReadEntry(keyPrefix + ".item");
|
||||
ConfigEntry lore = config.getReadEntry(keyPrefix + ".lore");
|
||||
|
||||
boolean configHasRowOrColSet = row.has() || col.has();
|
||||
boolean configHasMirrorRowOrColSet = mirrorRow.has() || mirrorCol.has();
|
||||
|
||||
if (configHasMirrorRowOrColSet) {
|
||||
customContent.addButton(overrideKey,
|
||||
row.getIntOr(-1),
|
||||
col.getIntOr(-1),
|
||||
mirrorRow.getBoolean(),
|
||||
mirrorCol.getBoolean(),
|
||||
item.getMaterial());
|
||||
} else if (configHasRowOrColSet) {
|
||||
customContent.addButton(overrideKey,
|
||||
row.getIntOr(-1),
|
||||
col.getIntOr(-1),
|
||||
title.getString(),
|
||||
lore.getStringList(),
|
||||
item.getMaterial());
|
||||
for (ConfigSection section : config.getSections("overrides")) {
|
||||
if (section.contains("row") ||
|
||||
section.contains("col") ||
|
||||
section.contains("mirrorrow") ||
|
||||
section.contains("mirrorcol")) {
|
||||
if (section.contains("mirrorrow") || section.contains("mirrorcol")) {
|
||||
customContent.addButton(section.getNodeKey(), section.getInt("row", -1),
|
||||
section.getInt("col", -1),
|
||||
section.getBoolean("mirrorrow", false),
|
||||
section.getBoolean("mirrorcol", false),
|
||||
section.isSet("item") ? CompatibleMaterial.getMaterial(section.getString("item")) : null);
|
||||
} else {
|
||||
customContent.addButton(section.getNodeKey(), section.getInt("row", -1),
|
||||
section.getInt("col", -1),
|
||||
section.getString("title", null),
|
||||
section.isSet("lore") ? section.getStringList("lore") : null,
|
||||
section.isSet("item") ? CompatibleMaterial.getMaterial(section.getString("item")) : null);
|
||||
}
|
||||
} else {
|
||||
customContent.addButton(overrideKey,
|
||||
position.getStringOr("-1"),
|
||||
title.getString(),
|
||||
lore.getStringList(),
|
||||
item.getMaterial());
|
||||
|
||||
customContent.addButton(section.getNodeKey(), section.getString("position", "-1"),
|
||||
section.getString("title", null),
|
||||
section.isSet("lore") ? section.getStringList("lore") : null,
|
||||
section.isSet("item") ? CompatibleMaterial.getMaterial(section.getString("item")) : null);
|
||||
}
|
||||
}
|
||||
|
||||
for (String disabled : disabledGuis.getStringListOr(Collections.emptyList())) {
|
||||
for (String disabled : config.getStringList("disabled")) {
|
||||
customContent.disableButton(disabled);
|
||||
}
|
||||
} else {
|
||||
|
@ -1,496 +1,495 @@
|
||||
//
|
||||
//package com.songoda.core.locale;
|
||||
//
|
||||
//import com.songoda.core.configuration.Config;
|
||||
//import com.songoda.core.configuration.ConfigSection;
|
||||
//import com.songoda.core.utils.TextUtils;
|
||||
//import org.bukkit.configuration.InvalidConfigurationException;
|
||||
//import org.bukkit.plugin.Plugin;
|
||||
//import org.bukkit.plugin.java.JavaPlugin;
|
||||
//
|
||||
//import java.io.BufferedInputStream;
|
||||
//import java.io.BufferedReader;
|
||||
//import java.io.ByteArrayInputStream;
|
||||
//import java.io.File;
|
||||
//import java.io.FileInputStream;
|
||||
//import java.io.FileOutputStream;
|
||||
//import java.io.IOException;
|
||||
//import java.io.InputStream;
|
||||
//import java.io.InputStreamReader;
|
||||
//import java.io.OutputStream;
|
||||
//import java.nio.charset.Charset;
|
||||
//import java.nio.charset.StandardCharsets;
|
||||
//import java.util.ArrayList;
|
||||
//import java.util.HashMap;
|
||||
//import java.util.List;
|
||||
//import java.util.Map;
|
||||
//import java.util.logging.Level;
|
||||
//import java.util.logging.Logger;
|
||||
//import java.util.regex.Matcher;
|
||||
//import java.util.regex.Pattern;
|
||||
//import java.util.stream.Collectors;
|
||||
//
|
||||
///**
|
||||
// * Assists in the utilization of localization files.
|
||||
// */
|
||||
//public class Locale {
|
||||
// private static final Pattern OLD_NODE_PATTERN = Pattern.compile("^([^ ]+)\\s*=\\s*\"?(.*?)\"?$");
|
||||
// private static final String FILE_EXTENSION = ".lang";
|
||||
//
|
||||
// private final Map<String, String> nodes = new HashMap<>();
|
||||
// private final Plugin plugin;
|
||||
// private final File file;
|
||||
// private final String name;
|
||||
//
|
||||
// /**
|
||||
// * Instantiate the Locale class for future use
|
||||
// *
|
||||
// * @param plugin Owning Plugin
|
||||
// * @param file Location of the locale file
|
||||
// * @param name The locale name for the language
|
||||
// */
|
||||
// public Locale(Plugin plugin, File file, String name) {
|
||||
// this.plugin = plugin;
|
||||
// this.file = file;
|
||||
// this.name = name;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Load a default-included lang file from the plugin's jar file
|
||||
// *
|
||||
// * @param plugin plugin to load from
|
||||
// * @param name name of the default locale, eg "en_US"
|
||||
// *
|
||||
// * @return returns the loaded Locale, or null if there was an error
|
||||
// */
|
||||
// public static Locale loadDefaultLocale(JavaPlugin plugin, String name) {
|
||||
// saveDefaultLocale(plugin, name, name);
|
||||
//
|
||||
// return loadLocale(plugin, name);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Load a locale from this plugin's locale directory
|
||||
// *
|
||||
// * @param plugin plugin to load from
|
||||
// * @param name name of the locale, eg "en_US"
|
||||
// *
|
||||
// * @return returns the loaded Locale, or null if there was an error
|
||||
// */
|
||||
// public static Locale loadLocale(JavaPlugin plugin, String name) {
|
||||
// File localeFolder = new File(plugin.getDataFolder(), "locales/");
|
||||
// if (!localeFolder.exists()) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// File localeFile = new File(localeFolder, name + FILE_EXTENSION);
|
||||
// if (!localeFolder.exists()) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// // found the lang file, now load it in!
|
||||
// Locale l = new Locale(plugin, localeFile, name);
|
||||
//
|
||||
// if (!l.reloadMessages()) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// plugin.getLogger().info("Loaded locale \"" + name + "\"");
|
||||
//
|
||||
// return l;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Load all locales from this plugin's locale directory
|
||||
// *
|
||||
// * @param plugin plugin to load from
|
||||
// *
|
||||
// * @return returns the loaded Locales
|
||||
// */
|
||||
// public static List<Locale> loadAllLocales(JavaPlugin plugin) {
|
||||
// File localeFolder = new File(plugin.getDataFolder(), "locales/");
|
||||
// List<Locale> all = new ArrayList<>();
|
||||
//
|
||||
// for (File localeFile : localeFolder.listFiles()) {
|
||||
// String fileName = localeFile.getName();
|
||||
// if (!fileName.endsWith(FILE_EXTENSION)) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// fileName = fileName.substring(0, fileName.lastIndexOf('.'));
|
||||
// if (fileName.split("_").length != 2) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// Locale l = new Locale(plugin, localeFile, fileName);
|
||||
//
|
||||
// if (l.reloadMessages()) {
|
||||
// plugin.getLogger().info("Loaded locale \"" + fileName + "\"");
|
||||
// all.add(l);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return all;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Get a list of all locale files in this plugin's locale directory
|
||||
// *
|
||||
// * @param plugin Plugin to check for
|
||||
// */
|
||||
// public static List<String> getLocales(Plugin plugin) {
|
||||
// File localeFolder = new File(plugin.getDataFolder(), "locales/");
|
||||
// List<String> all = new ArrayList<>();
|
||||
//
|
||||
// for (File localeFile : localeFolder.listFiles()) {
|
||||
// String fileName = localeFile.getName();
|
||||
// if (!fileName.endsWith(FILE_EXTENSION)) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// fileName = fileName.substring(0, fileName.lastIndexOf('.'));
|
||||
//
|
||||
// if (fileName.split("_").length != 2) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// all.add(fileName);
|
||||
// }
|
||||
//
|
||||
// return all;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Save a locale file from the Plugin's Resources to the locale folder
|
||||
// *
|
||||
// * @param plugin plugin owning the locale file
|
||||
// * @param locale the specific locale file to save
|
||||
// * @param fileName where to save the file
|
||||
// *
|
||||
// * @return true if the operation was successful, false otherwise
|
||||
// */
|
||||
// public static boolean saveDefaultLocale(JavaPlugin plugin, String locale, String fileName) {
|
||||
// return saveLocale(plugin, plugin.getResource(locale + FILE_EXTENSION), fileName, true);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Save a locale file from an InputStream to the locale folder
|
||||
// *
|
||||
// * @param plugin plugin owning the locale file
|
||||
// * @param in file to save
|
||||
// * @param fileName the name of the file to save
|
||||
// *
|
||||
// * @return true if the operation was successful, false otherwise
|
||||
// */
|
||||
// public static boolean saveLocale(Plugin plugin, InputStream in, String fileName) {
|
||||
// return saveLocale(plugin, in, fileName, false);
|
||||
// }
|
||||
//
|
||||
// private static boolean saveLocale(Plugin plugin, InputStream in, String fileName, boolean builtin) {
|
||||
// if (in == null) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// File localeFolder = new File(plugin.getDataFolder(), "locales/");
|
||||
// if (!localeFolder.exists()) localeFolder.mkdirs();
|
||||
//
|
||||
// if (!fileName.endsWith(FILE_EXTENSION)) {
|
||||
// fileName = fileName + FILE_EXTENSION;
|
||||
// }
|
||||
//
|
||||
// File destinationFile = new File(localeFolder, fileName);
|
||||
// if (destinationFile.exists()) {
|
||||
// return updateFiles(plugin, in, destinationFile, builtin);
|
||||
// }
|
||||
//
|
||||
// try (OutputStream outputStream = new FileOutputStream(destinationFile)) {
|
||||
// copy(in, outputStream);
|
||||
//
|
||||
// fileName = fileName.substring(0, fileName.lastIndexOf('.'));
|
||||
//
|
||||
// return fileName.split("_").length == 2;
|
||||
// } catch (IOException ignore) {
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // Write new changes to existing files, if any at all
|
||||
// private static boolean updateFiles(Plugin plugin, InputStream defaultFile, File existingFile, boolean builtin) {
|
||||
// try (BufferedInputStream defaultIn = new BufferedInputStream(defaultFile);
|
||||
// BufferedInputStream existingIn = new BufferedInputStream(new FileInputStream(existingFile))) {
|
||||
//
|
||||
// Charset defaultCharset = TextUtils.detectCharset(defaultIn, StandardCharsets.UTF_8);
|
||||
// Charset existingCharset = TextUtils.detectCharset(existingIn, StandardCharsets.UTF_8);
|
||||
//
|
||||
// try (BufferedReader defaultReaderOriginal = new BufferedReader(new InputStreamReader(defaultIn, defaultCharset));
|
||||
// BufferedReader existingReaderOriginal = new BufferedReader(new InputStreamReader(existingIn, existingCharset));
|
||||
// BufferedReader defaultReader = translatePropertyToYAML(defaultReaderOriginal, defaultCharset);
|
||||
// BufferedReader existingReader = translatePropertyToYAML(existingReaderOriginal, existingCharset)) {
|
||||
//
|
||||
// Config existingLang = new Config(existingFile);
|
||||
// existingLang.load(existingReader);
|
||||
// translateMsgRoot(existingLang, existingFile, existingCharset);
|
||||
//
|
||||
// Config defaultLang = new Config();
|
||||
// String defaultData = defaultReader.lines().map(s -> s.replaceAll("[\uFEFF\uFFFE\u200B]", "")).collect(Collectors.joining("\n"));
|
||||
// defaultLang.loadFromString(defaultData);
|
||||
// translateMsgRoot(defaultLang, defaultData, defaultCharset);
|
||||
//
|
||||
// List<String> added = new ArrayList<>();
|
||||
//
|
||||
// for (String defaultValueKey : defaultLang.getKeys(true)) {
|
||||
// Object val = defaultLang.get(defaultValueKey);
|
||||
// if (val instanceof ConfigSection) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// if (!existingLang.contains(defaultValueKey)) {
|
||||
// added.add(defaultValueKey);
|
||||
// existingLang.set(defaultValueKey, val);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (!added.isEmpty()) {
|
||||
// if (!builtin) {
|
||||
// existingLang.setHeader("New messages added for " + plugin.getName() + " v" + plugin.getDescription().getVersion() + ".",
|
||||
// "",
|
||||
// "These translations were found untranslated, join",
|
||||
// "our translation Discord https://discord.gg/f7fpZEf",
|
||||
// "to request an official update!",
|
||||
// "",
|
||||
// String.join("\n", added)
|
||||
// );
|
||||
// } else {
|
||||
// existingLang.setHeader("New messages added for " + plugin.getName() + " v" + plugin.getDescription().getVersion() + ".",
|
||||
// "",
|
||||
// String.join("\n", added)
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// existingLang.setRootNodeSpacing(0);
|
||||
// existingLang.save();
|
||||
// }
|
||||
//
|
||||
// existingLang.setRootNodeSpacing(0);
|
||||
// existingLang.save();
|
||||
//
|
||||
// return !added.isEmpty();
|
||||
// } catch (InvalidConfigurationException ex) {
|
||||
// plugin.getLogger().log(Level.SEVERE, "Error checking config " + existingFile.getName(), ex);
|
||||
// }
|
||||
// } catch (IOException ignore) {
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Clear the previous message cache and load new messages directly from file
|
||||
// *
|
||||
// * @return reload messages from file
|
||||
// */
|
||||
// public boolean reloadMessages() {
|
||||
// if (!this.file.exists()) {
|
||||
// plugin.getLogger().warning("Could not find file for locale \"" + this.name + "\"");
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// this.nodes.clear(); // Clear previous data (if any)
|
||||
//
|
||||
// // guess what encoding this file is in
|
||||
// Charset charset = TextUtils.detectCharset(file, null);
|
||||
// if (charset == null) {
|
||||
// plugin.getLogger().warning("Could not determine charset for locale \"" + this.name + "\"");
|
||||
// charset = StandardCharsets.UTF_8;
|
||||
// }
|
||||
//
|
||||
// // load in the file!
|
||||
// try (FileInputStream stream = new FileInputStream(file);
|
||||
// BufferedReader source = new BufferedReader(new InputStreamReader(stream, charset));
|
||||
// BufferedReader reader = translatePropertyToYAML(source, charset)) {
|
||||
// Config lang = new Config(file);
|
||||
// lang.load(reader);
|
||||
// translateMsgRoot(lang, file, charset);
|
||||
//
|
||||
// // load lists as strings with newlines
|
||||
// lang.getValues(true).forEach((k, v) -> nodes.put(k,
|
||||
// v instanceof List
|
||||
// ? (((List<?>) v).stream().map(Object::toString).collect(Collectors.joining("\n")))
|
||||
// : v.toString()));
|
||||
//
|
||||
// return true;
|
||||
// } catch (IOException ex) {
|
||||
// ex.printStackTrace();
|
||||
// } catch (InvalidConfigurationException ex) {
|
||||
// Logger.getLogger(Locale.class.getName()).log(Level.SEVERE, "Configuration error in language file \"" + file.getName() + "\"", ex);
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// protected static BufferedReader translatePropertyToYAML(BufferedReader source, Charset charset) throws IOException {
|
||||
// StringBuilder output = new StringBuilder();
|
||||
//
|
||||
// String line, line1;
|
||||
// for (int lineNumber = 0; (line = source.readLine()) != null; ++lineNumber) {
|
||||
// if (lineNumber == 0) {
|
||||
// // remove BOM markers, if any
|
||||
// line1 = line;
|
||||
// line = line.replaceAll("[\uFEFF\uFFFE\u200B]", "");
|
||||
// if (line1.length() != line.length()) {
|
||||
// output.append(line1, 0, line1.length() - line.length());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Matcher matcher;
|
||||
// if ((line = line.replace('\r', ' ')
|
||||
// .replaceAll("\\p{C}", "?")
|
||||
// .replace(";", "")).trim().isEmpty()
|
||||
// || line.trim().startsWith("#") /* Comment */
|
||||
// // need to trim the search group because tab characters somehow ended up at the end of lines in a lot of these files
|
||||
// || !(matcher = OLD_NODE_PATTERN.matcher(line.trim())).find()) {
|
||||
// if (line.startsWith("//")) {
|
||||
// // someone used an improper comment in some files *grumble grumble*
|
||||
// output.append("#").append(line).append("\n");
|
||||
// } else {
|
||||
// output.append(line).append("\n");
|
||||
// }
|
||||
// } else {
|
||||
// output.append(matcher.group(1)).append(": \"").append(matcher.group(2)).append("\"\n");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // I hate Java sometimes because of crap like this:
|
||||
// return new BufferedReader(new InputStreamReader(new BufferedInputStream(new ByteArrayInputStream(output.toString().getBytes(charset))), charset));
|
||||
// }
|
||||
//
|
||||
// protected static void translateMsgRoot(Config lang, File file, Charset charset) throws IOException {
|
||||
// List<String> msgs = lang.getValues(true).entrySet().stream()
|
||||
// .filter(e -> e.getValue() instanceof ConfigSection)
|
||||
// .map(Map.Entry::getKey)
|
||||
// .collect(Collectors.toList());
|
||||
//
|
||||
// if (!msgs.isEmpty()) {
|
||||
// try (FileInputStream stream = new FileInputStream(file);
|
||||
// BufferedReader source = new BufferedReader(new InputStreamReader(stream, charset))) {
|
||||
// String line;
|
||||
// for (int lineNumber = 0; (line = source.readLine()) != null; ++lineNumber) {
|
||||
// if (lineNumber == 0) {
|
||||
// // remove BOM markers, if any
|
||||
// line = line.replaceAll("[\uFEFF\uFFFE\u200B]", "");
|
||||
// }
|
||||
//
|
||||
// Matcher matcher;
|
||||
// if (!(line = line.trim()).isEmpty() && !line.startsWith("#")
|
||||
// && (matcher = OLD_NODE_PATTERN.matcher(line)).find()
|
||||
// && msgs.contains(matcher.group(1))) {
|
||||
// lang.set(matcher.group(1) + ".message", matcher.group(2));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// protected static void translateMsgRoot(Config lang, String file, Charset charset) throws IOException {
|
||||
// List<String> msgs = lang.getValues(true).entrySet().stream()
|
||||
// .filter(e -> e.getValue() instanceof ConfigSection)
|
||||
// .map(Map.Entry::getKey)
|
||||
// .collect(Collectors.toList());
|
||||
//
|
||||
// if (!msgs.isEmpty()) {
|
||||
// String[] source = file.split("\n");
|
||||
//
|
||||
// String line;
|
||||
// for (int lineNumber = 0; lineNumber < source.length; ++lineNumber) {
|
||||
// line = source[lineNumber];
|
||||
// if (lineNumber == 0) {
|
||||
// // remove BOM markers, if any
|
||||
// line = line.replaceAll("[\uFEFF\uFFFE\u200B]", "");
|
||||
// }
|
||||
//
|
||||
// Matcher matcher;
|
||||
// if (!(line = line.trim()).isEmpty() && !line.startsWith("#")
|
||||
// && (matcher = OLD_NODE_PATTERN.matcher(line)).find()
|
||||
// && msgs.contains(matcher.group(1))) {
|
||||
// lang.set(matcher.group(1) + ".message", matcher.group(2));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Supply the Message object with the plugins prefix.
|
||||
// *
|
||||
// * @param message message to be applied
|
||||
// *
|
||||
// * @return applied message
|
||||
// */
|
||||
// private Message supplyPrefix(Message message) {
|
||||
// return message.setPrefix(this.nodes.getOrDefault("general.nametag.prefix", "[" + plugin.getName() + "]"));
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Create a new unsaved Message
|
||||
// *
|
||||
// * @param message the message to create
|
||||
// *
|
||||
// * @return the created message
|
||||
// */
|
||||
// public Message newMessage(String message) {
|
||||
// return supplyPrefix(new Message(message));
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Get a message set for a specific node.
|
||||
// *
|
||||
// * @param node the node to get
|
||||
// *
|
||||
// * @return the message for the specified node
|
||||
// */
|
||||
// public Message getMessage(String node) {
|
||||
// if (this.nodes.containsKey(node + ".message")) {
|
||||
// node += ".message";
|
||||
// }
|
||||
//
|
||||
// return this.getMessageOrDefault(node, node);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Get a message set for a specific node
|
||||
// *
|
||||
// * @param node the node to get
|
||||
// * @param defaultValue the default value given that a value for the node was not found
|
||||
// *
|
||||
// * @return the message for the specified node. Default if none found
|
||||
// */
|
||||
// public Message getMessageOrDefault(String node, String defaultValue) {
|
||||
// if (this.nodes.containsKey(node + ".message")) {
|
||||
// node += ".message";
|
||||
// }
|
||||
//
|
||||
// return supplyPrefix(new Message(this.nodes.getOrDefault(node, defaultValue)));
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Return the locale name (i.e. "en_US")
|
||||
// *
|
||||
// * @return the locale name
|
||||
// */
|
||||
// public String getName() {
|
||||
// return name;
|
||||
// }
|
||||
//
|
||||
// private static void copy(InputStream input, OutputStream output) {
|
||||
// int n;
|
||||
// byte[] buffer = new byte[1024 * 4];
|
||||
//
|
||||
// try {
|
||||
// while ((n = input.read(buffer)) != -1) {
|
||||
// output.write(buffer, 0, n);
|
||||
// }
|
||||
// } catch (IOException ex) {
|
||||
// ex.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
package com.songoda.core.locale;
|
||||
|
||||
import com.songoda.core.configuration.Config;
|
||||
import com.songoda.core.configuration.ConfigSection;
|
||||
import com.songoda.core.utils.TextUtils;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Assists in the utilization of localization files.
|
||||
*/
|
||||
public class Locale {
|
||||
private static final Pattern OLD_NODE_PATTERN = Pattern.compile("^([^ ]+)\\s*=\\s*\"?(.*?)\"?$");
|
||||
private static final String FILE_EXTENSION = ".lang";
|
||||
|
||||
private final Map<String, String> nodes = new HashMap<>();
|
||||
private final Plugin plugin;
|
||||
private final File file;
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Instantiate the Locale class for future use
|
||||
*
|
||||
* @param plugin Owning Plugin
|
||||
* @param file Location of the locale file
|
||||
* @param name The locale name for the language
|
||||
*/
|
||||
public Locale(Plugin plugin, File file, String name) {
|
||||
this.plugin = plugin;
|
||||
this.file = file;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a default-included lang file from the plugin's jar file
|
||||
*
|
||||
* @param plugin plugin to load from
|
||||
* @param name name of the default locale, eg "en_US"
|
||||
*
|
||||
* @return returns the loaded Locale, or null if there was an error
|
||||
*/
|
||||
public static Locale loadDefaultLocale(JavaPlugin plugin, String name) {
|
||||
saveDefaultLocale(plugin, name, name);
|
||||
|
||||
return loadLocale(plugin, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a locale from this plugin's locale directory
|
||||
*
|
||||
* @param plugin plugin to load from
|
||||
* @param name name of the locale, eg "en_US"
|
||||
*
|
||||
* @return returns the loaded Locale, or null if there was an error
|
||||
*/
|
||||
public static Locale loadLocale(JavaPlugin plugin, String name) {
|
||||
File localeFolder = new File(plugin.getDataFolder(), "locales/");
|
||||
if (!localeFolder.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
File localeFile = new File(localeFolder, name + FILE_EXTENSION);
|
||||
if (!localeFolder.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// found the lang file, now load it in!
|
||||
Locale l = new Locale(plugin, localeFile, name);
|
||||
|
||||
if (!l.reloadMessages()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
plugin.getLogger().info("Loaded locale \"" + name + "\"");
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all locales from this plugin's locale directory
|
||||
*
|
||||
* @param plugin plugin to load from
|
||||
*
|
||||
* @return returns the loaded Locales
|
||||
*/
|
||||
public static List<Locale> loadAllLocales(JavaPlugin plugin) {
|
||||
File localeFolder = new File(plugin.getDataFolder(), "locales/");
|
||||
List<Locale> all = new ArrayList<>();
|
||||
|
||||
for (File localeFile : localeFolder.listFiles()) {
|
||||
String fileName = localeFile.getName();
|
||||
if (!fileName.endsWith(FILE_EXTENSION)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fileName = fileName.substring(0, fileName.lastIndexOf('.'));
|
||||
if (fileName.split("_").length != 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Locale l = new Locale(plugin, localeFile, fileName);
|
||||
|
||||
if (l.reloadMessages()) {
|
||||
plugin.getLogger().info("Loaded locale \"" + fileName + "\"");
|
||||
all.add(l);
|
||||
}
|
||||
}
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all locale files in this plugin's locale directory
|
||||
*
|
||||
* @param plugin Plugin to check for
|
||||
*/
|
||||
public static List<String> getLocales(Plugin plugin) {
|
||||
File localeFolder = new File(plugin.getDataFolder(), "locales/");
|
||||
List<String> all = new ArrayList<>();
|
||||
|
||||
for (File localeFile : localeFolder.listFiles()) {
|
||||
String fileName = localeFile.getName();
|
||||
if (!fileName.endsWith(FILE_EXTENSION)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fileName = fileName.substring(0, fileName.lastIndexOf('.'));
|
||||
|
||||
if (fileName.split("_").length != 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
all.add(fileName);
|
||||
}
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a locale file from the Plugin's Resources to the locale folder
|
||||
*
|
||||
* @param plugin plugin owning the locale file
|
||||
* @param locale the specific locale file to save
|
||||
* @param fileName where to save the file
|
||||
*
|
||||
* @return true if the operation was successful, false otherwise
|
||||
*/
|
||||
public static boolean saveDefaultLocale(JavaPlugin plugin, String locale, String fileName) {
|
||||
return saveLocale(plugin, plugin.getResource(locale + FILE_EXTENSION), fileName, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a locale file from an InputStream to the locale folder
|
||||
*
|
||||
* @param plugin plugin owning the locale file
|
||||
* @param in file to save
|
||||
* @param fileName the name of the file to save
|
||||
*
|
||||
* @return true if the operation was successful, false otherwise
|
||||
*/
|
||||
public static boolean saveLocale(Plugin plugin, InputStream in, String fileName) {
|
||||
return saveLocale(plugin, in, fileName, false);
|
||||
}
|
||||
|
||||
private static boolean saveLocale(Plugin plugin, InputStream in, String fileName, boolean builtin) {
|
||||
if (in == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
File localeFolder = new File(plugin.getDataFolder(), "locales/");
|
||||
if (!localeFolder.exists()) localeFolder.mkdirs();
|
||||
|
||||
if (!fileName.endsWith(FILE_EXTENSION)) {
|
||||
fileName = fileName + FILE_EXTENSION;
|
||||
}
|
||||
|
||||
File destinationFile = new File(localeFolder, fileName);
|
||||
if (destinationFile.exists()) {
|
||||
return updateFiles(plugin, in, destinationFile, builtin);
|
||||
}
|
||||
|
||||
try (OutputStream outputStream = new FileOutputStream(destinationFile)) {
|
||||
copy(in, outputStream);
|
||||
|
||||
fileName = fileName.substring(0, fileName.lastIndexOf('.'));
|
||||
|
||||
return fileName.split("_").length == 2;
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write new changes to existing files, if any at all
|
||||
private static boolean updateFiles(Plugin plugin, InputStream defaultFile, File existingFile, boolean builtin) {
|
||||
try (BufferedInputStream defaultIn = new BufferedInputStream(defaultFile);
|
||||
BufferedInputStream existingIn = new BufferedInputStream(new FileInputStream(existingFile))) {
|
||||
|
||||
Charset defaultCharset = TextUtils.detectCharset(defaultIn, StandardCharsets.UTF_8);
|
||||
Charset existingCharset = TextUtils.detectCharset(existingIn, StandardCharsets.UTF_8);
|
||||
|
||||
try (BufferedReader defaultReaderOriginal = new BufferedReader(new InputStreamReader(defaultIn, defaultCharset));
|
||||
BufferedReader existingReaderOriginal = new BufferedReader(new InputStreamReader(existingIn, existingCharset));
|
||||
BufferedReader defaultReader = translatePropertyToYAML(defaultReaderOriginal, defaultCharset);
|
||||
BufferedReader existingReader = translatePropertyToYAML(existingReaderOriginal, existingCharset)) {
|
||||
|
||||
Config existingLang = new Config(existingFile);
|
||||
existingLang.load(existingReader);
|
||||
translateMsgRoot(existingLang, existingFile, existingCharset);
|
||||
|
||||
Config defaultLang = new Config();
|
||||
String defaultData = defaultReader.lines().map(s -> s.replaceAll("[\uFEFF\uFFFE\u200B]", "")).collect(Collectors.joining("\n"));
|
||||
defaultLang.loadFromString(defaultData);
|
||||
translateMsgRoot(defaultLang, defaultData, defaultCharset);
|
||||
|
||||
List<String> added = new ArrayList<>();
|
||||
|
||||
for (String defaultValueKey : defaultLang.getKeys(true)) {
|
||||
Object val = defaultLang.get(defaultValueKey);
|
||||
if (val instanceof ConfigSection) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!existingLang.contains(defaultValueKey)) {
|
||||
added.add(defaultValueKey);
|
||||
existingLang.set(defaultValueKey, val);
|
||||
}
|
||||
}
|
||||
|
||||
if (!added.isEmpty()) {
|
||||
if (!builtin) {
|
||||
existingLang.setHeader("New messages added for " + plugin.getName() + " v" + plugin.getDescription().getVersion() + ".",
|
||||
"",
|
||||
"These translations were found untranslated, join",
|
||||
"our translation Discord https://discord.gg/f7fpZEf",
|
||||
"to request an official update!",
|
||||
"",
|
||||
String.join("\n", added)
|
||||
);
|
||||
} else {
|
||||
existingLang.setHeader("New messages added for " + plugin.getName() + " v" + plugin.getDescription().getVersion() + ".",
|
||||
"",
|
||||
String.join("\n", added)
|
||||
);
|
||||
}
|
||||
|
||||
existingLang.setRootNodeSpacing(0);
|
||||
existingLang.save();
|
||||
}
|
||||
|
||||
existingLang.setRootNodeSpacing(0);
|
||||
existingLang.save();
|
||||
|
||||
return !added.isEmpty();
|
||||
} catch (InvalidConfigurationException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Error checking config " + existingFile.getName(), ex);
|
||||
}
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the previous message cache and load new messages directly from file
|
||||
*
|
||||
* @return reload messages from file
|
||||
*/
|
||||
public boolean reloadMessages() {
|
||||
if (!this.file.exists()) {
|
||||
plugin.getLogger().warning("Could not find file for locale \"" + this.name + "\"");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.nodes.clear(); // Clear previous data (if any)
|
||||
|
||||
// guess what encoding this file is in
|
||||
Charset charset = TextUtils.detectCharset(file, null);
|
||||
if (charset == null) {
|
||||
plugin.getLogger().warning("Could not determine charset for locale \"" + this.name + "\"");
|
||||
charset = StandardCharsets.UTF_8;
|
||||
}
|
||||
|
||||
// load in the file!
|
||||
try (FileInputStream stream = new FileInputStream(file);
|
||||
BufferedReader source = new BufferedReader(new InputStreamReader(stream, charset));
|
||||
BufferedReader reader = translatePropertyToYAML(source, charset)) {
|
||||
Config lang = new Config(file);
|
||||
lang.load(reader);
|
||||
translateMsgRoot(lang, file, charset);
|
||||
|
||||
// load lists as strings with newlines
|
||||
lang.getValues(true).forEach((k, v) -> nodes.put(k,
|
||||
v instanceof List
|
||||
? (((List<?>) v).stream().map(Object::toString).collect(Collectors.joining("\n")))
|
||||
: v.toString()));
|
||||
|
||||
return true;
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
} catch (InvalidConfigurationException ex) {
|
||||
Logger.getLogger(Locale.class.getName()).log(Level.SEVERE, "Configuration error in language file \"" + file.getName() + "\"", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static BufferedReader translatePropertyToYAML(BufferedReader source, Charset charset) throws IOException {
|
||||
StringBuilder output = new StringBuilder();
|
||||
|
||||
String line, line1;
|
||||
for (int lineNumber = 0; (line = source.readLine()) != null; ++lineNumber) {
|
||||
if (lineNumber == 0) {
|
||||
// remove BOM markers, if any
|
||||
line1 = line;
|
||||
line = line.replaceAll("[\uFEFF\uFFFE\u200B]", "");
|
||||
if (line1.length() != line.length()) {
|
||||
output.append(line1, 0, line1.length() - line.length());
|
||||
}
|
||||
}
|
||||
|
||||
Matcher matcher;
|
||||
if ((line = line.replace('\r', ' ')
|
||||
.replaceAll("\\p{C}", "?")
|
||||
.replace(";", "")).trim().isEmpty()
|
||||
|| line.trim().startsWith("#") /* Comment */
|
||||
// need to trim the search group because tab characters somehow ended up at the end of lines in a lot of these files
|
||||
|| !(matcher = OLD_NODE_PATTERN.matcher(line.trim())).find()) {
|
||||
if (line.startsWith("//")) {
|
||||
// someone used an improper comment in some files *grumble grumble*
|
||||
output.append("#").append(line).append("\n");
|
||||
} else {
|
||||
output.append(line).append("\n");
|
||||
}
|
||||
} else {
|
||||
output.append(matcher.group(1)).append(": \"").append(matcher.group(2)).append("\"\n");
|
||||
}
|
||||
}
|
||||
|
||||
// I hate Java sometimes because of crap like this:
|
||||
return new BufferedReader(new InputStreamReader(new BufferedInputStream(new ByteArrayInputStream(output.toString().getBytes(charset))), charset));
|
||||
}
|
||||
|
||||
protected static void translateMsgRoot(Config lang, File file, Charset charset) throws IOException {
|
||||
List<String> msgs = lang.getValues(true).entrySet().stream()
|
||||
.filter(e -> e.getValue() instanceof ConfigSection)
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!msgs.isEmpty()) {
|
||||
try (FileInputStream stream = new FileInputStream(file);
|
||||
BufferedReader source = new BufferedReader(new InputStreamReader(stream, charset))) {
|
||||
String line;
|
||||
for (int lineNumber = 0; (line = source.readLine()) != null; ++lineNumber) {
|
||||
if (lineNumber == 0) {
|
||||
// remove BOM markers, if any
|
||||
line = line.replaceAll("[\uFEFF\uFFFE\u200B]", "");
|
||||
}
|
||||
|
||||
Matcher matcher;
|
||||
if (!(line = line.trim()).isEmpty() && !line.startsWith("#")
|
||||
&& (matcher = OLD_NODE_PATTERN.matcher(line)).find()
|
||||
&& msgs.contains(matcher.group(1))) {
|
||||
lang.set(matcher.group(1) + ".message", matcher.group(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static void translateMsgRoot(Config lang, String file, Charset charset) throws IOException {
|
||||
List<String> msgs = lang.getValues(true).entrySet().stream()
|
||||
.filter(e -> e.getValue() instanceof ConfigSection)
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!msgs.isEmpty()) {
|
||||
String[] source = file.split("\n");
|
||||
|
||||
String line;
|
||||
for (int lineNumber = 0; lineNumber < source.length; ++lineNumber) {
|
||||
line = source[lineNumber];
|
||||
if (lineNumber == 0) {
|
||||
// remove BOM markers, if any
|
||||
line = line.replaceAll("[\uFEFF\uFFFE\u200B]", "");
|
||||
}
|
||||
|
||||
Matcher matcher;
|
||||
if (!(line = line.trim()).isEmpty() && !line.startsWith("#")
|
||||
&& (matcher = OLD_NODE_PATTERN.matcher(line)).find()
|
||||
&& msgs.contains(matcher.group(1))) {
|
||||
lang.set(matcher.group(1) + ".message", matcher.group(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supply the Message object with the plugins prefix.
|
||||
*
|
||||
* @param message message to be applied
|
||||
*
|
||||
* @return applied message
|
||||
*/
|
||||
private Message supplyPrefix(Message message) {
|
||||
return message.setPrefix(this.nodes.getOrDefault("general.nametag.prefix", "[" + plugin.getName() + "]"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new unsaved Message
|
||||
*
|
||||
* @param message the message to create
|
||||
*
|
||||
* @return the created message
|
||||
*/
|
||||
public Message newMessage(String message) {
|
||||
return supplyPrefix(new Message(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a message set for a specific node.
|
||||
*
|
||||
* @param node the node to get
|
||||
*
|
||||
* @return the message for the specified node
|
||||
*/
|
||||
public Message getMessage(String node) {
|
||||
if (this.nodes.containsKey(node + ".message")) {
|
||||
node += ".message";
|
||||
}
|
||||
|
||||
return this.getMessageOrDefault(node, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a message set for a specific node
|
||||
*
|
||||
* @param node the node to get
|
||||
* @param defaultValue the default value given that a value for the node was not found
|
||||
*
|
||||
* @return the message for the specified node. Default if none found
|
||||
*/
|
||||
public Message getMessageOrDefault(String node, String defaultValue) {
|
||||
if (this.nodes.containsKey(node + ".message")) {
|
||||
node += ".message";
|
||||
}
|
||||
|
||||
return supplyPrefix(new Message(this.nodes.getOrDefault(node, defaultValue)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the locale name (i.e. "en_US")
|
||||
*
|
||||
* @return the locale name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
private static void copy(InputStream input, OutputStream output) {
|
||||
int n;
|
||||
byte[] buffer = new byte[1024 * 4];
|
||||
|
||||
try {
|
||||
while ((n = input.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, n);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,102 +0,0 @@
|
||||
package com.songoda.core.locale;
|
||||
|
||||
import com.songoda.core.http.HttpClient;
|
||||
import com.songoda.core.http.HttpResponse;
|
||||
import com.songoda.core.http.UnexpectedHttpStatusException;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.Writer;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class LocaleFileManager {
|
||||
private final HttpClient httpClient;
|
||||
private final String projectName;
|
||||
|
||||
public LocaleFileManager(HttpClient httpClient, String projectName) {
|
||||
this.httpClient = httpClient;
|
||||
this.projectName = projectName;
|
||||
}
|
||||
|
||||
public List<String> downloadMissingTranslations(File targetDirectory) throws IOException {
|
||||
List<String> availableLanguages = this.fetchAvailableLanguageFiles();
|
||||
if (availableLanguages == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Files.createDirectories(targetDirectory.toPath());
|
||||
|
||||
List<String> downloadedLocales = new LinkedList<>();
|
||||
for (String languageFileName : availableLanguages) {
|
||||
File languageFile = new File(targetDirectory, languageFileName);
|
||||
if (languageFile.exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String languageFileContents = fetchProjectFile(languageFileName);
|
||||
if (languageFileContents == null) {
|
||||
throw new IOException("Failed to download language file " + languageFileName); // TODO: Better exception
|
||||
}
|
||||
|
||||
try (Writer writer = Files.newBufferedWriter(languageFile.toPath(), StandardCharsets.UTF_8)) {
|
||||
writer.write(languageFileContents);
|
||||
}
|
||||
|
||||
downloadedLocales.add(languageFileName);
|
||||
}
|
||||
|
||||
return downloadedLocales;
|
||||
}
|
||||
|
||||
public @Nullable List<String> fetchAvailableLanguageFiles() throws IOException {
|
||||
String projectLanguageIndex = fetchProjectFile("_index.txt");
|
||||
|
||||
if (projectLanguageIndex == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> result = new LinkedList<>();
|
||||
|
||||
for (String line : projectLanguageIndex.split("\r?\n")) {
|
||||
line = line.trim();
|
||||
|
||||
if (!line.startsWith("#") && !line.isEmpty()) {
|
||||
result.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String fetchProjectFile(String fileName) throws IOException {
|
||||
String url = formatUrl("https://songoda.github.io/Translations/projects/%s/%s", this.projectName, fileName);
|
||||
HttpResponse httpResponse = this.httpClient.get(url);
|
||||
|
||||
if (httpResponse.getResponseCode() == 404) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (httpResponse.getResponseCode() != 200) {
|
||||
throw new UnexpectedHttpStatusException(httpResponse.getResponseCode(), url);
|
||||
}
|
||||
|
||||
return httpResponse.getBodyAsString();
|
||||
}
|
||||
|
||||
private static String formatUrl(String url, Object... params) throws UnsupportedEncodingException {
|
||||
Object[] encodedParams = new Object[params.length];
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
encodedParams[i] = URLEncoder.encode(params[i].toString(), "UTF-8");
|
||||
}
|
||||
|
||||
return String.format(url, encodedParams);
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
package com.songoda.core.locale;
|
||||
|
||||
import com.songoda.core.configuration.songoda.SongodaYamlConfig;
|
||||
import com.songoda.core.configuration.yaml.YamlConfiguration;
|
||||
import com.songoda.core.http.SimpleHttpClient;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class LocaleManager {
|
||||
protected final Plugin plugin;
|
||||
protected final File localesDirectory;
|
||||
|
||||
protected final List<SongodaYamlConfig> loadedLocales = new LinkedList<>();
|
||||
protected final @Nullable YamlConfiguration fallbackLocale;
|
||||
|
||||
public LocaleManager(Plugin plugin) throws IOException {
|
||||
this.plugin = plugin;
|
||||
this.localesDirectory = new File(this.plugin.getDataFolder(), "locales");
|
||||
|
||||
this.fallbackLocale = loadFallbackLocale();
|
||||
}
|
||||
|
||||
public List<String> downloadMissingLocales() {
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(new SimpleHttpClient(), this.plugin.getName());
|
||||
|
||||
try {
|
||||
return localeFileManager.downloadMissingTranslations(this.localesDirectory);
|
||||
} catch (IOException ex) {
|
||||
this.plugin.getLogger().warning("Failed to download missing locales: " + ex.getMessage());
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public void load(String locale) throws IOException {
|
||||
File fileToLoad = determineAvailableLocaleVariation(locale);
|
||||
if (fileToLoad == null) {
|
||||
throw new FileNotFoundException("Locale file " + locale + " not found");
|
||||
}
|
||||
|
||||
for (SongodaYamlConfig loadedLocale : this.loadedLocales) {
|
||||
if (loadedLocale.file.equals(fileToLoad)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SongodaYamlConfig localeConfig = new SongodaYamlConfig(fileToLoad);
|
||||
localeConfig.load();
|
||||
this.loadedLocales.add(localeConfig);
|
||||
}
|
||||
|
||||
public void loadExclusively(String locale) {
|
||||
loadExclusively(Collections.singletonList(locale));
|
||||
}
|
||||
|
||||
public void loadExclusively(List<String> locales) {
|
||||
unloadAll();
|
||||
}
|
||||
|
||||
public void unloadAll() {
|
||||
this.loadedLocales.clear();
|
||||
}
|
||||
|
||||
protected @Nullable File determineAvailableLocaleVariation(String locale) {
|
||||
File localeFile = new File(this.localesDirectory, locale + ".lang");
|
||||
if (localeFile.exists()) {
|
||||
return localeFile;
|
||||
}
|
||||
|
||||
File[] availableLocales = this.localesDirectory.listFiles();
|
||||
if (availableLocales == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (File availableLocale : availableLocales) {
|
||||
if (availableLocale.getName().startsWith(locale)) {
|
||||
return availableLocale;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected @Nullable YamlConfiguration loadFallbackLocale() throws IOException {
|
||||
URL fallbackLocaleUrl = this.plugin.getClass().getResource("/en_US.lang");
|
||||
if (fallbackLocaleUrl == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
YamlConfiguration locale = new YamlConfiguration();
|
||||
try (Reader reader = new InputStreamReader(fallbackLocaleUrl.openStream(), StandardCharsets.UTF_8)) {
|
||||
locale.load(reader);
|
||||
}
|
||||
|
||||
return locale;
|
||||
}
|
||||
|
||||
protected SongodaYamlConfig parseLocaleFile(File file) throws IOException {
|
||||
SongodaYamlConfig locale = new SongodaYamlConfig(file, this.plugin.getLogger());
|
||||
locale.load();
|
||||
|
||||
return locale;
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package com.songoda.core.utils;
|
||||
|
||||
public class Pair<T, U> {
|
||||
private final T first;
|
||||
private final U second;
|
||||
|
||||
public Pair(T first, U second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public T getFirst() {
|
||||
return this.first;
|
||||
}
|
||||
|
||||
public U getSecond() {
|
||||
return this.second;
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package com.songoda.core.configuration;
|
||||
|
||||
import com.songoda.core.configuration.yaml.YamlConfiguration;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class ReadOnlyConfigEntryTest {
|
||||
@Test
|
||||
void testGetKey() {
|
||||
ConfigEntry entry = new ReadOnlyConfigEntry(new YamlConfiguration(), "key-1");
|
||||
|
||||
assertEquals("key-1", entry.getKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetConfig() {
|
||||
IConfiguration config = new YamlConfiguration();
|
||||
ConfigEntry entry = new ReadOnlyConfigEntry(config, "key");
|
||||
|
||||
assertSame(config, entry.getConfig());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNullGetters() {
|
||||
ConfigEntry entry = new ReadOnlyConfigEntry(new YamlConfiguration(), "key-null");
|
||||
|
||||
assertNull(entry.getDefaultValue());
|
||||
assertNull(entry.getUpgradeSteps());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWritingMethodsDoingNothing() {
|
||||
YamlConfiguration config = new YamlConfiguration();
|
||||
ConfigEntry entry = new ReadOnlyConfigEntry(config, "key");
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () -> entry.setDefaultValue("value"));
|
||||
assertThrows(UnsupportedOperationException.class, () -> entry.withDefaultValue("value"));
|
||||
assertThrows(UnsupportedOperationException.class, () -> entry.withComment("A comment."));
|
||||
assertThrows(UnsupportedOperationException.class, () -> entry.withUpgradeStep(0, "old-key"));
|
||||
assertThrows(UnsupportedOperationException.class, () -> entry.set("value"));
|
||||
|
||||
assertNull(entry.getDefaultValue());
|
||||
assertNull(entry.getUpgradeSteps());
|
||||
assertNull(config.getNodeComment("key"));
|
||||
assertNull(entry.getUpgradeSteps());
|
||||
assertNull(entry.get());
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
package com.songoda.core.configuration.songoda;
|
||||
|
||||
import com.songoda.core.configuration.ConfigEntry;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class SongodaYamlConfigRoundtripTest {
|
||||
private Path testDirectoryPath;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
this.testDirectoryPath = Files.createTempDirectory("SongodaCore-YamlConfigRoundtripTest");
|
||||
this.testDirectoryPath.toFile().deleteOnExit();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws IOException {
|
||||
try (Stream<Path> paths = Files.list(this.testDirectoryPath)) {
|
||||
for (Path path : paths.toArray(Path[]::new)) {
|
||||
Files.deleteIfExists(path);
|
||||
}
|
||||
}
|
||||
Files.deleteIfExists(this.testDirectoryPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
void roundtripTest() throws IOException {
|
||||
Path testFilePath = this.testDirectoryPath.resolve("config.yml");
|
||||
|
||||
Files.write(testFilePath, ("# Don't touch this – it's used to track the version of the config.\n" +
|
||||
"version: 1\n" +
|
||||
"messages:\n" +
|
||||
" # This message is shown when the 'foo' command succeeds.\n" +
|
||||
" fooSuccess: Remastered success value\n" +
|
||||
"# This is the range of the 'foo' command\n").getBytes());
|
||||
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(testFilePath.toFile())
|
||||
.withVersion(3);
|
||||
|
||||
ConfigEntry cmdFooSuccess = cfg.createEntry("command.foo.success")
|
||||
.withDefaultValue("Default success value")
|
||||
.withComment("This message is shown when the 'foo' command succeeds.")
|
||||
.withUpgradeStep(1, "messages.fooSuccess");
|
||||
ConfigEntry range = cfg.createEntry("range")
|
||||
.withComment("This is the range of the 'foo' command")
|
||||
.withUpgradeStep(1, null, o -> {
|
||||
if (o == null) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
return o;
|
||||
})
|
||||
.withUpgradeStep(2, null, o -> o + " blocks");
|
||||
ConfigEntry incrementer = cfg.createEntry("incrementer", 0)
|
||||
.withComment("This is the incrementer of the 'foo' command")
|
||||
.withUpgradeStep(1, null, o -> {
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) o + 1;
|
||||
})
|
||||
.withUpgradeStep(3, null, (o) -> "text");
|
||||
ConfigEntry entryWithoutUpgradeStep = cfg.createEntry("entryWithoutUpgradeStep")
|
||||
.withDefaultValue("Default value")
|
||||
.withComment("This is the entry without an upgrade step");
|
||||
|
||||
ConfigEntry entryWithCyrillic = cfg.createEntry("entryWithCyrillic")
|
||||
.withDefaultValue("Кириллица")
|
||||
.withComment("This is the entry with cyrillic characters");
|
||||
|
||||
assertTrue(cfg.init());
|
||||
|
||||
assertNull(cfg.get("messages.fooSuccess"));
|
||||
assertEquals("Remastered success value", cfg.get("command.foo.success"));
|
||||
assertEquals("Remastered success value", cmdFooSuccess.get());
|
||||
assertTrue(cmdFooSuccess.has());
|
||||
|
||||
assertTrue(range.has());
|
||||
assertEquals(cfg.get("range"), range.get());
|
||||
|
||||
assertTrue(incrementer.has());
|
||||
assertEquals(cfg.get("incrementer"), incrementer.get());
|
||||
|
||||
assertTrue(entryWithoutUpgradeStep.has());
|
||||
assertEquals(cfg.get("entryWithoutUpgradeStep"), entryWithoutUpgradeStep.get());
|
||||
|
||||
assertTrue(entryWithCyrillic.has());
|
||||
assertEquals(cfg.get("entryWithCyrillic"), entryWithCyrillic.get());
|
||||
|
||||
assertEquals("# Don't touch this – it's used to track the version of the config.\n" +
|
||||
"version: 3\n" +
|
||||
"command:\n" +
|
||||
" foo:\n" +
|
||||
" # This message is shown when the 'foo' command succeeds.\n" +
|
||||
" success: Remastered success value\n" +
|
||||
"# This is the range of the 'foo' command\n" +
|
||||
"range: 10 blocks\n" +
|
||||
"# This is the incrementer of the 'foo' command\n" +
|
||||
"incrementer: 0\n" +
|
||||
"# This is the entry without an upgrade step\n" +
|
||||
"entryWithoutUpgradeStep: Default value\n" +
|
||||
"# This is the entry with cyrillic characters\n" +
|
||||
"entryWithCyrillic: Кириллица\n", new String(Files.readAllBytes(testFilePath)));
|
||||
}
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
package com.songoda.core.configuration.songoda;
|
||||
|
||||
import com.songoda.core.configuration.ConfigEntry;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class SongodaYamlConfigTest {
|
||||
Path tmpDir;
|
||||
Path cfg;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
this.tmpDir = Files.createTempDirectory("SongodaYamlConfigTest");
|
||||
|
||||
this.cfg = Files.createTempFile(this.tmpDir, "config", ".yml");
|
||||
this.tmpDir.toFile().deleteOnExit();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws IOException {
|
||||
try (Stream<Path> stream = Files.walk(this.tmpDir)) {
|
||||
stream
|
||||
.sorted(Comparator.reverseOrder())
|
||||
.map(Path::toFile)
|
||||
.forEach(File::delete);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad() throws IOException {
|
||||
Files.write(this.cfg, "test-key: foo\n".getBytes());
|
||||
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(this.cfg.toFile());
|
||||
cfg.set("test-key", "bar");
|
||||
cfg.load();
|
||||
|
||||
assertEquals("foo", cfg.get("test-key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSave() throws IOException {
|
||||
Files.write(this.cfg, "test-key: foo\n".getBytes());
|
||||
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(this.cfg.toFile());
|
||||
cfg.set("test-key", "bar");
|
||||
cfg.save();
|
||||
|
||||
assertEquals("test-key: bar\n", new String(Files.readAllBytes(this.cfg)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaveToNonExistingSubDirectory() throws IOException {
|
||||
File configFile = new File(this.tmpDir.toFile(), "testSaveToNonExistingSubDirectory/config.yml");
|
||||
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(configFile);
|
||||
cfg.set("test-key", "bar");
|
||||
cfg.save();
|
||||
|
||||
assertEquals("test-key: bar\n", new String(Files.readAllBytes(configFile.toPath())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithVersion() throws IOException {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(this.cfg.toFile());
|
||||
cfg.withVersion("version-key", 1, null);
|
||||
|
||||
assertEquals(1, cfg.get("version-key"));
|
||||
|
||||
cfg.save();
|
||||
assertEquals("version-key: 1\n", new String(Files.readAllBytes(this.cfg)));
|
||||
|
||||
cfg.withVersion(2);
|
||||
|
||||
assertEquals(2, cfg.get("version"));
|
||||
assertNull(cfg.get("version-key"));
|
||||
|
||||
cfg.save();
|
||||
assertEquals(
|
||||
"# Don't touch this – it's used to track the version of the config.\n" +
|
||||
"version: 2\n",
|
||||
new String(Files.readAllBytes(this.cfg))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithZeroVersion() throws IOException {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(this.cfg.toFile());
|
||||
cfg.withVersion("version-key", 0, null);
|
||||
|
||||
assertEquals(0, cfg.get("version-key"));
|
||||
|
||||
cfg.save();
|
||||
assertEquals("version-key: 0\n", new String(Files.readAllBytes(this.cfg)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithNegativeVersion() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(this.cfg.toFile());
|
||||
assertThrows(IllegalArgumentException.class, () -> cfg.withVersion("version-key", -1, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadWithTooNewVersion() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(this.cfg.toFile())
|
||||
.withVersion(1);
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> cfg.load(new StringReader("version: 10\n")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithUpToDateVersion() throws IOException {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(this.cfg.toFile())
|
||||
.withVersion(2);
|
||||
|
||||
assertFalse(cfg.upgradeOldConfigVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithNewerVersion() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(this.cfg.toFile())
|
||||
.withVersion(5);
|
||||
|
||||
assertThrows(IllegalStateException.class, cfg::upgradeOldConfigVersionByOne);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithKeyWithoutConfigEntry() throws IOException {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(this.cfg.toFile());
|
||||
|
||||
cfg.set("test-key", "foo");
|
||||
cfg.load();
|
||||
|
||||
assertNull(cfg.get("test-key"));
|
||||
|
||||
cfg.set("test-key", "foo");
|
||||
assertEquals("foo", cfg.get("test-key"));
|
||||
|
||||
cfg.save();
|
||||
cfg.load();
|
||||
|
||||
assertEquals("foo", cfg.get("test-key"));
|
||||
assertEquals(1, cfg.getKeys("").size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateEntryAppliesDefaultValueForNullValue() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(this.cfg.toFile());
|
||||
ConfigEntry entry = cfg.createEntry("key", "value");
|
||||
|
||||
cfg.init();
|
||||
|
||||
assertEquals("value", entry.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateDuplicateEntry() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(this.cfg.toFile());
|
||||
ConfigEntry entry = cfg.createEntry("key", null);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> cfg.createEntry("key", "other-value"));
|
||||
|
||||
assertNull(entry.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVersionUpgradePersistsCommentsOnKeyChange() throws IOException {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(this.cfg.toFile())
|
||||
.withVersion(2);
|
||||
|
||||
cfg.createEntry("newKey", "value")
|
||||
.withComment("This is a comment")
|
||||
.withUpgradeStep(1, "oldKey");
|
||||
|
||||
cfg.load(new StringReader("version: 1\noldKey: old-value\n"));
|
||||
|
||||
assertNull(cfg.get("oldKey"));
|
||||
assertNull(cfg.getNodeComment("oldKey"));
|
||||
|
||||
assertEquals("old-value", cfg.get("newKey"));
|
||||
assertEquals("This is a comment", Objects.requireNonNull(cfg.getNodeComment("newKey")).get());
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
cfg.save(writer);
|
||||
|
||||
assertEquals("# Don't touch this – it's used to track the version of the config.\n" +
|
||||
"version: 2\n" +
|
||||
"# This is a comment\n" +
|
||||
"newKey: old-value\n",
|
||||
writer.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReadOnlyEntry() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(this.cfg.toFile());
|
||||
ConfigEntry entry = cfg.createEntry("key", "default-value");
|
||||
ConfigEntry readOnlyConfigEntry = cfg.getReadEntry("key");
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () -> readOnlyConfigEntry.set("new-value"));
|
||||
assertEquals("default-value", entry.get());
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () -> readOnlyConfigEntry.setDefaultValue("new-default-value"));
|
||||
assertEquals("default-value", entry.get());
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () -> readOnlyConfigEntry.withComment("test-comment"));
|
||||
assertThrows(UnsupportedOperationException.class, () -> readOnlyConfigEntry.withComment(() -> "test-comment"));
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () -> readOnlyConfigEntry.withUpgradeStep(10, "new-key"));
|
||||
assertThrows(UnsupportedOperationException.class, () -> readOnlyConfigEntry.withUpgradeStep(10, "new-key", (o) -> "new-value"));
|
||||
|
||||
assertEquals("default-value", entry.get());
|
||||
|
||||
entry.set("new-value");
|
||||
assertEquals("new-value", readOnlyConfigEntry.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInit_Failure() {
|
||||
assertTrue(this.cfg.toFile().setWritable(false));
|
||||
|
||||
Logger mockLogger = Mockito.mock(Logger.class);
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(this.cfg.toFile(), mockLogger);
|
||||
|
||||
cfg.createEntry("key", "default-value");
|
||||
|
||||
assertFalse(cfg.init());
|
||||
Mockito.verify(mockLogger).log(Mockito.eq(Level.SEVERE), Mockito.anyString(), Mockito.any(IOException.class));
|
||||
|
||||
assertTrue(this.cfg.toFile().setWritable(true));
|
||||
}
|
||||
}
|
@ -1,258 +0,0 @@
|
||||
package com.songoda.core.configuration.yaml;
|
||||
|
||||
import com.songoda.core.compatibility.CompatibleMaterial;
|
||||
import com.songoda.core.configuration.ConfigEntry;
|
||||
import com.songoda.core.configuration.songoda.SongodaYamlConfig;
|
||||
import com.songoda.ultimatestacker.core.configuration.Config;
|
||||
import org.bukkit.Material;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class YamlConfigEntryTest {
|
||||
@Test
|
||||
void testHas() {
|
||||
YamlConfiguration config = new YamlConfiguration();
|
||||
config.set("key-2", "value-2");
|
||||
|
||||
ConfigEntry entry1 = new YamlConfigEntry(config, "key-1", null);
|
||||
ConfigEntry entry2 = new YamlConfigEntry(config, "key-2", null);
|
||||
|
||||
assertFalse(entry1.has());
|
||||
assertTrue(entry2.has());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetKey() {
|
||||
ConfigEntry entry = new YamlConfigEntry(new YamlConfiguration(), "key-1", null);
|
||||
assertEquals("key-1", entry.getKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetConfig() {
|
||||
YamlConfiguration config = new YamlConfiguration();
|
||||
ConfigEntry entry = new YamlConfigEntry(config, "key-1", null);
|
||||
assertSame(config, entry.getConfig());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDefaultValue() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(new File("ConfigEntryTest.yml"));
|
||||
ConfigEntry entry = cfg.createEntry("key", "value");
|
||||
|
||||
assertEquals("value", entry.getDefaultValue());
|
||||
|
||||
entry.setDefaultValue("new-value");
|
||||
assertEquals("new-value", entry.getDefaultValue());
|
||||
|
||||
entry.withDefaultValue("new-value-2");
|
||||
assertEquals("new-value-2", entry.getDefaultValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetOr() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(new File("ConfigEntryTest.yml"));
|
||||
ConfigEntry entry = cfg.createEntry("key", "value");
|
||||
|
||||
assertEquals("value", entry.getOr("invalid"));
|
||||
|
||||
entry.set(null);
|
||||
assertEquals("invalid", entry.getOr("invalid"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetString() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(new File("ConfigEntryTest.yml"));
|
||||
ConfigEntry entry = cfg.createEntry("key", null);
|
||||
|
||||
entry.set("value");
|
||||
assertEquals("value", entry.getString());
|
||||
|
||||
entry.set("new-value");
|
||||
assertEquals("new-value", entry.getString());
|
||||
|
||||
entry.set(null);
|
||||
assertNull(entry.getString());
|
||||
assertNull(entry.getStringOr(null));
|
||||
assertEquals("12", entry.getStringOr("12"));
|
||||
|
||||
entry.set(10.5);
|
||||
assertEquals("10.5", entry.getString());
|
||||
|
||||
entry.set(true);
|
||||
assertEquals("true", entry.getString());
|
||||
|
||||
entry.set(CompatibleMaterial.STONE);
|
||||
assertEquals("STONE", entry.getString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetInt() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(new File("ConfigEntryTest.yml"));
|
||||
ConfigEntry entry = cfg.createEntry("key", null);
|
||||
|
||||
entry.set(1.0);
|
||||
assertEquals(1, entry.getInt());
|
||||
|
||||
entry.set("1.5");
|
||||
assertEquals(1, entry.getInt());
|
||||
|
||||
entry.set("10");
|
||||
assertEquals(10.0, entry.getInt());
|
||||
|
||||
entry.set("10,0");
|
||||
assertThrows(NumberFormatException.class, entry::getInt);
|
||||
|
||||
entry.set(null);
|
||||
assertEquals(0, entry.getInt());
|
||||
assertEquals(11, entry.getIntOr(11));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDouble() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(new File("ConfigEntryTest.yml"));
|
||||
ConfigEntry entry = cfg.createEntry("key", null);
|
||||
|
||||
entry.set(1.0);
|
||||
assertEquals(1.0, entry.getDouble());
|
||||
|
||||
entry.set("1.5");
|
||||
assertEquals(1.5, entry.getDouble());
|
||||
|
||||
entry.set("10");
|
||||
assertEquals(10.0, entry.getDouble());
|
||||
|
||||
entry.set("10,0");
|
||||
assertThrows(NumberFormatException.class, entry::getDouble);
|
||||
|
||||
entry.set(null);
|
||||
assertEquals(0.0, entry.getDouble());
|
||||
assertEquals(11.5, entry.getDoubleOr(11.5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetBoolean() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(new File("ConfigEntryTest.yml"));
|
||||
ConfigEntry entry = cfg.createEntry("key", null);
|
||||
|
||||
entry.set(false);
|
||||
assertFalse(entry.getBoolean());
|
||||
|
||||
entry.set("false");
|
||||
assertFalse(entry.getBoolean());
|
||||
|
||||
entry.set("invalid");
|
||||
assertFalse(entry.getBoolean());
|
||||
|
||||
entry.set(1);
|
||||
assertFalse(entry.getBoolean());
|
||||
|
||||
entry.set(true);
|
||||
assertTrue(entry.getBoolean());
|
||||
|
||||
entry.set("true");
|
||||
assertTrue(entry.getBoolean());
|
||||
|
||||
entry.set(null);
|
||||
assertFalse(entry.getBoolean());
|
||||
assertTrue(entry.getBooleanOr(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetStringList() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(new File("ConfigEntryTest.yml"));
|
||||
ConfigEntry entry = cfg.createEntry("key", null);
|
||||
|
||||
final List<String> fallbackValue = Collections.unmodifiableList(new LinkedList<>());
|
||||
|
||||
entry.set(null);
|
||||
assertNull(entry.getStringList());
|
||||
assertSame(fallbackValue, entry.getStringListOr(fallbackValue));
|
||||
|
||||
entry.set(Collections.singletonList("value"));
|
||||
assertEquals(Collections.singletonList("value"), entry.getStringList());
|
||||
|
||||
entry.set(new String[] {"value2"});
|
||||
assertEquals(Collections.singletonList("value2"), entry.getStringList());
|
||||
|
||||
entry.set("string-value");
|
||||
assertNull(entry.getStringList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetMaterial() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(new File("ConfigEntryTest.yml"));
|
||||
ConfigEntry entry = cfg.createEntry("key", null);
|
||||
|
||||
entry.set("LOG");
|
||||
assertEquals(CompatibleMaterial.BIRCH_LOG, entry.getMaterial());
|
||||
|
||||
entry.set("OAK_LOG");
|
||||
assertEquals(CompatibleMaterial.OAK_LOG, entry.getMaterial());
|
||||
|
||||
entry.set("10");
|
||||
assertNull(entry.getMaterial());
|
||||
|
||||
entry.set(null);
|
||||
assertNull(entry.getMaterial());
|
||||
assertEquals(CompatibleMaterial.ACACIA_BOAT, entry.getMaterialOr(CompatibleMaterial.ACACIA_BOAT));
|
||||
|
||||
entry.set(CompatibleMaterial.GRASS);
|
||||
assertEquals(CompatibleMaterial.GRASS, entry.getMaterial());
|
||||
|
||||
entry.set(Material.GRASS);
|
||||
assertEquals(CompatibleMaterial.GRASS, entry.getMaterial());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidWithUpgradeNull() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(new File("ConfigEntryTest.yml"));
|
||||
ConfigEntry entry = cfg.createEntry("key", "value");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> entry.withUpgradeStep(1, null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEqualsAndHashCode() {
|
||||
SongodaYamlConfig cfg = new SongodaYamlConfig(new File("ConfigEntryTest.yml"));
|
||||
ConfigEntry entry = cfg.createEntry("key", "value");
|
||||
|
||||
assertEquals(entry, entry);
|
||||
assertEquals(entry.hashCode(), entry.hashCode());
|
||||
|
||||
|
||||
ConfigEntry other = new YamlConfigEntry(cfg, "key", "value");
|
||||
assertEquals(entry, other);
|
||||
assertEquals(entry.hashCode(), other.hashCode());
|
||||
|
||||
other = new YamlConfigEntry(cfg, "key", "value2");
|
||||
assertNotEquals(entry, other);
|
||||
assertNotEquals(entry.hashCode(), other.hashCode());
|
||||
|
||||
other = new YamlConfigEntry(cfg, "key2", "value");
|
||||
assertNotEquals(entry, other);
|
||||
assertNotEquals(entry.hashCode(), other.hashCode());
|
||||
|
||||
other = new YamlConfigEntry(cfg, "key", "value2");
|
||||
assertNotEquals(entry, other);
|
||||
assertNotEquals(entry.hashCode(), other.hashCode());
|
||||
|
||||
other = new YamlConfigEntry(cfg, "key2", "value2");
|
||||
assertNotEquals(entry, other);
|
||||
assertNotEquals(entry.hashCode(), other.hashCode());
|
||||
|
||||
assertNotEquals(entry, null);
|
||||
assertNotEquals(entry, "key");
|
||||
}
|
||||
}
|
@ -1,613 +0,0 @@
|
||||
package com.songoda.core.configuration.yaml;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.yaml.snakeyaml.constructor.DuplicateKeyException;
|
||||
import org.yaml.snakeyaml.error.YAMLException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class YamlConfigurationTest {
|
||||
static final String inputYaml = "foo: bar\n" +
|
||||
"primitives:\n" +
|
||||
" int: " + Integer.MIN_VALUE + "\n" +
|
||||
" long: " + Long.MIN_VALUE + "\n" +
|
||||
" float: " + Float.MIN_VALUE + "\n" +
|
||||
" double: " + Double.MIN_VALUE + "\n" +
|
||||
" char: ä\n" +
|
||||
" string: string\n" +
|
||||
" string-long: " + StringUtils.repeat("abc", 512) + "\n" +
|
||||
" string-multi-line: |\n" +
|
||||
" a\n" +
|
||||
" b\n" +
|
||||
" c\n" +
|
||||
" boolean: true\n" +
|
||||
" list: [2, 1, 3]\n" +
|
||||
" map:\n" +
|
||||
" key: value\n" +
|
||||
" set:\n" +
|
||||
" - 1\n" +
|
||||
" - 2\n" +
|
||||
" - 3\n";
|
||||
static final String expectedOutYaml = "foo: bar\n" +
|
||||
"primitives:\n" +
|
||||
" int: " + Integer.MIN_VALUE + "\n" +
|
||||
" long: " + Long.MIN_VALUE + "\n" +
|
||||
" float: " + Float.MIN_VALUE + "\n" +
|
||||
" double: " + Double.MIN_VALUE + "\n" +
|
||||
" char: ä\n" +
|
||||
" string: string\n" +
|
||||
" string-long: " + StringUtils.repeat("abc", 512) + "\n" +
|
||||
" string-multi-line: |\n" +
|
||||
" a\n" +
|
||||
" b\n" +
|
||||
" c\n" +
|
||||
" boolean: true\n" +
|
||||
" list:\n" +
|
||||
" - 2\n" +
|
||||
" - 1\n" +
|
||||
" - 3\n" +
|
||||
" map:\n" +
|
||||
" key: value\n" +
|
||||
" set:\n" +
|
||||
" - 1\n" +
|
||||
" - 2\n" +
|
||||
" - 3\n";
|
||||
|
||||
@Test
|
||||
void testYamlParser() throws IOException {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
cfg.load(new StringReader(inputYaml));
|
||||
|
||||
assertEquals(Integer.MIN_VALUE, cfg.get("primitives.int"));
|
||||
assertEquals(Long.MIN_VALUE, cfg.get("primitives.long"));
|
||||
assertEquals(Float.MIN_VALUE, ((Number) cfg.get("primitives.float")).floatValue());
|
||||
assertEquals(Double.MIN_VALUE, cfg.get("primitives.double"));
|
||||
|
||||
assertEquals("ä", cfg.get("primitives.char"));
|
||||
|
||||
assertEquals("string", cfg.get("primitives.string"));
|
||||
|
||||
assertInstanceOf(Boolean.class, cfg.get("primitives.boolean"));
|
||||
assertTrue((Boolean) cfg.get("primitives.boolean"));
|
||||
|
||||
List<?> primitivesList = (List<?>) cfg.get("primitives.list");
|
||||
assertNotNull(primitivesList);
|
||||
assertInstanceOf(List.class, cfg.get("primitives.list"));
|
||||
assertEquals(3, primitivesList.size());
|
||||
assertEquals(2, primitivesList.get(0));
|
||||
assertEquals(1, primitivesList.get(1));
|
||||
assertEquals(3, primitivesList.get(2));
|
||||
|
||||
assertEquals("value", cfg.get("primitives.map.key"));
|
||||
|
||||
assertInstanceOf(List.class, cfg.get("primitives.set"));
|
||||
assertEquals(3, ((List<?>) cfg.get("primitives.set")).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testYamlParserWithEmptyFile() throws IOException {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
cfg.load(new StringReader(""));
|
||||
assertTrue(cfg.getKeys("").isEmpty());
|
||||
|
||||
cfg.load(new StringReader("\n"));
|
||||
assertTrue(cfg.getKeys("").isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testYamlParserWithDuplicateKeys() {
|
||||
assertThrowsExactly(DuplicateKeyException.class,
|
||||
() -> new YamlConfiguration().load(new StringReader("test: value1\ntest: value2")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testYamlParserWithInvalidReader() throws IOException {
|
||||
Reader reader = new StringReader("");
|
||||
reader.close();
|
||||
|
||||
assertThrowsExactly(YAMLException.class, () -> new YamlConfiguration().load(reader));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testYamlWriter() throws IOException {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
final StringWriter stringWriter = new StringWriter(inputYaml.length());
|
||||
|
||||
cfg.load(new StringReader(inputYaml));
|
||||
cfg.save(stringWriter);
|
||||
|
||||
assertEquals(expectedOutYaml, stringWriter.toString());
|
||||
assertEquals(expectedOutYaml, cfg.toYamlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testYamlWriterWithNullValue() throws IOException {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
final StringWriter stringWriter = new StringWriter(1);
|
||||
|
||||
cfg.set("null-value", null);
|
||||
cfg.set("nested.null-value", null);
|
||||
cfg.save(stringWriter);
|
||||
|
||||
assertEquals("", stringWriter.toString());
|
||||
assertEquals("", cfg.toYamlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testYamlWriterWithNoData() throws IOException {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
final StringWriter stringWriter = new StringWriter(inputYaml.length());
|
||||
|
||||
cfg.save(stringWriter);
|
||||
|
||||
assertEquals("", stringWriter.toString());
|
||||
assertEquals("", cfg.toYamlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testYamlWriterWithNoDataAndComments() throws IOException {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
final StringWriter stringWriter = new StringWriter(inputYaml.length());
|
||||
|
||||
cfg.setHeaderComment("baz");
|
||||
cfg.setNodeComment("foo", "bar");
|
||||
|
||||
cfg.save(stringWriter);
|
||||
|
||||
assertEquals("# baz\n", stringWriter.toString());
|
||||
assertEquals("# baz\n", cfg.toYamlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetter() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.set("foo.bar.innerBar", "bar")); // 'foo.bar' gets overwritten
|
||||
|
||||
Object prevValue = cfg.set("foo.bar", "baz");
|
||||
assertInstanceOf(Map.class, prevValue);
|
||||
assertEquals(1, ((Map<?, ?>) prevValue).size());
|
||||
assertEquals("bar", ((Map<?, ?>) prevValue).get("innerBar"));
|
||||
|
||||
assertNull(cfg.set("number", 27));
|
||||
assertNull(cfg.set("bar.foo.faa1", "value1"));
|
||||
assertNull(cfg.set("bar.foo.faa2", "value2"));
|
||||
|
||||
assertFalse(cfg.has("a.b.c"));
|
||||
assertFalse(cfg.has("a"));
|
||||
|
||||
Map<String, Object> expectedValues = new HashMap<String, Object>() {{
|
||||
put("number", 27);
|
||||
|
||||
put("foo", new HashMap<String, Object>() {{
|
||||
put("bar", "baz");
|
||||
}});
|
||||
|
||||
put("bar", new HashMap<String, Object>() {{
|
||||
put("foo", new HashMap<String, Object>() {{
|
||||
put("faa1", "value1");
|
||||
put("faa2", "value2");
|
||||
}});
|
||||
}});
|
||||
}};
|
||||
|
||||
assertEquals(expectedValues, cfg.values);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetterAndGetterWithPrimitiveValues() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.set("foobar", "test"));
|
||||
assertNull(cfg.set("foo.bar", "test2"));
|
||||
assertEquals("test", cfg.set("foobar", "overwritten-test"));
|
||||
|
||||
assertEquals("overwritten-test", cfg.get("foobar"));
|
||||
|
||||
assertEquals("test2", cfg.get("foo.bar"));
|
||||
|
||||
assertNull(cfg.set("primitives.int", Integer.MIN_VALUE));
|
||||
assertNull(cfg.set("primitives.long", Long.MIN_VALUE));
|
||||
assertNull(cfg.set("primitives.float", Float.MIN_VALUE));
|
||||
assertNull(cfg.set("primitives.double", Double.MIN_VALUE));
|
||||
assertNull(cfg.set("primitives.char", 'ä'));
|
||||
assertNull(cfg.set("primitives.string", "string"));
|
||||
assertNull(cfg.set("primitives.boolean", true));
|
||||
|
||||
assertEquals(Integer.MIN_VALUE, cfg.get("primitives.int"));
|
||||
assertEquals(Long.MIN_VALUE, cfg.get("primitives.long"));
|
||||
|
||||
assertInstanceOf(Double.class, cfg.get("primitives.float"));
|
||||
assertEquals(Float.MIN_VALUE, ((Number) cfg.get("primitives.float")).floatValue());
|
||||
|
||||
assertEquals(Double.MIN_VALUE, cfg.get("primitives.double"));
|
||||
|
||||
assertInstanceOf(String.class, cfg.get("primitives.char"));
|
||||
assertEquals("ä", cfg.get("primitives.char"));
|
||||
|
||||
assertEquals("string", cfg.get("primitives.string"));
|
||||
assertInstanceOf(Boolean.class, cfg.get("primitives.boolean"));
|
||||
assertTrue((Boolean) cfg.get("primitives.boolean"));
|
||||
|
||||
assertNull(cfg.set("primitives.map.key", "value"));
|
||||
assertEquals("value", cfg.get("primitives.map.key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetterWithNullValue() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.set("foo.bar.null", "not-null-string"));
|
||||
assertEquals("not-null-string", cfg.set("foo.bar.null", null));
|
||||
|
||||
assertNull(cfg.get("foo.bar.null"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetNonExistingNestedKey() throws IOException {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
cfg.load(new StringReader(inputYaml));
|
||||
|
||||
assertNull(cfg.get("primitives.map2.key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetOrDefault() throws IOException {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
cfg.load(new StringReader(inputYaml));
|
||||
|
||||
assertEquals("bar", cfg.set("foo", "bar"));
|
||||
assertNull(cfg.set("bar.baz", "foz"));
|
||||
|
||||
assertEquals("bar", cfg.getOr("foo", "baz"));
|
||||
assertEquals("foz", cfg.getOr("bar.baz", "baz"));
|
||||
|
||||
assertEquals("default", cfg.getOr("foo.bar", "default"));
|
||||
assertEquals("default", cfg.getOr("bar.baz.foo", "default"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetterWithNullKey() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.get(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetKeys() throws IOException {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
cfg.load(new StringReader(inputYaml));
|
||||
|
||||
assertEquals(2, cfg.getKeys("").size());
|
||||
assertTrue(cfg.getKeys(null).isEmpty());
|
||||
|
||||
assertTrue(cfg.getKeys("primitives.map.key.non-existing-subkey").isEmpty());
|
||||
assertTrue(cfg.getKeys("foo").isEmpty());
|
||||
|
||||
assertArrayEquals(new String[] {"key"}, cfg.getKeys("primitives.map").toArray());
|
||||
assertArrayEquals(new String[] {"int", "long", "float", "double", "char", "string", "string-long", "string-multi-line", "boolean", "list", "map", "set"}, cfg.getKeys("primitives").toArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetterWithListValues() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.set("primitives.list", Arrays.asList(2, 1, 3)));
|
||||
|
||||
assertInstanceOf(List.class, cfg.get("primitives.list"));
|
||||
List<?> primitivesList = (List<?>) cfg.get("primitives.list");
|
||||
assertNotNull(primitivesList);
|
||||
assertEquals(3, primitivesList.size());
|
||||
assertEquals(2, primitivesList.get(0));
|
||||
assertEquals(1, primitivesList.get(1));
|
||||
assertEquals(3, primitivesList.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetterWithEnumValue() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.set("primitives.enum", TestEnum.ENUM_VALUE));
|
||||
|
||||
assertInstanceOf(String.class, cfg.get("primitives.enum"));
|
||||
assertEquals(TestEnum.ENUM_VALUE, TestEnum.valueOf((String) cfg.get("primitives.enum")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetterWithBooleanArrayValue() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.set("primitives.array", new boolean[] {Boolean.FALSE, Boolean.TRUE}));
|
||||
|
||||
assertInstanceOf(List.class, cfg.get("primitives.array"));
|
||||
List<?> primitivesList = (List<?>) cfg.get("primitives.array");
|
||||
assert primitivesList != null;
|
||||
assertEquals(2, primitivesList.size());
|
||||
assertEquals(Boolean.FALSE, primitivesList.get(0));
|
||||
assertEquals(Boolean.TRUE, primitivesList.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetterWithByteArrayValue() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.set("primitives.array", new byte[] {2, Byte.MIN_VALUE, Byte.MAX_VALUE}));
|
||||
|
||||
assertInstanceOf(List.class, cfg.get("primitives.array"));
|
||||
List<?> primitivesList = (List<?>) cfg.get("primitives.array");
|
||||
assert primitivesList != null;
|
||||
assertEquals(3, primitivesList.size());
|
||||
assertEquals(2, primitivesList.get(0));
|
||||
assertEquals((int) Byte.MIN_VALUE, primitivesList.get(1));
|
||||
assertEquals((int) Byte.MAX_VALUE, primitivesList.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetterWithCharArrayValue() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.set("primitives.array", new char[] {'x', Character.MIN_VALUE, Character.MAX_VALUE}));
|
||||
|
||||
assertInstanceOf(List.class, cfg.get("primitives.array"));
|
||||
List<?> primitivesList = (List<?>) cfg.get("primitives.array");
|
||||
assert primitivesList != null;
|
||||
assertEquals(3, primitivesList.size());
|
||||
assertEquals("x", primitivesList.get(0));
|
||||
assertEquals(String.valueOf(Character.MIN_VALUE), primitivesList.get(1));
|
||||
assertEquals(String.valueOf(Character.MAX_VALUE), primitivesList.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetterWithShortArrayValue() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.set("primitives.array", new short[] {2, Short.MIN_VALUE, Short.MAX_VALUE}));
|
||||
|
||||
assertInstanceOf(List.class, cfg.get("primitives.array"));
|
||||
List<?> primitivesList = (List<?>) cfg.get("primitives.array");
|
||||
assert primitivesList != null;
|
||||
assertEquals(3, primitivesList.size());
|
||||
assertEquals(2, primitivesList.get(0));
|
||||
assertEquals((int) Short.MIN_VALUE, primitivesList.get(1));
|
||||
assertEquals((int) Short.MAX_VALUE, primitivesList.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetterWithIntArrayValue() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.set("primitives.array", new int[] {2, Integer.MIN_VALUE, Integer.MAX_VALUE}));
|
||||
|
||||
assertInstanceOf(List.class, cfg.get("primitives.array"));
|
||||
List<?> primitivesList = (List<?>) cfg.get("primitives.array");
|
||||
assert primitivesList != null;
|
||||
assertEquals(3, primitivesList.size());
|
||||
assertEquals(2, primitivesList.get(0));
|
||||
assertEquals(Integer.MIN_VALUE, primitivesList.get(1));
|
||||
assertEquals(Integer.MAX_VALUE, primitivesList.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetterWithLongArrayValue() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.set("primitives.array", new long[] {2, Long.MIN_VALUE, Long.MAX_VALUE}));
|
||||
|
||||
assertInstanceOf(List.class, cfg.get("primitives.array"));
|
||||
List<?> primitivesList = (List<?>) cfg.get("primitives.array");
|
||||
assert primitivesList != null;
|
||||
assertEquals(3, primitivesList.size());
|
||||
assertEquals((long) 2, primitivesList.get(0));
|
||||
assertEquals(Long.MIN_VALUE, primitivesList.get(1));
|
||||
assertEquals(Long.MAX_VALUE, primitivesList.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetterWithFloatArrayValue() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.set("primitives.array", new float[] {2, Float.MIN_VALUE, Float.MAX_VALUE}));
|
||||
|
||||
assertInstanceOf(List.class, cfg.get("primitives.array"));
|
||||
List<?> primitivesList = (List<?>) cfg.get("primitives.array");
|
||||
assert primitivesList != null;
|
||||
assertEquals(3, primitivesList.size());
|
||||
assertEquals((double) 2, primitivesList.get(0));
|
||||
assertEquals((double) Float.MIN_VALUE, primitivesList.get(1));
|
||||
assertEquals((double) Float.MAX_VALUE, primitivesList.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetterWithDoubleArrayValue() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.set("primitives.array", new double[] {2, Double.MIN_VALUE, Double.MAX_VALUE}));
|
||||
|
||||
assertInstanceOf(List.class, cfg.get("primitives.array"));
|
||||
List<?> primitivesList = (List<?>) cfg.get("primitives.array");
|
||||
assert primitivesList != null;
|
||||
assertEquals(3, primitivesList.size());
|
||||
assertEquals((double) 2, primitivesList.get(0));
|
||||
assertEquals(Double.MIN_VALUE, primitivesList.get(1));
|
||||
assertEquals(Double.MAX_VALUE, primitivesList.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetterWithStringArrayValue() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertNull(cfg.set("primitives.array", new String[] {"zyx", "b", "a"}));
|
||||
|
||||
assertInstanceOf(List.class, cfg.get("primitives.array"));
|
||||
List<?> primitivesList = (List<?>) cfg.get("primitives.array");
|
||||
assert primitivesList != null;
|
||||
assertEquals(3, primitivesList.size());
|
||||
assertEquals("zyx", primitivesList.get(0));
|
||||
assertEquals("b", primitivesList.get(1));
|
||||
assertEquals("a", primitivesList.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHas() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
assertFalse(cfg.has(null));
|
||||
|
||||
assertNull(cfg.set("foo", "bar"));
|
||||
|
||||
assertTrue(cfg.has("foo"));
|
||||
assertFalse(cfg.has("bar"));
|
||||
|
||||
assertNull(cfg.set("foo.bar", "baz"));
|
||||
assertTrue(cfg.has("foo.bar"));
|
||||
|
||||
assertFalse(cfg.has("foo.baz"));
|
||||
assertNull(YamlConfiguration.getInnerMap(cfg.values, new String[] {"foo", "baz"}, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReset() throws IOException {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
cfg.load(new StringReader(inputYaml));
|
||||
|
||||
cfg.setNodeComment("foo", "bar");
|
||||
cfg.setHeaderComment("baz");
|
||||
|
||||
assertFalse(cfg.values.isEmpty());
|
||||
cfg.reset();
|
||||
assertTrue(cfg.values.isEmpty());
|
||||
|
||||
assertNotNull(cfg.getHeaderComment());
|
||||
assertNotNull(cfg.getNodeComment("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnset() throws IOException {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
cfg.load(new StringReader(inputYaml));
|
||||
|
||||
Object unsetResult;
|
||||
|
||||
assertTrue(cfg.has("foo"));
|
||||
unsetResult = cfg.unset("foo");
|
||||
assertEquals("bar", unsetResult);
|
||||
assertFalse(cfg.has("foo"));
|
||||
|
||||
assertTrue(cfg.has("primitives"));
|
||||
assertTrue(cfg.has("primitives.int"));
|
||||
assertTrue(cfg.has("primitives.double"));
|
||||
unsetResult = cfg.unset("primitives.int");
|
||||
assertEquals(Integer.MIN_VALUE, unsetResult);
|
||||
assertFalse(cfg.has("primitives.int"));
|
||||
assertTrue(cfg.has("primitives.double"));
|
||||
|
||||
unsetResult = cfg.unset("primitives");
|
||||
assertInstanceOf(Map.class, unsetResult);
|
||||
assertFalse(cfg.has("primitives"));
|
||||
assertFalse(cfg.has("primitives.double"));
|
||||
assertFalse(cfg.has("primitives.string"));
|
||||
|
||||
unsetResult = cfg.unset("unknown.nested.key");
|
||||
assertNull(unsetResult);
|
||||
unsetResult = cfg.unset("unknown-key");
|
||||
assertNull(unsetResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToString() throws IOException {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
String firstToString = cfg.toString();
|
||||
|
||||
assertTrue(firstToString.contains(YamlConfiguration.class.getSimpleName()));
|
||||
assertTrue(firstToString.contains(cfg.values.toString()));
|
||||
|
||||
cfg.load(new StringReader(inputYaml));
|
||||
|
||||
String secondToString = cfg.toString();
|
||||
|
||||
assertNotEquals(firstToString, secondToString);
|
||||
assertTrue(secondToString.contains(YamlConfiguration.class.getSimpleName()));
|
||||
assertTrue(secondToString.contains(cfg.values.toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadWithInvalidYaml() {
|
||||
final YamlConfiguration cfg = new YamlConfiguration();
|
||||
|
||||
IllegalStateException exception = assertThrowsExactly(IllegalStateException.class,
|
||||
() -> cfg.load(new StringReader("Hello world")));
|
||||
|
||||
assertEquals("The YAML file does not have the expected tree structure: java.lang.String", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHeaderComments() throws IOException {
|
||||
String expectedHeaderComment = "This is a header comment";
|
||||
|
||||
YamlConfiguration cfg = new YamlConfiguration();
|
||||
cfg.setHeaderComment(expectedHeaderComment);
|
||||
cfg.set("foo", "bar");
|
||||
|
||||
assertNotNull(cfg.getHeaderComment());
|
||||
assertEquals(expectedHeaderComment, cfg.getHeaderComment().get());
|
||||
|
||||
assertEquals("# " + expectedHeaderComment + "\n\nfoo: bar\n", cfg.toYamlString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNodeComments() throws IOException {
|
||||
String expectedYaml = "# Foo-Comment\n" +
|
||||
"foo: bar\n" +
|
||||
"# Level1-Comment\n" +
|
||||
"level1:\n" +
|
||||
" level2:\n" +
|
||||
" # Level3-Comment\n" +
|
||||
" level3: value\n";
|
||||
|
||||
YamlConfiguration cfg = new YamlConfiguration();
|
||||
cfg.set("foo", "bar");
|
||||
cfg.set("level1.level2.level3", "value");
|
||||
|
||||
cfg.setNodeComment("foo", "Foo-Comment");
|
||||
cfg.setNodeComment("level1", "Level1-Comment");
|
||||
cfg.setNodeComment("level1.level2.level3", "Level3-Comment");
|
||||
|
||||
Supplier<String> currentNodeComment = cfg.getNodeComment("foo");
|
||||
assertNotNull(currentNodeComment);
|
||||
assertEquals("Foo-Comment", currentNodeComment.get());
|
||||
|
||||
currentNodeComment = cfg.getNodeComment("level1");
|
||||
assertNotNull(currentNodeComment);
|
||||
assertEquals("Level1-Comment", currentNodeComment.get());
|
||||
|
||||
currentNodeComment = cfg.getNodeComment("level1.level2");
|
||||
assertNull(currentNodeComment);
|
||||
|
||||
currentNodeComment = cfg.getNodeComment("level1.level2.level3");
|
||||
assertNotNull(currentNodeComment);
|
||||
assertEquals("Level3-Comment", currentNodeComment.get());
|
||||
|
||||
assertEquals(expectedYaml, cfg.toYamlString());
|
||||
}
|
||||
|
||||
private enum TestEnum {
|
||||
ENUM_VALUE;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "#toString(): " + super.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,160 +0,0 @@
|
||||
package com.songoda.core.locale;
|
||||
|
||||
import com.songoda.core.http.MockHttpClient;
|
||||
import com.songoda.core.http.MockHttpResponse;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
class LocaleFileManagerTest {
|
||||
private final byte[] validIndexFile = ("# This is a comment\n\nen_US.lang\nen.yml\nde.txt\n").getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
private Path testDirectoryPath;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
this.testDirectoryPath = Files.createTempDirectory("SongodaCore-LocaleFileManagerTest");
|
||||
this.testDirectoryPath.toFile().deleteOnExit();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws IOException {
|
||||
try (Stream<Path> paths = Files.list(this.testDirectoryPath)) {
|
||||
for (Path path : paths.toArray(Path[]::new)) {
|
||||
Files.deleteIfExists(path);
|
||||
}
|
||||
}
|
||||
Files.deleteIfExists(this.testDirectoryPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
void downloadMissingTranslations_EmptyTargetDir() throws IOException {
|
||||
Plugin plugin = Mockito.mock(Plugin.class);
|
||||
Mockito.when(plugin.getDataFolder()).thenReturn(this.testDirectoryPath.toFile());
|
||||
|
||||
MockHttpClient httpClient = new MockHttpClient(new MockHttpResponse(200, this.validIndexFile));
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(httpClient, "test");
|
||||
|
||||
List<String> downloadedFiles = localeFileManager.downloadMissingTranslations(plugin.getDataFolder());
|
||||
|
||||
Assertions.assertSame(3, downloadedFiles.size());
|
||||
Assertions.assertEquals("en.yml", downloadedFiles.get(1));
|
||||
Assertions.assertEquals("en_US.lang", downloadedFiles.get(0));
|
||||
Assertions.assertEquals("de.txt", downloadedFiles.get(2));
|
||||
|
||||
String[] localeFiles = plugin.getDataFolder().list();
|
||||
Assertions.assertNotNull(localeFiles);
|
||||
Arrays.sort(localeFiles);
|
||||
Assertions.assertArrayEquals(new String[] {"de.txt", "en.yml", "en_US.lang"}, localeFiles);
|
||||
|
||||
Assertions.assertSame(4, httpClient.callsOnGet.size());
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(0).contains("/test/_index.txt"));
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(1).contains("/test/en_US.lang"));
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(2).contains("/test/en.yml"));
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(3).contains("/test/de.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void downloadMissingTranslations() throws IOException {
|
||||
Plugin plugin = Mockito.mock(Plugin.class);
|
||||
Mockito.when(plugin.getDataFolder()).thenReturn(this.testDirectoryPath.toFile());
|
||||
|
||||
Files.createDirectories(plugin.getDataFolder().toPath());
|
||||
Files.createFile(new File(plugin.getDataFolder(), "en_US.lang").toPath());
|
||||
Files.createFile(new File(plugin.getDataFolder(), "fr.lang").toPath());
|
||||
|
||||
MockHttpClient httpClient = new MockHttpClient(new MockHttpResponse(200, this.validIndexFile));
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(httpClient, "test");
|
||||
|
||||
List<String> downloadedFiles = localeFileManager.downloadMissingTranslations(plugin.getDataFolder());
|
||||
|
||||
Assertions.assertSame(2, downloadedFiles.size());
|
||||
Assertions.assertEquals("en.yml", downloadedFiles.get(0));
|
||||
Assertions.assertEquals("de.txt", downloadedFiles.get(1));
|
||||
|
||||
String[] localeFiles = plugin.getDataFolder().list();
|
||||
|
||||
Assertions.assertNotNull(localeFiles);
|
||||
Arrays.sort(localeFiles);
|
||||
Assertions.assertArrayEquals(new String[] {"de.txt", "en.yml", "en_US.lang", "fr.lang"}, localeFiles);
|
||||
|
||||
Assertions.assertSame(3, httpClient.callsOnGet.size());
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(0).contains("/test/_index.txt"));
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(1).contains("/test/en.yml"));
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(2).contains("/test/de.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchAvailableLanguageFiles() throws IOException {
|
||||
MockHttpClient httpClient = new MockHttpClient(new MockHttpResponse(200, this.validIndexFile));
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(httpClient, "test");
|
||||
|
||||
List<String> availableLanguages = localeFileManager.fetchAvailableLanguageFiles();
|
||||
|
||||
Assertions.assertSame(1, httpClient.callsOnGet.size());
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(0).contains("/test/"));
|
||||
|
||||
Assertions.assertNotNull(availableLanguages);
|
||||
Assertions.assertSame(3, availableLanguages.size());
|
||||
Assertions.assertTrue(availableLanguages.contains("en_US.lang"));
|
||||
Assertions.assertTrue(availableLanguages.contains("en.yml"));
|
||||
Assertions.assertTrue(availableLanguages.contains("de.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchAvailableLanguageFiles_SpecialCharsInProjectName() throws IOException {
|
||||
MockHttpClient httpClient = new MockHttpClient(new MockHttpResponse(200, this.validIndexFile));
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(httpClient, "test project (special)");
|
||||
|
||||
List<String> availableLanguages = localeFileManager.fetchAvailableLanguageFiles();
|
||||
|
||||
Assertions.assertSame(1, httpClient.callsOnGet.size());
|
||||
Assertions.assertTrue(httpClient.callsOnGet.get(0).contains("/test+project+%28special%29/"));
|
||||
|
||||
Assertions.assertNotNull(availableLanguages);
|
||||
Assertions.assertSame(3, availableLanguages.size());
|
||||
Assertions.assertTrue(availableLanguages.contains("en_US.lang"));
|
||||
Assertions.assertTrue(availableLanguages.contains("en.yml"));
|
||||
Assertions.assertTrue(availableLanguages.contains("de.txt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchAvailableLanguageFiles_EmptyIndex() throws IOException {
|
||||
MockHttpClient httpClient = new MockHttpClient(new MockHttpResponse(200, new byte[0]));
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(httpClient, "empty-project");
|
||||
|
||||
List<String> availableLanguages = localeFileManager.fetchAvailableLanguageFiles();
|
||||
|
||||
Assertions.assertNotNull(availableLanguages);
|
||||
Assertions.assertTrue(availableLanguages.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchAvailableLanguageFiles_UnknownProject() throws IOException {
|
||||
MockHttpClient httpClient = new MockHttpClient(new MockHttpResponse(404, new byte[0]));
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(httpClient, "unknown");
|
||||
|
||||
Assertions.assertNull(localeFileManager.fetchAvailableLanguageFiles());
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchAvailableLanguageFiles_HttpStatus500() {
|
||||
MockHttpClient httpClient = new MockHttpClient(new MockHttpResponse(500, new byte[0]));
|
||||
LocaleFileManager localeFileManager = new LocaleFileManager(httpClient, "test");
|
||||
|
||||
Assertions.assertThrows(IOException.class, localeFileManager::fetchAvailableLanguageFiles);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user