#927 Integrate ConfigMe into AuthMe (work in progress)

- Replace own code with ConfigMe
This commit is contained in:
ljacqu
2016-08-30 15:28:07 +02:00
parent 33eab1df21
commit c7bb7b460e
44 changed files with 290 additions and 1482 deletions
@@ -1,108 +1,46 @@
package fr.xephi.authme.settings;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.github.authme.configme.SettingsManager;
import com.github.authme.configme.migration.MigrationService;
import com.github.authme.configme.propertymap.PropertyEntry;
import com.github.authme.configme.resource.PropertyResource;
import com.google.common.io.Files;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.RegistrationSettings;
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.bukkit.configuration.file.YamlConfiguration;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static fr.xephi.authme.util.FileUtils.copyFileFromResource;
/**
* The AuthMe settings manager.
*/
public class Settings {
public class Settings extends SettingsManager {
private final File pluginFolder;
private final File configFile;
private final PropertyMap propertyMap;
private final SettingsMigrationService migrationService;
private FileConfiguration configuration;
/** The file with the localized messages based on {@link PluginSettings#MESSAGES_LANGUAGE}. */
private File messagesFile;
private List<String> welcomeMessage;
private String emailMessage;
/**
* Constructor. Checks the given {@link FileConfiguration} object for completeness.
* Constructor.
*
* @param configFile The configuration file
* @param pluginFolder The AuthMe plugin folder
* @param propertyMap Collection of all available settings
* @param migrationService Migration service to check the settings file with
* @param pluginFolder the AuthMe plugin folder
* @param knownProperties collection of all available settings
* @param resource the property resource to read and write properties to
* @param migrationService migration service to check the settings file with
*/
public Settings(File configFile, File pluginFolder, PropertyMap propertyMap,
SettingsMigrationService migrationService) {
this.configuration = YamlConfiguration.loadConfiguration(configFile);
this.configFile = configFile;
public Settings(File pluginFolder, List<PropertyEntry> knownProperties, PropertyResource resource,
MigrationService migrationService) {
super(knownProperties, resource, migrationService);
this.pluginFolder = pluginFolder;
this.propertyMap = propertyMap;
this.migrationService = migrationService;
validateAndLoadOptions();
}
/**
* Constructor for testing purposes, allowing more options.
*
* @param configuration The FileConfiguration object to use
* @param configFile The file to write to
* @param pluginFolder The plugin folder
* @param propertyMap The property map whose properties should be verified for presence, or null to skip this
* @param migrationService Migration service, or null to skip migration checks
*/
@VisibleForTesting
Settings(FileConfiguration configuration, File configFile, File pluginFolder, PropertyMap propertyMap,
SettingsMigrationService migrationService) {
this.configuration = configuration;
this.configFile = configFile;
this.pluginFolder = pluginFolder;
this.propertyMap = propertyMap;
this.migrationService = migrationService;
if (propertyMap != null && migrationService != null) {
validateAndLoadOptions();
}
}
/**
* Get the given property from the configuration.
*
* @param property The property to retrieve
* @param <T> The property's type
* @return The property's value
*/
public <T> T getProperty(Property<T> property) {
return property.getFromFile(configuration);
}
/**
* Set a new value for the given property.
*
* @param property The property to modify
* @param value The new value to assign to the property
* @param <T> The property's type
*/
public <T> void setProperty(Property<T> property, T value) {
configuration.set(property.getPath(), value);
}
/**
@@ -141,71 +79,9 @@ public class Settings {
return welcomeMessage;
}
/**
* Reload the configuration.
*/
public void reload() {
configuration = YamlConfiguration.loadConfiguration(configFile);
validateAndLoadOptions();
}
/**
* Save the config file. Use after migrating one or more settings.
*/
public void save() {
try (FileWriter writer = new FileWriter(configFile)) {
Yaml simpleYaml = newYaml(false);
Yaml singleQuoteYaml = newYaml(true);
writer.write("");
// Contains all but the last node of the setting, e.g. [DataSource, mysql] for "DataSource.mysql.username"
List<String> currentPath = new ArrayList<>();
for (Map.Entry<Property<?>, String[]> entry : propertyMap.entrySet()) {
Property<?> property = entry.getKey();
// Handle properties
List<String> propertyPath = Arrays.asList(property.getPath().split("\\."));
List<String> commonPathParts = CollectionUtils.filterCommonStart(
currentPath, propertyPath.subList(0, propertyPath.size() - 1));
List<String> newPathParts = CollectionUtils.getRange(propertyPath, commonPathParts.size());
if (commonPathParts.isEmpty()) {
writer.append("\n");
}
int indentationLevel = commonPathParts.size();
if (newPathParts.size() > 1) {
for (String path : newPathParts.subList(0, newPathParts.size() - 1)) {
writer.append("\n")
.append(indent(indentationLevel))
.append(path)
.append(": ");
++indentationLevel;
}
}
for (String comment : entry.getValue()) {
writer.append("\n")
.append(indent(indentationLevel))
.append("# ")
.append(comment);
}
writer.append("\n")
.append(indent(indentationLevel))
.append(CollectionUtils.getRange(newPathParts, newPathParts.size() - 1).get(0))
.append(": ")
.append(toYaml(property, indentationLevel, simpleYaml, singleQuoteYaml));
currentPath = propertyPath.subList(0, propertyPath.size() - 1);
}
writer.flush();
writer.close();
} catch (IOException e) {
ConsoleLogger.logException("Could not save config file:", e);
}
}
private void validateAndLoadOptions() {
if (migrationService.checkAndMigrate(configuration, propertyMap, pluginFolder)) {
@Override
protected void validateAndLoadOptions() {
if (migrationService.checkAndMigrate(resource, knownProperties)) {
ConsoleLogger.info("Merged new config options");
ConsoleLogger.info("Please check your config.yml file for new settings!");
save();
@@ -216,11 +92,6 @@ public class Settings {
emailMessage = readEmailMessage();
}
private <T> String toYaml(Property<T> property, int indent, Yaml simpleYaml, Yaml singleQuoteYaml) {
String representation = property.toYaml(configuration, simpleYaml, singleQuoteYaml);
return Joiner.on("\n" + indent(indent)).join(representation.split("\\n"));
}
private File buildMessagesFile() {
String languageCode = getProperty(PluginSettings.MESSAGES_LANGUAGE);
@@ -270,20 +141,4 @@ public class Settings {
}
return "";
}
private static Yaml newYaml(boolean useSingleQuotes) {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
options.setAllowUnicode(true);
if (useSingleQuotes) {
options.setDefaultScalarStyle(DumperOptions.ScalarStyle.SINGLE_QUOTED);
}
return new Yaml(options);
}
private static String indent(int level) {
// We use an indentation of 4 spaces
return Strings.repeat(" ", level * 4);
}
}
@@ -1,17 +1,20 @@
package fr.xephi.authme.settings;
import com.github.authme.configme.migration.PlainMigrationService;
import com.github.authme.configme.properties.Property;
import com.github.authme.configme.propertymap.PropertyEntry;
import com.github.authme.configme.resource.PropertyResource;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.propertymap.PropertyMap;
import org.bukkit.configuration.file.FileConfiguration;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import static com.github.authme.configme.properties.PropertyInitializer.newListProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newProperty;
import static fr.xephi.authme.settings.properties.RegistrationSettings.DELAY_JOIN_MESSAGE;
import static fr.xephi.authme.settings.properties.RegistrationSettings.REMOVE_JOIN_MESSAGE;
import static fr.xephi.authme.settings.properties.RegistrationSettings.REMOVE_LEAVE_MESSAGE;
@@ -22,55 +25,40 @@ import static fr.xephi.authme.settings.properties.RestrictionSettings.FORCE_SPAW
/**
* Service for verifying that the configuration is up-to-date.
*/
public class SettingsMigrationService {
public class SettingsMigrationService extends PlainMigrationService {
/**
* Checks the config file and performs any necessary migrations.
*
* @param configuration The file configuration to check and migrate
* @param propertyMap The property map of all existing properties
* @param pluginFolder The plugin folder
* @return True if there is a change and the config must be saved, false if the config is up-to-date
*/
public boolean checkAndMigrate(FileConfiguration configuration, PropertyMap propertyMap, File pluginFolder) {
return performMigrations(configuration, pluginFolder)
|| hasDeprecatedProperties(configuration)
|| !containsAllSettings(configuration, propertyMap);
private final File pluginFolder;
public SettingsMigrationService(File pluginFolder) {
this.pluginFolder = pluginFolder;
}
private boolean performMigrations(FileConfiguration configuration, File pluginFolder) {
@Override
protected boolean performMigrations(PropertyResource resource, List<PropertyEntry> knownProperties) {
boolean changes = false;
if ("[a-zA-Z0-9_?]*".equals(configuration.getString(ALLOWED_NICKNAME_CHARACTERS.getPath()))) {
configuration.set(ALLOWED_NICKNAME_CHARACTERS.getPath(), "[a-zA-Z0-9_]*");
if ("[a-zA-Z0-9_?]*".equals(resource.getString(ALLOWED_NICKNAME_CHARACTERS.getPath()))) {
resource.setValue(ALLOWED_NICKNAME_CHARACTERS.getPath(), "[a-zA-Z0-9_]*");
changes = true;
}
// Note ljacqu 20160211: Concatenating migration methods with | instead of the usual ||
// ensures that all migrations will be performed
return changes
| performMailTextToFileMigration(configuration, pluginFolder)
| migrateJoinLeaveMessages(configuration)
| migrateForceSpawnSettings(configuration)
| changeBooleanSettingToLogLevelProperty(configuration);
| performMailTextToFileMigration(resource)
| migrateJoinLeaveMessages(resource)
| migrateForceSpawnSettings(resource)
| changeBooleanSettingToLogLevelProperty(resource)
|| hasDeprecatedProperties(resource);
}
public boolean containsAllSettings(FileConfiguration configuration, PropertyMap propertyMap) {
for (Property<?> property : propertyMap.keySet()) {
if (!property.isPresent(configuration)) {
return false;
}
}
return true;
}
private static boolean hasDeprecatedProperties(FileConfiguration configuration) {
private static boolean hasDeprecatedProperties(PropertyResource resource) {
String[] deprecatedProperties = {
"Converter.Rakamak.newPasswordHash", "Hooks.chestshop", "Hooks.legacyChestshop", "Hooks.notifications",
"Passpartu", "Performances", "settings.restrictions.enablePasswordVerifier", "Xenoforo.predefinedSalt",
"VeryGames", "settings.restrictions.allowAllCommandsIfRegistrationIsOptional", "DataSource.mySQLWebsite",
"Hooks.customAttributes", "Security.stop.kickPlayersBeforeStopping"};
for (String deprecatedPath : deprecatedProperties) {
if (configuration.contains(deprecatedPath)) {
if (resource.contains(deprecatedPath)) {
return true;
}
}
@@ -84,18 +72,18 @@ public class SettingsMigrationService {
/**
* Check if {@code Email.mailText} is present and move it to the Email.html file if it doesn't exist yet.
*
* @param configuration The file configuration to verify
* @param pluginFolder The plugin data folder
* @param resource The property resource
* @return True if a migration has been completed, false otherwise
*/
private static boolean performMailTextToFileMigration(FileConfiguration configuration, File pluginFolder) {
private boolean performMailTextToFileMigration(PropertyResource resource) {
final String oldSettingPath = "Email.mailText";
if (!configuration.contains(oldSettingPath)) {
final String oldMailText = resource.getString(oldSettingPath);
if (oldMailText == null) {
return false;
}
final File emailFile = new File(pluginFolder, "email.html");
final String mailText = configuration.getString(oldSettingPath)
final String mailText = oldMailText
.replace("<playername>", "<playername />").replace("%playername%", "<playername />")
.replace("<servername>", "<servername />").replace("%servername%", "<servername />")
.replace("<generatedpass>", "<generatedpass />").replace("%generatedpass%", "<generatedpass />")
@@ -114,12 +102,12 @@ public class SettingsMigrationService {
* Detect deprecated {@code settings.delayJoinLeaveMessages} and inform user of new "remove join messages"
* and "remove leave messages" settings.
*
* @param configuration The file configuration
* @param resource The property resource
* @return True if the configuration has changed, false otherwise
*/
private static boolean migrateJoinLeaveMessages(FileConfiguration configuration) {
Property<Boolean> oldDelayJoinProperty = Property.newProperty("settings.delayJoinLeaveMessages", false);
boolean hasMigrated = moveProperty(oldDelayJoinProperty, DELAY_JOIN_MESSAGE, configuration);
private static boolean migrateJoinLeaveMessages(PropertyResource resource) {
Property<Boolean> oldDelayJoinProperty = newProperty("settings.delayJoinLeaveMessages", false);
boolean hasMigrated = moveProperty(oldDelayJoinProperty, DELAY_JOIN_MESSAGE, resource);
if (hasMigrated) {
ConsoleLogger.info(String.format("Note that we now also have the settings %s and %s",
@@ -132,33 +120,33 @@ public class SettingsMigrationService {
* Detect old "force spawn loc on join" and "force spawn on these worlds" settings and moves them
* to the new paths.
*
* @param configuration The file configuration
* @param resource The property resource
* @return True if the configuration has changed, false otherwise
*/
private static boolean migrateForceSpawnSettings(FileConfiguration configuration) {
Property<Boolean> oldForceLocEnabled = Property.newProperty(
private static boolean migrateForceSpawnSettings(PropertyResource resource) {
Property<Boolean> oldForceLocEnabled = newProperty(
"settings.restrictions.ForceSpawnLocOnJoinEnabled", false);
Property<List<String>> oldForceWorlds = Property.newListProperty(
Property<List<String>> oldForceWorlds = newListProperty(
"settings.restrictions.ForceSpawnOnTheseWorlds", "world", "world_nether", "world_the_ed");
return moveProperty(oldForceLocEnabled, FORCE_SPAWN_LOCATION_AFTER_LOGIN, configuration)
| moveProperty(oldForceWorlds, FORCE_SPAWN_ON_WORLDS, configuration);
return moveProperty(oldForceLocEnabled, FORCE_SPAWN_LOCATION_AFTER_LOGIN, resource)
| moveProperty(oldForceWorlds, FORCE_SPAWN_ON_WORLDS, resource);
}
/**
* Changes the old boolean property "hide spam from console" to the new property specifying
* the log level.
*
* @param configuration The file configuration
* @param resource The property resource
* @return True if the configuration has changed, false otherwise
*/
private static boolean changeBooleanSettingToLogLevelProperty(FileConfiguration configuration) {
private static boolean changeBooleanSettingToLogLevelProperty(PropertyResource resource) {
final String oldPath = "Security.console.noConsoleSpam";
final Property<LogLevel> newProperty = PluginSettings.LOG_LEVEL;
if (!newProperty.isPresent(configuration) && configuration.contains(oldPath)) {
if (!newProperty.isPresent(resource) && resource.contains(oldPath)) {
ConsoleLogger.info("Moving '" + oldPath + "' to '" + newProperty.getPath() + "'");
LogLevel level = configuration.getBoolean(oldPath) ? LogLevel.INFO : LogLevel.FINE;
configuration.set(newProperty.getPath(), level.name());
LogLevel level = Boolean.valueOf(resource.getString(oldPath)) ? LogLevel.INFO : LogLevel.FINE;
resource.setValue(newProperty.getPath(), level.name());
return true;
}
return false;
@@ -169,18 +157,18 @@ public class SettingsMigrationService {
*
* @param oldProperty The old property (create a temporary {@link Property} object with the path)
* @param newProperty The new property to move the value to
* @param configuration The file configuration
* @param resource The property resource
* @param <T> The type of the property
* @return True if a migration has been done, false otherwise
*/
private static <T> boolean moveProperty(Property<T> oldProperty,
Property<T> newProperty,
FileConfiguration configuration) {
if (configuration.contains(oldProperty.getPath())) {
PropertyResource resource) {
if (resource.contains(oldProperty.getPath())) {
ConsoleLogger.info("Detected deprecated property " + oldProperty.getPath());
if (!configuration.contains(newProperty.getPath())) {
if (!resource.contains(newProperty.getPath())) {
ConsoleLogger.info("Renamed " + oldProperty.getPath() + " to " + newProperty.getPath());
configuration.set(newProperty.getPath(), oldProperty.getFromFile(configuration));
resource.setValue(newProperty.getPath(), oldProperty.getValue(resource));
}
return true;
}
@@ -1,17 +0,0 @@
package fr.xephi.authme.settings.domain;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Comment for properties which are also included in the YAML file upon saving.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Comment {
String[] value();
}
@@ -1,49 +0,0 @@
package fr.xephi.authme.settings.domain;
import org.bukkit.configuration.file.FileConfiguration;
import org.yaml.snakeyaml.Yaml;
/**
* Enum property.
*
* @param <E> The enum class
*/
class EnumProperty<E extends Enum<E>> extends Property<E> {
private Class<E> clazz;
public EnumProperty(Class<E> clazz, String path, E defaultValue) {
super(path, defaultValue);
this.clazz = clazz;
}
@Override
public E getFromFile(FileConfiguration configuration) {
String textValue = configuration.getString(getPath());
if (textValue == null) {
return getDefaultValue();
}
E mappedValue = mapToEnum(textValue);
return mappedValue == null ? getDefaultValue() : mappedValue;
}
@Override
public boolean isPresent(FileConfiguration configuration) {
return super.isPresent(configuration) && mapToEnum(configuration.getString(getPath())) != null;
}
@Override
public String toYaml(FileConfiguration configuration, Yaml simpleYaml, Yaml singleQuoteYaml) {
E value = getFromFile(configuration);
return singleQuoteYaml.dump(value.name());
}
private E mapToEnum(String value) {
for (E entry : clazz.getEnumConstants()) {
if (entry.name().equalsIgnoreCase(value)) {
return entry;
}
}
return null;
}
}
@@ -1,235 +0,0 @@
package fr.xephi.authme.settings.domain;
import org.bukkit.configuration.file.FileConfiguration;
import org.yaml.snakeyaml.Yaml;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Property class, representing a <i>setting</i> that is read from the config.yml file.
*/
public abstract class Property<T> {
private final String path;
private final T defaultValue;
protected Property(String path, T defaultValue) {
Objects.requireNonNull(defaultValue);
this.path = path;
this.defaultValue = defaultValue;
}
/**
* Create a new string list property.
*
* @param path The property's path
* @param defaultValues The items in the default list
* @return The created list property
*/
public static Property<List<String>> newListProperty(String path, String... defaultValues) {
// does not have the same name as not to clash with #newProperty(String, String)
return new StringListProperty(path, defaultValues);
}
/**
* Create a new String list property where all values are lowercase.
*
* @param path The property's path
* @param defaultValues The items in the default list
* @return The created list property
*/
public static Property<List<String>> newLowercaseListProperty(String path, String... defaultValues) {
return new LowercaseStringListProperty(path, defaultValues);
}
/**
* Create a new enum property.
*
* @param clazz The enum class
* @param path The property's path
* @param defaultValue The default value
* @param <E> The enum type
* @return The created enum property
*/
public static <E extends Enum<E>> Property<E> newProperty(Class<E> clazz, String path, E defaultValue) {
return new EnumProperty<>(clazz, path, defaultValue);
}
public static Property<Boolean> newProperty(String path, boolean defaultValue) {
return new BooleanProperty(path, defaultValue);
}
public static Property<Integer> newProperty(String path, int defaultValue) {
return new IntegerProperty(path, defaultValue);
}
public static Property<String> newProperty(String path, String defaultValue) {
return new StringProperty(path, defaultValue);
}
/**
* Get the property value from the given configuration &ndash; guaranteed to never return null.
*
* @param configuration The configuration to read the value from
* @return The value, or default if not present
*/
public abstract T getFromFile(FileConfiguration configuration);
/**
* Return whether or not the given configuration file contains the property.
*
* @param configuration The configuration file to verify
* @return True if the property is present, false otherwise
*/
public boolean isPresent(FileConfiguration configuration) {
return configuration.contains(path);
}
/**
* Format the property's value as YAML.
*
* @param configuration The file configuration
* @param simpleYaml YAML object (default)
* @param singleQuoteYaml YAML object using single quotes
* @return The generated YAML
*/
public String toYaml(FileConfiguration configuration, Yaml simpleYaml, Yaml singleQuoteYaml) {
return simpleYaml.dump(getFromFile(configuration));
}
/**
* Return the default value of the property.
*
* @return The default value
*/
public T getDefaultValue() {
return defaultValue;
}
/**
* Return the property path (i.e. the node at which this property is located in the YAML file).
*
* @return The path
*/
public String getPath() {
return path;
}
@Override
public String toString() {
return "Property '" + path + "'";
}
/**
* Boolean property.
*/
private static final class BooleanProperty extends Property<Boolean> {
public BooleanProperty(String path, Boolean defaultValue) {
super(path, defaultValue);
}
@Override
public Boolean getFromFile(FileConfiguration configuration) {
return configuration.getBoolean(getPath(), getDefaultValue());
}
}
/**
* Integer property.
*/
private static final class IntegerProperty extends Property<Integer> {
public IntegerProperty(String path, Integer defaultValue) {
super(path, defaultValue);
}
@Override
public Integer getFromFile(FileConfiguration configuration) {
return configuration.getInt(getPath(), getDefaultValue());
}
}
/**
* String property.
*/
private static final class StringProperty extends Property<String> {
public StringProperty(String path, String defaultValue) {
super(path, defaultValue);
}
@Override
public String getFromFile(FileConfiguration configuration) {
return configuration.getString(getPath(), getDefaultValue());
}
@Override
public String toYaml(FileConfiguration configuration, Yaml simpleYaml, Yaml singleQuoteYaml) {
return singleQuoteYaml.dump(getFromFile(configuration));
}
}
/**
* String list property.
*/
private static class StringListProperty extends Property<List<String>> {
public StringListProperty(String path, String[] defaultValues) {
super(path, Arrays.asList(defaultValues));
}
@Override
public List<String> getFromFile(FileConfiguration configuration) {
if (!configuration.isList(getPath())) {
return getDefaultValue();
}
return configuration.getStringList(getPath());
}
@Override
public boolean isPresent(FileConfiguration configuration) {
return configuration.isList(getPath());
}
@Override
public String toYaml(FileConfiguration configuration, Yaml simpleYaml, Yaml singleQuoteYaml) {
List<String> value = getFromFile(configuration);
String yaml = singleQuoteYaml.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'
return value.isEmpty() ? yaml : "\n" + yaml;
}
}
/**
* Lowercase String list property.
*/
private static final class LowercaseStringListProperty extends StringListProperty {
public LowercaseStringListProperty(String path, String[] defaultValues) {
super(path, defaultValues);
}
@Override
public List<String> getFromFile(FileConfiguration configuration) {
if (!configuration.isList(getPath())) {
return getDefaultValue();
}
// make sure all elements are lowercase
List<String> lowercaseList = new ArrayList<>();
for (String element : configuration.getStringList(getPath())) {
lowercaseList.add(element.toLowerCase());
}
return lowercaseList;
}
}
}
@@ -1,7 +0,0 @@
package fr.xephi.authme.settings.domain;
/**
* Marker for classes that define {@link Property} fields.
*/
public interface SettingsClass {
}
@@ -0,0 +1,33 @@
package fr.xephi.authme.settings.properties;
import com.github.authme.configme.SettingsHolder;
import com.github.authme.configme.properties.Property;
import com.github.authme.configme.propertymap.PropertyEntry;
import com.github.authme.configme.propertymap.SettingsFieldRetriever;
import java.util.List;
/**
* Utility class responsible for retrieving all {@link Property} fields
* from {@link SettingsHolder} implementations via reflection.
*/
public final class AuthMeSettingsRetriever {
private AuthMeSettingsRetriever() {
}
/**
* Constructs a list with all property fields in AuthMe {@link SettingsHolder} classes.
*
* @return list of all known properties
*/
public static List<PropertyEntry> getAllPropertyFields() {
SettingsFieldRetriever retriever = new SettingsFieldRetriever(
DatabaseSettings.class, ConverterSettings.class, PluginSettings.class,
RestrictionSettings.class, EmailSettings.class, HooksSettings.class,
ProtectionSettings.class, PurgeSettings.class, SecuritySettings.class,
RegistrationSettings.class, BackupSettings.class);
return retriever.getAllPropertyFields();
}
}
@@ -1,12 +1,12 @@
package fr.xephi.authme.settings.properties;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import com.github.authme.configme.Comment;
import com.github.authme.configme.SettingsHolder;
import com.github.authme.configme.properties.Property;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newProperty;
public class BackupSettings implements SettingsClass {
public class BackupSettings implements SettingsHolder {
@Comment("Enable or disable automatic backup")
public static final Property<Boolean> ENABLED =
@@ -1,12 +1,12 @@
package fr.xephi.authme.settings.properties;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import com.github.authme.configme.Comment;
import com.github.authme.configme.SettingsHolder;
import com.github.authme.configme.properties.Property;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newProperty;
public class ConverterSettings implements SettingsClass {
public class ConverterSettings implements SettingsHolder {
@Comment("Rakamak file name")
public static final Property<String> RAKAMAK_FILE_NAME =
@@ -1,13 +1,13 @@
package fr.xephi.authme.settings.properties;
import com.github.authme.configme.Comment;
import com.github.authme.configme.SettingsHolder;
import com.github.authme.configme.properties.Property;
import fr.xephi.authme.datasource.DataSourceType;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newProperty;
public class DatabaseSettings implements SettingsClass {
public class DatabaseSettings implements SettingsHolder {
@Comment({"What type of database do you want to use?",
"Valid values: sqlite, mysql"})
@@ -1,15 +1,15 @@
package fr.xephi.authme.settings.properties;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import com.github.authme.configme.Comment;
import com.github.authme.configme.SettingsHolder;
import com.github.authme.configme.properties.Property;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newListProperty;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newListProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newProperty;
public class EmailSettings implements SettingsClass {
public class EmailSettings implements SettingsHolder {
@Comment("Email SMTP server host")
public static final Property<String> SMTP_HOST =
@@ -1,15 +1,15 @@
package fr.xephi.authme.settings.properties;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import com.github.authme.configme.Comment;
import com.github.authme.configme.SettingsHolder;
import com.github.authme.configme.properties.Property;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newListProperty;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newListProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newProperty;
public class HooksSettings implements SettingsClass {
public class HooksSettings implements SettingsHolder {
@Comment("Do we need to hook with multiverse for spawn checking?")
public static final Property<Boolean> MULTIVERSE =
@@ -1,13 +1,13 @@
package fr.xephi.authme.settings.properties;
import com.github.authme.configme.Comment;
import com.github.authme.configme.SettingsHolder;
import com.github.authme.configme.properties.Property;
import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newProperty;
public class PluginSettings implements SettingsClass {
public class PluginSettings implements SettingsHolder {
@Comment("The name shown in the help messages")
public static final Property<String> HELP_HEADER =
@@ -1,16 +1,16 @@
package fr.xephi.authme.settings.properties;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import com.github.authme.configme.Comment;
import com.github.authme.configme.SettingsHolder;
import com.github.authme.configme.properties.Property;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newListProperty;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newListProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newProperty;
public class ProtectionSettings implements SettingsClass {
public class ProtectionSettings implements SettingsHolder {
@Comment("Enable some servers protection (country based login, antibot)")
public static final Property<Boolean> ENABLE_PROTECTION =
@@ -1,12 +1,12 @@
package fr.xephi.authme.settings.properties;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import com.github.authme.configme.Comment;
import com.github.authme.configme.SettingsHolder;
import com.github.authme.configme.properties.Property;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newProperty;
public class PurgeSettings implements SettingsClass {
public class PurgeSettings implements SettingsHolder {
@Comment("If enabled, AuthMe automatically purges old, unused accounts")
public static final Property<Boolean> USE_AUTO_PURGE =
@@ -1,15 +1,15 @@
package fr.xephi.authme.settings.properties;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import com.github.authme.configme.Comment;
import com.github.authme.configme.SettingsHolder;
import com.github.authme.configme.properties.Property;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newListProperty;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newListProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newProperty;
public class RegistrationSettings implements SettingsClass {
public class RegistrationSettings implements SettingsHolder {
@Comment("Enable registration on the server?")
public static final Property<Boolean> IS_ENABLED =
@@ -1,16 +1,16 @@
package fr.xephi.authme.settings.properties;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import com.github.authme.configme.Comment;
import com.github.authme.configme.SettingsHolder;
import com.github.authme.configme.properties.Property;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newListProperty;
import static fr.xephi.authme.settings.domain.Property.newLowercaseListProperty;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newListProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newLowercaseListProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newProperty;
public class RestrictionSettings implements SettingsClass {
public class RestrictionSettings implements SettingsHolder {
@Comment({
"Can not authenticated players chat?",
@@ -1,16 +1,16 @@
package fr.xephi.authme.settings.properties;
import com.github.authme.configme.Comment;
import com.github.authme.configme.SettingsHolder;
import com.github.authme.configme.properties.Property;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import java.util.List;
import static fr.xephi.authme.settings.domain.Property.newLowercaseListProperty;
import static fr.xephi.authme.settings.domain.Property.newProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newLowercaseListProperty;
import static com.github.authme.configme.properties.PropertyInitializer.newProperty;
public class SecuritySettings implements SettingsClass {
public class SecuritySettings implements SettingsHolder {
@Comment({"Stop the server if we can't contact the sql database",
"Take care with this, if you set this to false,",
@@ -1,75 +0,0 @@
package fr.xephi.authme.settings.properties;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import fr.xephi.authme.settings.propertymap.PropertyMap;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
/**
* Utility class responsible for retrieving all {@link Property} fields
* from {@link SettingsClass} implementations via reflection.
*/
public final class SettingsFieldRetriever {
/** The classes to scan for properties. */
private static final List<Class<? extends SettingsClass>> CONFIGURATION_CLASSES = Arrays.asList(
DatabaseSettings.class, ConverterSettings.class, PluginSettings.class,
RestrictionSettings.class, EmailSettings.class, HooksSettings.class,
ProtectionSettings.class, PurgeSettings.class, SecuritySettings.class,
RegistrationSettings.class, BackupSettings.class);
private SettingsFieldRetriever() {
}
/**
* Scan all given classes for their properties and return the generated {@link PropertyMap}.
*
* @return PropertyMap containing all found properties and their associated comments
* @see #CONFIGURATION_CLASSES
*/
public static PropertyMap getAllPropertyFields() {
PropertyMap properties = new PropertyMap();
for (Class<?> clazz : CONFIGURATION_CLASSES) {
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
Property<?> property = getPropertyField(field);
if (property != null) {
properties.put(property, getCommentsForField(field));
}
}
}
return properties;
}
private static String[] getCommentsForField(Field field) {
if (field.isAnnotationPresent(Comment.class)) {
return field.getAnnotation(Comment.class).value();
}
return new String[0];
}
/**
* Return the given field's value if it is a static {@link Property}.
*
* @param field The field's value to return
* @return The property the field defines, or null if not applicable
*/
private static Property<?> getPropertyField(Field field) {
field.setAccessible(true);
if (field.isAccessible() && Property.class.equals(field.getType()) && Modifier.isStatic(field.getModifiers())) {
try {
return (Property<?>) field.get(null);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not fetch field '" + field.getName() + "' from class '"
+ field.getDeclaringClass().getSimpleName() + "'", e);
}
}
return null;
}
}
@@ -1,121 +0,0 @@
package fr.xephi.authme.settings.propertymap;
import fr.xephi.authme.ConsoleLogger;
import java.util.ArrayList;
import java.util.List;
/**
* Node class for building a tree from supplied String paths, ordered by insertion.
* <p>
* For instance, consider a tree to which the following paths are inserted (in the given order):
* "animal.bird.duck", "color.yellow", "animal.rodent.rat", "animal.rodent.rabbit", "color.red".
* For such a tree:<ul>
* <li>"animal" (or any of its children) is sorted before "color" (or any of its children)</li>
* <li>"animal.bird" or any child thereof is sorted before "animal.rodent"</li>
* <li>"animal.rodent.rat" comes before "animal.rodent.rabbit"</li>
* </ul>
*
* @see PropertyMapComparator
*/
final class Node {
private final String name;
private final List<Node> children;
private Node(String name) {
this.name = name;
this.children = new ArrayList<>();
}
/**
* Create a root node, i.e. the starting node for a new tree. Call this method to create
* a new tree and always pass this root to other methods.
*
* @return The generated root node.
*/
public static Node createRoot() {
return new Node(null);
}
/**
* Add a child node, creating any intermediary children that don't exist.
*
* @param fullPath The entire path of the node to add, separate by periods
*/
public void addNode(String fullPath) {
String[] pathParts = fullPath.split("\\.");
Node parent = this;
for (String part : pathParts) {
Node child = parent.getChild(part);
if (child == null) {
child = new Node(part);
parent.children.add(child);
}
parent = child;
}
}
/**
* Compare two nodes by this class' sorting behavior (insertion order).
* Note that this method assumes that both supplied paths exist in the tree.
*
* @param fullPath1 The full path to the first node
* @param fullPath2 The full path to the second node
* @return The comparison result, in the same format as {@link Comparable#compareTo}
*/
public int compare(String fullPath1, String fullPath2) {
String[] path1 = fullPath1.split("\\.");
String[] path2 = fullPath2.split("\\.");
int commonCount = 0;
Node commonNode = this;
while (commonCount < path1.length && commonCount < path2.length
&& path1[commonCount].equals(path2[commonCount]) && commonNode != null) {
commonNode = commonNode.getChild(path1[commonCount]);
++commonCount;
}
if (commonNode == null) {
ConsoleLogger.warning("Could not find common node for '" + fullPath1 + "' at index " + commonCount);
return fullPath1.compareTo(fullPath2); // fallback
} else if (commonCount >= path1.length || commonCount >= path2.length) {
return Integer.compare(path1.length, path2.length);
}
int child1Index = commonNode.getChildIndex(path1[commonCount]);
int child2Index = commonNode.getChildIndex(path2[commonCount]);
return Integer.compare(child1Index, child2Index);
}
private Node getChild(String name) {
for (Node child : children) {
if (child.name.equals(name)) {
return child;
}
}
return null;
}
/**
* Return the child's index, i.e. the position at which it was inserted to its parent.
*
* @param name The name of the node
* @return The insertion index
*/
private int getChildIndex(String name) {
int i = 0;
for (Node child : children) {
if (child.name.equals(name)) {
return i;
}
++i;
}
return -1;
}
@Override
public String toString() {
return "Node '" + name + "'";
}
}
@@ -1,66 +0,0 @@
package fr.xephi.authme.settings.propertymap;
import fr.xephi.authme.settings.domain.Property;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* Class wrapping a {@code Map<Property, String[]>} for storing properties and their associated
* comments with custom ordering.
*
* @see PropertyMapComparator for details about the map's order
*/
public class PropertyMap {
private Map<Property<?>, String[]> map;
private PropertyMapComparator comparator;
/**
* Create a new property map.
*/
public PropertyMap() {
comparator = new PropertyMapComparator();
map = new TreeMap<>(comparator);
}
/**
* Add a new property to the map.
*
* @param property The property to add
* @param comments The comments associated to the property
*/
public void put(Property<?> property, String[] comments) {
comparator.add(property);
map.put(property, comments);
}
/**
* Return the entry set of the map.
*
* @return The entry set
*/
public Set<Map.Entry<Property<?>, String[]>> entrySet() {
return map.entrySet();
}
/**
* Return the key set of the map, i.e. all property objects it holds.
*
* @return The key set
*/
public Set<Property<?>> keySet() {
return map.keySet();
}
/**
* Return the size of the map.
*
* @return The size
*/
public int size() {
return map.size();
}
}
@@ -1,34 +0,0 @@
package fr.xephi.authme.settings.propertymap;
import fr.xephi.authme.settings.domain.Property;
import java.util.Comparator;
/**
* Custom comparator for {@link PropertyMap}. It guarantees that the map's entries:
* <ul>
* <li>are grouped by path, e.g. all "DataSource.mysql" properties are together, and "DataSource.mysql" properties
* are within the broader "DataSource" group.</li>
* <li>are ordered by insertion, e.g. if the first "DataSource" property is inserted before the first "security"
* property, then "DataSource" properties will come before the "security" ones.</li>
* </ul>
*/
final class PropertyMapComparator implements Comparator<Property<?>> {
private Node parent = Node.createRoot();
/**
* Method to call when adding a new property to the map (as to retain its insertion time).
*
* @param property The property that is being added
*/
public void add(Property<?> property) {
parent.addNode(property.getPath());
}
@Override
public int compare(Property<?> p1, Property<?> p2) {
return parent.compare(p1.getPath(), p2.getPath());
}
}