diff --git a/src/main/java/net/knarcraft/knarlib/config/StargateYamlConfiguration.java b/src/main/java/net/knarcraft/knarlib/config/StargateYamlConfiguration.java new file mode 100644 index 0000000..32d66c5 --- /dev/null +++ b/src/main/java/net/knarcraft/knarlib/config/StargateYamlConfiguration.java @@ -0,0 +1,296 @@ +package net.knarcraft.knarlib.config; + +import net.knarcraft.knarlib.util.ConfigHelper; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.FileConfiguration; +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.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; + +/** + * A YAML configuration which retains all comments + * + *
This configuration converts all comments to YAML values when loaded, which all start with comment_. When saved, + * those YAML values are converted to normal text comments. This ensures that the comments aren't removed by the + * YamlConfiguration during its parsing.
+ * + *Note: When retrieving a configuration section, that will include comments that start with "comment_". You should + * filter those before parsing input, or use "getKeysWithoutComments".
+ * + *Also note: This class was originally made for the Stargate rewrite, but can be used for any configuration file. + * In order to make sure the comments are retained, always use this class instead of plugin.getConfig() whenever code + * might choose to save the configuration.
+ * + * @author Kristian Knarvik + * @author Thorin + */ +public class StargateYamlConfiguration extends YamlConfiguration { + + private static final String START_OF_COMMENT_LINE = "[HASHTAG]"; + private static final String END_OF_COMMENT = "_endOfComment_"; + private static final String START_OF_COMMENT = "comment_"; + + @Override + @NotNull + @Deprecated + @SuppressWarnings("deprecation") + protected String buildHeader() { + return ""; + } + + @Override + @NotNull + public String saveToString() { + // Convert YAML comments to normal comments + return this.convertYAMLMappingsToComments(super.saveToString()); + } + + @Override + public void loadFromString(@NotNull String contents) throws InvalidConfigurationException { + // Convert normal comments to YAML comments to prevent them from disappearing + super.loadFromString(this.convertCommentsToYAMLMappings(contents)); + } + + /** + * Loads the configuration of the given plugin + * + * @param pluginThe plugin to load the configuration from
+ * @returnThe loaded configuration, or null if unsuccessful
+ */ + @SuppressWarnings("unused") + @Nullable + public static StargateYamlConfiguration loadConfiguration(@NotNull Plugin plugin) { + StargateYamlConfiguration configuration = new StargateYamlConfiguration(); + configuration.options().copyDefaults(true); + try { + configuration.load(new File(plugin.getDataFolder(), ConfigHelper.getConfigFile())); + return configuration; + } catch (IOException | InvalidConfigurationException exception) { + plugin.getLogger().log(Level.SEVERE, "Unable to load the configuration! Message: " + exception.getMessage()); + return null; + } + } + + /** + * Saves the given configuration + * + * @param pluginThe plugin to save configuration for
+ * @param configurationThe configuration to save
+ * @returnTrue if the configuration was successfully saved
+ */ + @SuppressWarnings("unused") + public static boolean saveConfiguration(@NotNull Plugin plugin, @NotNull StargateYamlConfiguration configuration) { + try { + configuration.save(new File(plugin.getDataFolder(), ConfigHelper.getConfigFile())); + return true; + } catch (IOException exception) { + plugin.getLogger().log(Level.SEVERE, "Unable to save the configuration! Message: " + exception.getMessage()); + return false; + } + } + + /** + * Gets a configuration section's keys, without any comment entries + * + * @param configurationSectionThe configuration section to get keys for
+ * @param deepWhether to get keys for child elements as well
+ * @returnThe configuration section's keys, with comment entries removed
+ */ + public static SetA mapping follows this format: comment_{CommentNumber}: "The comment" + * This needs to be done as comments otherwise get removed using + * the {@link FileConfiguration#save(File)} method. The config + * needs to be saved if a config value has changed.
+ * + * @param configStringThe config string to convert
+ */ + @NotNull + private String convertCommentsToYAMLMappings(@NotNull String configString) { + StringBuilder yamlBuilder = new StringBuilder(); + ListThe string builder used for building YAML
+ * @param currentCommentThe comment to add as a YAML string
+ * @param lineThe current line
+ * @param previousIndentationThe indentation of the current block comment
+ * @param commentIdThe id of the comment
+ */ + private void addYamlString(@NotNull StringBuilder yamlBuilder, @NotNull ListThe list to add to
+ * @param commentThe comment to add
+ */ + private void addComment(@NotNull ListThe string builder to add the generated YAML to
+ * @param commentLinesThe lines of the comment to convert into YAML
+ * @param commentIdThe unique id of the comment
+ * @param indentationThe indentation to add to every line
+ */ + private void generateCommentYAML(@NotNull StringBuilder yamlBuilder, @NotNull ListThe internal YAML structure is converted to a string with the same format as a standard configuration file. + * The internal structure has comments in the format: START_OF_COMMENT + id + multi-line YAML string + + * END_OF_COMMENT.
+ * + * @param yamlStringA string using the YAML format
+ * @returnThe corresponding comment string
+ */ + @NotNull + private String convertYAMLMappingsToComments(@NotNull String yamlString) { + StringBuilder finalText = new StringBuilder(); + + String[] lines = yamlString.split("\n"); + for (int currentIndex = 0; currentIndex < lines.length; currentIndex++) { + String line = lines[currentIndex]; + String possibleComment = line.trim(); + + if (possibleComment.startsWith(START_OF_COMMENT)) { + //Add an empty line before every comment block + finalText.append("\n"); + currentIndex = readComment(finalText, lines, currentIndex + 1, getIndentation(line)); + } else { + //Output the configuration key + finalText.append(line).append("\n"); + } + } + + return finalText.toString().trim(); + } + + /** + * Fully reads a comment + * + * @param builderThe string builder to write to
+ * @param linesThe lines to read from
+ * @param startIndexThe index to start reading from
+ * @param commentIndentationThe indentation of the read comment
+ * @returnThe index containing the next non-comment line
+ */ + private int readComment(@NotNull StringBuilder builder, @NotNull String[] lines, int startIndex, + int commentIndentation) { + for (int currentIndex = startIndex; currentIndex < lines.length; currentIndex++) { + String line = lines[currentIndex]; + String possibleComment = line.trim(); + if (!line.contains(END_OF_COMMENT)) { + possibleComment = possibleComment.replace(START_OF_COMMENT_LINE, ""); + builder.append(addIndentation(commentIndentation)).append("# ").append(possibleComment).append("\n"); + } else { + return currentIndex; + } + } + + return startIndex; + } + + /** + * Gets a string containing the given indentation + * + * @param indentationSpacesThe number spaces to use for indentation
+ * @returnA string containing the number of spaces specified
+ */ + @NotNull + private String addIndentation(int indentationSpaces) { + return " ".repeat(Math.max(0, indentationSpaces)); + } + + + /** + * Gets the indentation (number of spaces) of the given line + * + * @param lineThe line to get indentation of
+ * @returnThe number of spaces in the line's indentation
+ */ + private int getIndentation(@NotNull String line) { + int spacesFound = 0; + for (char aCharacter : line.toCharArray()) { + if (aCharacter == ' ') { + spacesFound++; + } else { + break; + } + } + return spacesFound; + } + +} \ No newline at end of file diff --git a/src/main/java/net/knarcraft/knarlib/util/ConfigHelper.java b/src/main/java/net/knarcraft/knarlib/util/ConfigHelper.java new file mode 100644 index 0000000..33d6934 --- /dev/null +++ b/src/main/java/net/knarcraft/knarlib/util/ConfigHelper.java @@ -0,0 +1,162 @@ +package net.knarcraft.knarlib.util; + +import net.knarcraft.knarlib.config.StargateYamlConfiguration; +import net.knarcraft.knarlib.property.ColorConversion; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.MemorySection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.logging.Level; + +/** + * A helper class for dealing with a plugin's configuration file + */ +@SuppressWarnings("unused") +public final class ConfigHelper { + + private static final String CONFIG_FILE = "config.yml"; + private static final String BACKUP_CONFIG_FILE = "config.yml.old"; + private static final String MIGRATION_FILE = "config-migrations.txt"; + + private ConfigHelper() { + + } + + /** + * Gets the relative string path of the configuration file + * + * @returnThe configuration file path
+ */ + public static String getConfigFile() { + return CONFIG_FILE; + } + + /** + * Saves any missing configuration values to the given plugin's configuration + * + * @param pluginThe plugin to add missing default values to
+ */ + public static void saveDefaults(@NotNull Plugin plugin) { + plugin.saveDefaultConfig(); + plugin.getConfig().options().copyDefaults(true); + plugin.reloadConfig(); + plugin.saveConfig(); + } + + /** + * Changes all configuration values from the old name to the new name + * + *Note: This method expects a file "config-migrations.txt" in the resources directory that contains mappings: + * oldKey=replacementKey in order to migrate from old to new configuration values. + * The old configuration file will be saved to config.yml.old
+ * + * @param pluginThe plugin to migrate the configuration for
+ * @returnTrue if the migration succeeded without any issues
+ */ + public static boolean migrateConfig(@NotNull Plugin plugin) { + File dataFolder = plugin.getDataFolder(); + //Save the old config just in case something goes wrong + try { + StargateYamlConfiguration currentConfiguration = new StargateYamlConfiguration(); + currentConfiguration.load(new File(dataFolder, CONFIG_FILE)); + currentConfiguration.save(new File(dataFolder, BACKUP_CONFIG_FILE)); + } catch (IOException | InvalidConfigurationException exception) { + plugin.getLogger().log(Level.WARNING, "Unable to save old backup and do migration"); + return false; + } + + //Load old and new configuration + plugin.reloadConfig(); + FileConfiguration oldConfiguration = plugin.getConfig(); + InputStream configStream = FileHelper.getInputStreamForInternalFile("/" + CONFIG_FILE); + if (configStream == null) { + plugin.getLogger().log(Level.SEVERE, "Could not migrate the configuration, as the internal " + + "configuration could not be read!"); + return false; + } + YamlConfiguration newConfiguration = StargateYamlConfiguration.loadConfiguration( + FileHelper.getBufferedReaderFromInputStream(configStream)); + + //Read all available config migrations + MapThe configuration fields to be migrated
+ * @param keyThe key/path of the property to migrate
+ * @param oldConfigurationThe original pre-migration configuration
+ */ + private static void migrateProperty(@NotNull Map