package net.knarcraft.blacksmith.config; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.jetbrains.annotations.NotNull; import java.io.File; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * 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.
* * @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 @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)); } /** * 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.
*/ @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; } }