diff --git a/src/main/java/fr/xephi/authme/settings/NewSetting.java b/src/main/java/fr/xephi/authme/settings/NewSetting.java index 3679c927..c48f1c46 100644 --- a/src/main/java/fr/xephi/authme/settings/NewSetting.java +++ b/src/main/java/fr/xephi/authme/settings/NewSetting.java @@ -8,6 +8,8 @@ import fr.xephi.authme.settings.propertymap.PropertyMap; import fr.xephi.authme.util.CollectionUtils; import fr.xephi.authme.util.StringUtils; import org.bukkit.configuration.file.FileConfiguration; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; import java.io.File; import java.io.FileWriter; @@ -36,16 +38,12 @@ public class NewSetting { this.configuration = configuration; this.file = file; - // TODO ljacqu 20160109: Ensure that save() works as desired (i.e. that it always produces valid YAML) - // and then uncomment the lines below. Once this is uncommented, the checks in the old Settings.java should - // be removed as we should check to rewrite the config.yml file only at one place - // -------- - // PropertyMap propertyMap = SettingsFieldRetriever.getAllPropertyFields(); - // if (SettingsMigrationService.checkAndMigrate(configuration, propertyMap)) { - // ConsoleLogger.info("Merged new config options"); - // ConsoleLogger.info("Please check your config.yml file for new settings!"); - // save(propertyMap); - // } + PropertyMap propertyMap = SettingsFieldRetriever.getAllPropertyFields(); + if (SettingsMigrationService.checkAndMigrate(configuration, propertyMap)) { + ConsoleLogger.info("Merged new config options"); + ConsoleLogger.info("Please check your config.yml file for new settings!"); + save(propertyMap); + } } /** @@ -76,14 +74,18 @@ public class NewSetting { return property.getFromFile(configuration); } - public void save() { - save(SettingsFieldRetriever.getAllPropertyFields()); - } - public void save(PropertyMap propertyMap) { try (FileWriter writer = new FileWriter(file)) { writer.write(""); + DumperOptions simpleOptions = new DumperOptions(); + simpleOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + Yaml simpleYaml = new Yaml(simpleOptions); + DumperOptions singleQuoteOptions = new DumperOptions(); + singleQuoteOptions.setDefaultScalarStyle(DumperOptions.ScalarStyle.SINGLE_QUOTED); + singleQuoteOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + Yaml singleQuoteYaml = new Yaml(singleQuoteOptions); + // Contains all but the last node of the setting, e.g. [DataSource, mysql] for "DataSource.mysql.username" List currentPath = new ArrayList<>(); for (Map.Entry, String[]> entry : propertyMap.entrySet()) { @@ -118,14 +120,8 @@ public class NewSetting { writer.append("\n") .append(indent(indentationLevel)) .append(CollectionUtils.getRange(newPathParts, newPathParts.size() - 1).get(0)) - .append(": "); - - List yamlLines = property.formatValueAsYaml(configuration); - String delim = ""; - for (String yamlLine : yamlLines) { - writer.append(delim).append(yamlLine); - delim = "\n" + indent(indentationLevel); - } + .append(": ") + .append(toYaml(property, indentationLevel, simpleYaml, singleQuoteYaml)); currentPath = propertyPath.subList(0, propertyPath.size() - 1); } @@ -137,6 +133,33 @@ public class NewSetting { } } + private String toYaml(Property property, int indent, Yaml simpleYaml, Yaml singleQuoteYaml) { + T value = property.getFromFile(configuration); + String representation = property.hasSingleQuotes() + ? singleQuoteYaml.dump(value) + : simpleYaml.dump(value); + + // If the property is a non-empty list we need to append a new line because it will be + // something like the following, which requires a new line: + // - 'item 1' + // - 'second item in list' + if (property.isList() && !((List) value).isEmpty()) { + representation = "\n" + representation; + } + + return join("\n" + indent(indent), representation.split("\\n")); + } + + private static String join(String delimiter, String[] items) { + StringBuilder sb = new StringBuilder(); + String delim = ""; + for (String item : items) { + sb.append(delim).append(item); + delim = delimiter; + } + return sb.toString(); + } + private static String indent(int level) { // YAML uses indentation of 4 spaces StringBuilder sb = new StringBuilder(level * 4); diff --git a/src/main/java/fr/xephi/authme/settings/domain/EnumPropertyType.java b/src/main/java/fr/xephi/authme/settings/domain/EnumPropertyType.java index ed184bb7..c71739a5 100644 --- a/src/main/java/fr/xephi/authme/settings/domain/EnumPropertyType.java +++ b/src/main/java/fr/xephi/authme/settings/domain/EnumPropertyType.java @@ -2,12 +2,9 @@ package fr.xephi.authme.settings.domain; import org.bukkit.configuration.file.FileConfiguration; -import java.util.List; - -import static java.util.Arrays.asList; - /** * Enum property type. + * * @param The enum class */ class EnumPropertyType> extends PropertyType { @@ -28,17 +25,17 @@ class EnumPropertyType> extends PropertyType { return mappedValue != null ? mappedValue : property.getDefaultValue(); } - @Override - protected List asYaml(E value) { - return asList("'" + value + "'"); - } - @Override public boolean contains(Property property, FileConfiguration configuration) { return super.contains(property, configuration) && mapToEnum(configuration.getString(property.getPath())) != null; } + @Override + public boolean hasSingleQuotes() { + return true; + } + private E mapToEnum(String value) { for (E entry : clazz.getEnumConstants()) { if (entry.name().equalsIgnoreCase(value)) { diff --git a/src/main/java/fr/xephi/authme/settings/domain/Property.java b/src/main/java/fr/xephi/authme/settings/domain/Property.java index 6b15b5d3..a67c6a49 100644 --- a/src/main/java/fr/xephi/authme/settings/domain/Property.java +++ b/src/main/java/fr/xephi/authme/settings/domain/Property.java @@ -7,7 +7,7 @@ import java.util.List; import java.util.Objects; /** - * Properties (i.e. a setting that is read from the config.yml file). + * Property class, representing a setting that is read from the config.yml file. */ public class Property { @@ -22,15 +22,43 @@ public class Property { this.defaultValue = defaultValue; } + /** + * Create a new property. See also {@link #newProperty(PropertyType, String, Object[])} for lists and + * {@link #newProperty(Class, String, Enum)}. + * + * @param type The property type + * @param path The property's path + * @param defaultValue The default value + * @param The type of the property + * @return The created property + */ public static Property newProperty(PropertyType type, String path, T defaultValue) { return new Property<>(type, path, defaultValue); } + /** + * Create a new list property. + * + * @param type The list type of the property + * @param path The property's path + * @param defaultValues The default value's items + * @param The list type + * @return The created list property + */ @SafeVarargs public static Property> newProperty(PropertyType> type, String path, U... defaultValues) { return new Property<>(type, path, Arrays.asList(defaultValues)); } + /** + * Create a new enum property. + * + * @param clazz The enum class + * @param path The property's path + * @param defaultValue The default value + * @param The enum type + * @return The created enum property + */ public static > Property newProperty(Class clazz, String path, E defaultValue) { return new Property<>(new EnumPropertyType<>(clazz), path, defaultValue); } @@ -53,9 +81,8 @@ public class Property { // ----- // Hooks to the PropertyType methods // ----- - /** - * Get the property value from the given configuration. + * Get the property value from the given configuration – guaranteed to never return null. * * @param configuration The configuration to read the value from * @return The value, or default if not present @@ -64,16 +91,6 @@ public class Property { return type.getFromFile(this, configuration); } - /** - * Format the property value as YAML. - * - * @param configuration The configuration to read the value from - * @return The property value as YAML - */ - public List formatValueAsYaml(FileConfiguration configuration) { - return type.asYaml(this, configuration); - } - /** * Return whether or not the given configuration file contains the property. * @@ -84,10 +101,30 @@ public class Property { return type.contains(this, configuration); } + /** + * Return whether the property should be represented wrapped in single quotes in YAML. + * The YAML format allows both using single quotes and not for all types but for certain + * types one representation makes more sense. Typically we wrap string-like types in + * single quotes and all other ones not. + * + * @return True if single quotes should be used, false otherwise + */ + public boolean hasSingleQuotes() { + return type.hasSingleQuotes(); + } + + /** + * Return whether the property has a list type. + * + * @return True if the property type is a list, false otherwise + */ + public boolean isList() { + return type.isList(); + } + // ----- // Trivial getters // ----- - /** * Return the default value of the property. * diff --git a/src/main/java/fr/xephi/authme/settings/domain/PropertyType.java b/src/main/java/fr/xephi/authme/settings/domain/PropertyType.java index dc1975bb..4be93415 100644 --- a/src/main/java/fr/xephi/authme/settings/domain/PropertyType.java +++ b/src/main/java/fr/xephi/authme/settings/domain/PropertyType.java @@ -2,11 +2,8 @@ package fr.xephi.authme.settings.domain; import org.bukkit.configuration.file.FileConfiguration; -import java.util.ArrayList; import java.util.List; -import static java.util.Arrays.asList; - /** * Handles a certain property type and provides type-specific functionality. * @@ -30,17 +27,6 @@ public abstract class PropertyType { */ public abstract T getFromFile(Property property, FileConfiguration configuration); - /** - * Return the property's value (or its default) as YAML. - * - * @param property The property to transform - * @param configuration The YAML configuration to read from - * @return The read value or its default in YAML format - */ - public List asYaml(Property property, FileConfiguration configuration) { - return asYaml(getFromFile(property, configuration)); - } - /** * Return whether the property is present in the given configuration. * @@ -53,12 +39,17 @@ public abstract class PropertyType { } /** - * Transform the given value to YAML. + * Return whether the property type should be wrapped in single quotes in YAML. * - * @param value The value to transform - * @return The value as YAML + * @return True if single quotes should be used, false if not */ - protected abstract List asYaml(T value); + public boolean hasSingleQuotes() { + return false; + } + + public boolean isList() { + return false; + } /** @@ -69,11 +60,6 @@ public abstract class PropertyType { public Boolean getFromFile(Property property, FileConfiguration configuration) { return configuration.getBoolean(property.getPath(), property.getDefaultValue()); } - - @Override - protected List asYaml(Boolean value) { - return asList(value ? "true" : "false"); - } } /** @@ -84,11 +70,6 @@ public abstract class PropertyType { public Double getFromFile(Property property, FileConfiguration configuration) { return configuration.getDouble(property.getPath(), property.getDefaultValue()); } - - @Override - protected List asYaml(Double value) { - return asList(String.valueOf(value)); - } } /** @@ -99,11 +80,6 @@ public abstract class PropertyType { public Integer getFromFile(Property property, FileConfiguration configuration) { return configuration.getInt(property.getPath(), property.getDefaultValue()); } - - @Override - protected List asYaml(Integer value) { - return asList(String.valueOf(value)); - } } /** @@ -114,15 +90,9 @@ public abstract class PropertyType { public String getFromFile(Property property, FileConfiguration configuration) { return configuration.getString(property.getPath(), property.getDefaultValue()); } - @Override - protected List asYaml(String value) { - return asList(toYamlLiteral(value)); - } - - public static String toYamlLiteral(String str) { - // TODO: Need to handle new lines properly - return "'" + str.replace("'", "''") + "'"; + public boolean hasSingleQuotes() { + return true; } } @@ -139,23 +109,18 @@ public abstract class PropertyType { } @Override - protected List asYaml(List value) { - if (value.isEmpty()) { - return asList("[]"); - } - - List resultLines = new ArrayList<>(); - resultLines.add(""); // add - for (String entry : value) { - // TODO: StringProperty#toYamlLiteral will return List... - resultLines.add(" - " + StringProperty.toYamlLiteral(entry)); - } - return resultLines; + public boolean contains(Property> property, FileConfiguration configuration) { + return configuration.contains(property.getPath()) && configuration.isList(property.getPath()); } @Override - public boolean contains(Property> property, FileConfiguration configuration) { - return configuration.contains(property.getPath()) && configuration.isList(property.getPath()); + public boolean hasSingleQuotes() { + return true; + } + + @Override + public boolean isList() { + return true; } }