Major refactoring related to how the config and language files are loaded.
parent
68dbd0c521
commit
f9c565b110
|
@ -26,6 +26,7 @@ API:
|
|||
|
||||
Internal:
|
||||
* The config key pattern is cached now.
|
||||
* Major refactoring related to how the config and language files are loaded.
|
||||
|
||||
Migration notes:
|
||||
* The folder structure has changed:
|
||||
|
|
|
@ -1,27 +1,28 @@
|
|||
package com.nisovin.shopkeepers;
|
||||
|
||||
import static com.nisovin.shopkeepers.config.ConfigHelper.loadConfigValue;
|
||||
import static com.nisovin.shopkeepers.config.ConfigHelper.toConfigKey;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bukkit.configuration.Configuration;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import com.nisovin.shopkeepers.config.Config;
|
||||
import com.nisovin.shopkeepers.config.ConfigLoadException;
|
||||
import com.nisovin.shopkeepers.config.annotation.WithDefaultValueType;
|
||||
import com.nisovin.shopkeepers.config.annotation.WithValueTypeProvider;
|
||||
import com.nisovin.shopkeepers.config.value.SettingLoadException;
|
||||
import com.nisovin.shopkeepers.config.value.types.ColoredStringListValue;
|
||||
import com.nisovin.shopkeepers.config.value.types.ColoredStringValue;
|
||||
import com.nisovin.shopkeepers.text.Text;
|
||||
import com.nisovin.shopkeepers.util.Log;
|
||||
import com.nisovin.shopkeepers.util.TextUtils;
|
||||
|
||||
public class Messages {
|
||||
@WithDefaultValueType(fieldType = String.class, valueType = ColoredStringValue.class)
|
||||
@WithValueTypeProvider(ColoredStringListValue.Provider.class)
|
||||
public class Messages extends Config {
|
||||
|
||||
// TODO Replace all with Text? Will require converting back to String, especially for texts used by items.
|
||||
public static String shopTypeAdminRegular = c("Admin shop");
|
||||
|
@ -360,65 +361,80 @@ public class Messages {
|
|||
plugin.saveResource(languageFilePath, false);
|
||||
}
|
||||
|
||||
// Load language config:
|
||||
// Load messages from language config:
|
||||
if (!languageFile.exists()) {
|
||||
Log.warning("Could not find language file '" + languageFile.getName() + "'!");
|
||||
} else {
|
||||
Log.info("Loading language file: " + languageFile.getName());
|
||||
try {
|
||||
YamlConfiguration langConfig = new YamlConfiguration();
|
||||
langConfig.load(languageFile);
|
||||
loadLanguageConfiguration(langConfig);
|
||||
// Load language config:
|
||||
YamlConfiguration languageConfig = new YamlConfiguration();
|
||||
languageConfig.load(languageFile);
|
||||
|
||||
// Load messages:
|
||||
INSTANCE.load(languageConfig);
|
||||
|
||||
// Also update the derived settings:
|
||||
Settings.onSettingsChanged();
|
||||
} catch (Exception e) {
|
||||
Log.warning("Could not load language file '" + languageFile.getName() + "'!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadLanguageConfiguration(Configuration config) throws ConfigLoadException {
|
||||
Set<String> messageKeys = new HashSet<>();
|
||||
try {
|
||||
Field[] fields = Messages.class.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (field.isSynthetic()) continue;
|
||||
if (!Modifier.isPublic(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
Class<?> typeClass = field.getType();
|
||||
Class<?> genericType = null;
|
||||
if (typeClass == List.class) {
|
||||
genericType = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
|
||||
}
|
||||
String configKey = toConfigKey(field.getName());
|
||||
messageKeys.add(configKey);
|
||||
if (!config.isSet(configKey)) {
|
||||
Log.warning(" Missing message: " + configKey);
|
||||
continue; // Skip, keeps current value (default)
|
||||
}
|
||||
/////
|
||||
|
||||
Object value = loadConfigValue(config, configKey, Collections.emptySet(), typeClass, genericType);
|
||||
if (value == null) {
|
||||
Log.warning(" Could not load message: " + configKey);
|
||||
continue; // Skip, keeps current value (default)
|
||||
}
|
||||
field.set(null, value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ConfigLoadException("Error while loading messages from language file!", e);
|
||||
}
|
||||
|
||||
Set<String> configKeys = config.getKeys(false);
|
||||
if (configKeys.size() != messageKeys.size()) {
|
||||
for (String configKey : configKeys) {
|
||||
if (!messageKeys.contains(configKey)) {
|
||||
Log.warning(" Unknown message: " + configKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Settings.onSettingsChanged();
|
||||
}
|
||||
private static final Messages INSTANCE = new Messages();
|
||||
|
||||
private Messages() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLogPrefix() {
|
||||
return "Language: ";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String msgMissingValue(String configKey) {
|
||||
return this.getLogPrefix() + "Missing message: " + configKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String msgUsingDefaultForMissingValue(String configKey, Object defaultValue) {
|
||||
return this.getLogPrefix() + "Using default value for missing message: " + configKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String msgSettingLoadException(String configKey, SettingLoadException e) {
|
||||
return this.getLogPrefix() + "Could not load message '" + configKey + "': " + e.getMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String msgDefaultSettingLoadException(String configKey, SettingLoadException e) {
|
||||
return this.getLogPrefix() + "Could not load default value for message '" + configKey + "': " + e.getMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String msgInsertingDefault(String configKey) {
|
||||
return this.getLogPrefix() + "Inserting default value for missing message: " + configKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String msgMissingDefault(String configKey) {
|
||||
return this.getLogPrefix() + "Missing default value for message: " + configKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateConfig(ConfigurationSection config) throws ConfigLoadException {
|
||||
super.validateConfig(config);
|
||||
|
||||
// Check for unexpected (possibly no longer existing) message keys:
|
||||
Set<String> messageKeys = this.getSettings().stream().map(this::getConfigKey).collect(Collectors.toSet());
|
||||
Set<String> configKeys = config.getKeys(false);
|
||||
for (String configKey : configKeys) {
|
||||
if (!messageKeys.contains(configKey)) {
|
||||
Log.warning(this.getLogPrefix() + "Unknown message: " + configKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,8 @@
|
|||
package com.nisovin.shopkeepers;
|
||||
|
||||
import static com.nisovin.shopkeepers.config.ConfigHelper.loadConfigValue;
|
||||
import static com.nisovin.shopkeepers.config.ConfigHelper.setConfigValue;
|
||||
import static com.nisovin.shopkeepers.config.ConfigHelper.toConfigKey;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
@ -26,6 +18,7 @@ import org.bukkit.inventory.ItemStack;
|
|||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.nisovin.shopkeepers.api.ShopkeepersPlugin;
|
||||
import com.nisovin.shopkeepers.config.Config;
|
||||
import com.nisovin.shopkeepers.config.ConfigLoadException;
|
||||
import com.nisovin.shopkeepers.config.migration.ConfigMigrations;
|
||||
import com.nisovin.shopkeepers.util.ConversionUtils;
|
||||
|
@ -34,9 +27,8 @@ import com.nisovin.shopkeepers.util.ItemData;
|
|||
import com.nisovin.shopkeepers.util.ItemUtils;
|
||||
import com.nisovin.shopkeepers.util.Log;
|
||||
import com.nisovin.shopkeepers.util.PermissionUtils;
|
||||
import com.nisovin.shopkeepers.util.Utils;
|
||||
|
||||
public class Settings {
|
||||
public class Settings extends Config {
|
||||
|
||||
/*
|
||||
* General Settings
|
||||
|
@ -330,7 +322,7 @@ public class Settings {
|
|||
setup();
|
||||
}
|
||||
|
||||
// Gets called after the config has been loaded:
|
||||
// Gets called after setting values have changed (eg. after the config has been loaded):
|
||||
private static void setup() {
|
||||
// Ignore display name (which is used for specifying the new shopkeeper name):
|
||||
namingItemData = new ItemData(ItemUtils.setItemStackName(nameItem.createItemStack(), null));
|
||||
|
@ -349,7 +341,7 @@ public class Settings {
|
|||
try {
|
||||
shopNamePattern = Pattern.compile("^" + Settings.nameRegex + "$");
|
||||
} catch (PatternSyntaxException e) {
|
||||
Log.warning("Config: 'name-regex' is not a valid regular expression ('" + Settings.nameRegex + "'). Reverting to default.");
|
||||
Log.warning(INSTANCE.getLogPrefix() + "'name-regex' is not a valid regular expression ('" + Settings.nameRegex + "'). Reverting to default.");
|
||||
Settings.nameRegex = "[A-Za-z0-9 ]{3,32}";
|
||||
shopNamePattern = Pattern.compile("^" + Settings.nameRegex + "$");
|
||||
}
|
||||
|
@ -363,7 +355,7 @@ public class Settings {
|
|||
// Validate:
|
||||
Integer maxShops = ConversionUtils.parseInt(permOption);
|
||||
if (maxShops == null || maxShops <= 0) {
|
||||
Log.warning("Config: Ignoring invalid entry in 'max-shops-perm-options': " + permOption);
|
||||
Log.warning(INSTANCE.getLogPrefix() + "Ignoring invalid entry in 'max-shops-perm-options': " + permOption);
|
||||
continue;
|
||||
}
|
||||
String permission = "shopkeeper.maxshops." + permOption;
|
||||
|
@ -380,16 +372,16 @@ public class Settings {
|
|||
foundInvalidEntityType = true;
|
||||
if ("PIG_ZOMBIE".equals(entityTypeId)) {
|
||||
// Migration note for MC 1.16 TODO Remove this again at some point?
|
||||
Log.warning("Config: Ignoring mob type 'PIG_ZOMBIE' in setting 'enabled-living-shops'. This mob no longer exist since MC 1.16. Consider replacing it with 'ZOMBIFIED_PIGLIN'.");
|
||||
Log.warning(INSTANCE.getLogPrefix() + "Ignoring mob type 'PIG_ZOMBIE' in setting 'enabled-living-shops'. This mob no longer exist since MC 1.16. Consider replacing it with 'ZOMBIFIED_PIGLIN'.");
|
||||
} else {
|
||||
Log.warning("Config: Invalid living entity type name in 'enabled-living-shops': " + entityTypeId);
|
||||
Log.warning(INSTANCE.getLogPrefix() + "Invalid living entity type name in 'enabled-living-shops': " + entityTypeId);
|
||||
}
|
||||
} else {
|
||||
enabledLivingShops.add(entityType);
|
||||
}
|
||||
}
|
||||
if (foundInvalidEntityType) {
|
||||
Log.warning("Config: All existing entity type names can be found here: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/EntityType.html");
|
||||
Log.warning(INSTANCE.getLogPrefix() + "All existing entity type names can be found here: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/entity/EntityType.html");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -549,7 +541,7 @@ public class Settings {
|
|||
public static ConfigLoadException loadConfig(Plugin plugin) {
|
||||
Log.info("Loading config.");
|
||||
|
||||
// Save default config in case the config file doesn't exist:
|
||||
// Save default config in case the config file does not exist:
|
||||
plugin.saveDefaultConfig();
|
||||
|
||||
// Load config:
|
||||
|
@ -559,7 +551,7 @@ public class Settings {
|
|||
// Load settings from config:
|
||||
boolean configChanged = false;
|
||||
try {
|
||||
configChanged = Settings.loadConfiguration(config);
|
||||
configChanged = loadConfig(config);
|
||||
} catch (ConfigLoadException e) {
|
||||
// Config loading failed with a severe issue:
|
||||
return e;
|
||||
|
@ -573,14 +565,8 @@ public class Settings {
|
|||
return null; // Config loaded successfully
|
||||
}
|
||||
|
||||
// These String / String list settings are exempt from color conversion:
|
||||
private static final Set<String> noColorConversionKeys = new HashSet<>(Arrays.asList(
|
||||
toConfigKey("debugOptions"), toConfigKey("fileEncoding"), toConfigKey("shopCreationItemSpawnEggEntityType"),
|
||||
toConfigKey("maxShopsPermOptions"), toConfigKey("enabledLivingShops"), toConfigKey("nameRegex"),
|
||||
toConfigKey("language")));
|
||||
|
||||
// Returns true, if the config misses values which need to be saved
|
||||
public static boolean loadConfiguration(Configuration config) throws ConfigLoadException {
|
||||
// Returns true, if the config has changed and needs to be saved.
|
||||
private static boolean loadConfig(Configuration config) throws ConfigLoadException {
|
||||
boolean configChanged = false;
|
||||
|
||||
// Perform config migrations:
|
||||
|
@ -589,104 +575,68 @@ public class Settings {
|
|||
configChanged = true;
|
||||
}
|
||||
|
||||
try {
|
||||
Field[] fields = Settings.class.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (field.isSynthetic()) continue;
|
||||
if (!Modifier.isPublic(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
Class<?> typeClass = field.getType();
|
||||
Class<?> genericType = null;
|
||||
if (typeClass == List.class) {
|
||||
genericType = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
|
||||
}
|
||||
String configKey = toConfigKey(field.getName());
|
||||
|
||||
// Initialize the setting with the default value, if it is missing in the config
|
||||
if (!config.isSet(configKey)) {
|
||||
Log.warning("Config: Inserting default value for missing config entry: " + configKey);
|
||||
|
||||
// Determine default value:
|
||||
Configuration defaults = config.getDefaults();
|
||||
Object defaultValue = loadConfigValue(defaults, configKey, noColorConversionKeys, typeClass, genericType);
|
||||
|
||||
// Validate default value:
|
||||
if (defaultValue == null) {
|
||||
Log.warning("Config: Missing default value for missing config entry: " + configKey);
|
||||
continue;
|
||||
} else if (!Utils.isAssignableFrom(typeClass, defaultValue.getClass())) {
|
||||
Log.warning("Config: Default value for missing config entry '" + configKey + "' is of wrong type: "
|
||||
+ "Got " + defaultValue.getClass().getName() + ", expecting " + typeClass.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set default value:
|
||||
setConfigValue(config, configKey, noColorConversionKeys, typeClass, genericType, defaultValue);
|
||||
configChanged = true;
|
||||
}
|
||||
|
||||
// Load value:
|
||||
Object value = loadConfigValue(config, configKey, noColorConversionKeys, typeClass, genericType);
|
||||
field.set(null, value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ConfigLoadException("Error while loading config values!", e);
|
||||
// Insert default values for settings missing inside the config:
|
||||
boolean insertedDefaults = Settings.INSTANCE.insertMissingDefaultValues(config);
|
||||
if (insertedDefaults) {
|
||||
configChanged = true;
|
||||
}
|
||||
|
||||
// Validation:
|
||||
validateSettings();
|
||||
// Load and validate settings:
|
||||
Settings.INSTANCE.load(config);
|
||||
|
||||
onSettingsChanged();
|
||||
return configChanged;
|
||||
}
|
||||
|
||||
private static void validateSettings() {
|
||||
/////
|
||||
|
||||
private static final Settings INSTANCE = new Settings();
|
||||
|
||||
private Settings() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateSettings() {
|
||||
if (maxContainerDistance > 50) {
|
||||
Log.warning("Config: 'max-container-distance' can be at most 50.");
|
||||
Log.warning(this.getLogPrefix() + "'max-container-distance' can be at most 50.");
|
||||
maxContainerDistance = 50;
|
||||
}
|
||||
if (gravityChunkRange < 0) {
|
||||
Log.warning("Config: 'gravity-chunk-range' cannot be negative.");
|
||||
Log.warning(this.getLogPrefix() + "'gravity-chunk-range' cannot be negative.");
|
||||
gravityChunkRange = 0;
|
||||
}
|
||||
// Certain items cannot be of type AIR:
|
||||
if (shopCreationItem.getType() == Material.AIR) {
|
||||
Log.warning("Config: 'shop-creation-item' can not be AIR.");
|
||||
Log.warning(this.getLogPrefix() + "'shop-creation-item' can not be AIR.");
|
||||
shopCreationItem = shopCreationItem.withType(Material.VILLAGER_SPAWN_EGG);
|
||||
}
|
||||
if (hireItem.getType() == Material.AIR) {
|
||||
Log.warning("Config: 'hire-item' can not be AIR.");
|
||||
Log.warning(this.getLogPrefix() + "'hire-item' can not be AIR.");
|
||||
hireItem = hireItem.withType(Material.EMERALD);
|
||||
}
|
||||
if (currencyItem.getType() == Material.AIR) {
|
||||
Log.warning("Config: 'currency-item' can not be AIR.");
|
||||
Log.warning(this.getLogPrefix() + "'currency-item' can not be AIR.");
|
||||
currencyItem = currencyItem.withType(Material.EMERALD);
|
||||
}
|
||||
if (namingOfPlayerShopsViaItem) {
|
||||
if (nameItem.getType() == Material.AIR) {
|
||||
Log.warning("Config: 'name-item' can not be AIR if naming-of-player-shops-via-item is enabled!");
|
||||
Log.warning(this.getLogPrefix() + "'name-item' can not be AIR if naming-of-player-shops-via-item is enabled!");
|
||||
nameItem = nameItem.withType(Material.NAME_TAG);
|
||||
}
|
||||
}
|
||||
if (maxTradesPages < 1) {
|
||||
Log.warning("Config: 'max-trades-pages' can not be less than 1!");
|
||||
Log.warning(this.getLogPrefix() + "'max-trades-pages' can not be less than 1!");
|
||||
maxTradesPages = 1;
|
||||
} else if (maxTradesPages > 10) {
|
||||
Log.warning("Config: 'max-trades-pages' can not be greater than 10!");
|
||||
Log.warning(this.getLogPrefix() + "'max-trades-pages' can not be greater than 10!");
|
||||
maxTradesPages = 10;
|
||||
}
|
||||
if (taxRate < 0) {
|
||||
Log.warning("Config: 'tax-rate' can not be less than 0!");
|
||||
Log.warning(this.getLogPrefix() + "'tax-rate' can not be less than 0!");
|
||||
taxRate = 0;
|
||||
} else if (taxRate > 100) {
|
||||
Log.warning("Config: 'tax-rate' can not be larger than 100!");
|
||||
Log.warning(this.getLogPrefix() + "'tax-rate' can not be larger than 100!");
|
||||
taxRate = 100;
|
||||
}
|
||||
}
|
||||
|
||||
/////
|
||||
|
||||
private Settings() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,485 @@
|
|||
package com.nisovin.shopkeepers.config;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.bukkit.configuration.Configuration;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.nisovin.shopkeepers.config.annotation.Colored;
|
||||
import com.nisovin.shopkeepers.config.annotation.Uncolored;
|
||||
import com.nisovin.shopkeepers.config.annotation.WithDefaultValueType;
|
||||
import com.nisovin.shopkeepers.config.annotation.WithValueType;
|
||||
import com.nisovin.shopkeepers.config.annotation.WithValueTypeProvider;
|
||||
import com.nisovin.shopkeepers.config.value.DefaultValueTypes;
|
||||
import com.nisovin.shopkeepers.config.value.SettingLoadException;
|
||||
import com.nisovin.shopkeepers.config.value.ValueType;
|
||||
import com.nisovin.shopkeepers.config.value.ValueTypeProvider;
|
||||
import com.nisovin.shopkeepers.config.value.ValueTypeRegistry;
|
||||
import com.nisovin.shopkeepers.config.value.types.ColoredStringListValue;
|
||||
import com.nisovin.shopkeepers.config.value.types.ColoredStringValue;
|
||||
import com.nisovin.shopkeepers.config.value.types.StringListValue;
|
||||
import com.nisovin.shopkeepers.config.value.types.StringValue;
|
||||
import com.nisovin.shopkeepers.util.Log;
|
||||
import com.nisovin.shopkeepers.util.Utils;
|
||||
import com.nisovin.shopkeepers.util.Validate;
|
||||
|
||||
public abstract class Config {
|
||||
|
||||
// Custom default value types specified by annotations:
|
||||
// This is lazily setup if required during settings setup.
|
||||
private ValueTypeRegistry customDefaultValueTypes = null;
|
||||
|
||||
// Lazily setup cache of all settings and value types:
|
||||
private Map<Field, ValueType<?>> valueTypes = null;
|
||||
private Collection<Field> settings = null;
|
||||
|
||||
protected Config() {
|
||||
}
|
||||
|
||||
protected String getLogPrefix() {
|
||||
return "Config: ";
|
||||
}
|
||||
|
||||
// SETTINGS SETUP
|
||||
|
||||
private void setupSettings() {
|
||||
if (settings != null) {
|
||||
return; // Already setup
|
||||
}
|
||||
assert valueTypes == null;
|
||||
|
||||
this.valueTypes = new LinkedHashMap<>();
|
||||
this.settings = Collections.unmodifiableSet(valueTypes.keySet());
|
||||
for (Field field : Utils.toIterable(this.streamSettings())) {
|
||||
ValueType<?> valueType = this.setupValueType(field);
|
||||
assert valueType != null;
|
||||
valueTypes.put(field, valueType);
|
||||
}
|
||||
|
||||
// The custom default value types are only required during the setup:
|
||||
customDefaultValueTypes = null;
|
||||
}
|
||||
|
||||
private Stream<Field> streamSettings() {
|
||||
Class<?> configClass = this.getClass();
|
||||
Stream<Field> settings = this.streamSettings(configClass);
|
||||
|
||||
// Append settings of parent config classes (allows for composition of config classes):
|
||||
// We stop once we reach this class in the type hierarchy:
|
||||
Class<?> parentClass = configClass.getSuperclass();
|
||||
assert parentClass != null;
|
||||
while (parentClass != Config.class) {
|
||||
settings = Stream.concat(settings, this.streamSettings(parentClass));
|
||||
parentClass = configClass.getSuperclass();
|
||||
assert parentClass != null;
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
private final Stream<Field> streamSettings(Class<?> configClass) {
|
||||
List<Field> fields = Arrays.asList(configClass.getDeclaredFields());
|
||||
return fields.stream().filter(field -> {
|
||||
// Filter fields:
|
||||
if (field.isSynthetic()) return false;
|
||||
if (Modifier.isFinal(field.getModifiers())) return false;
|
||||
if (!this.isSetting(field)) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be used to exclude fields from the settings.
|
||||
* <p>
|
||||
* By default, all public fields are included. Fields can also be declared in parent config classes.
|
||||
* <p>
|
||||
* Synthetic and final fields are always excluded.
|
||||
*
|
||||
* @param field
|
||||
* the field
|
||||
* @return <code>true</code> if the field is a setting, <code>false</code> otherwise
|
||||
*/
|
||||
protected boolean isSetting(Field field) {
|
||||
if (!Modifier.isPublic(field.getModifiers())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Assert: Does not return null.
|
||||
protected <T> ValueType<T> setupValueType(Field field) {
|
||||
ValueType<T> valueType = this.getValueTypeByAnnotation(field);
|
||||
if (valueType != null) return valueType;
|
||||
|
||||
valueType = this.getValueTypeByColoredAnnotation(field);
|
||||
if (valueType != null) return valueType;
|
||||
|
||||
valueType = this.getValueTypeByUncoloredAnnotation(field);
|
||||
if (valueType != null) return valueType;
|
||||
|
||||
valueType = this.getValueTypeByCustomDefaults(field);
|
||||
if (valueType != null) return valueType;
|
||||
|
||||
Type fieldType = field.getGenericType();
|
||||
valueType = DefaultValueTypes.get(fieldType);
|
||||
if (valueType != null) return valueType;
|
||||
|
||||
// ValueType could not be determined:
|
||||
String configKey = this.getConfigKey(field);
|
||||
throw new IllegalStateException("Setting '" + configKey + "' is of unsupported type: " + fieldType.getTypeName());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final <T> ValueType<T> getValueTypeByAnnotation(Field field) {
|
||||
WithValueType valueTypeAnnotation = field.getAnnotation(WithValueType.class);
|
||||
if (valueTypeAnnotation != null) {
|
||||
Class<? extends ValueType<?>> valueTypeClass = valueTypeAnnotation.value();
|
||||
assert valueTypeClass != null;
|
||||
return (ValueType<T>) instantiateValueType(valueTypeClass);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ValueType<?> instantiateValueType(Class<? extends ValueType<?>> valueTypeClass) {
|
||||
assert valueTypeClass != null;
|
||||
try {
|
||||
return valueTypeClass.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Could not instantiate ValueType: " + valueTypeClass.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final <T> ValueType<T> getValueTypeByColoredAnnotation(Field field) {
|
||||
Colored coloredAnnotation = field.getAnnotation(Colored.class);
|
||||
if (coloredAnnotation != null) {
|
||||
Type fieldType = field.getGenericType();
|
||||
if (fieldType == String.class) {
|
||||
return (ValueType<T>) ColoredStringValue.INSTANCE;
|
||||
} else if (ColoredStringListValue.TYPE_PATTERN.matches(fieldType)) {
|
||||
return (ValueType<T>) ColoredStringListValue.INSTANCE;
|
||||
} else {
|
||||
throw new IllegalArgumentException("The Colored annotation is not supported for settings of type " + fieldType.getTypeName());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final <T> ValueType<T> getValueTypeByUncoloredAnnotation(Field field) {
|
||||
Uncolored uncoloredAnnotation = field.getAnnotation(Uncolored.class);
|
||||
if (uncoloredAnnotation != null) {
|
||||
Type fieldType = field.getGenericType();
|
||||
if (fieldType == String.class) {
|
||||
return (ValueType<T>) StringValue.INSTANCE;
|
||||
} else if (ColoredStringListValue.TYPE_PATTERN.matches(fieldType)) {
|
||||
return (ValueType<T>) StringListValue.INSTANCE;
|
||||
} else {
|
||||
throw new IllegalArgumentException("The Uncolored annotation is not supported for settings of type " + fieldType.getTypeName());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected final <T> ValueType<T> getValueTypeByCustomDefaults(Field field) {
|
||||
this.setupCustomDefaultValueTypes(); // Lazy setup
|
||||
assert customDefaultValueTypes != null;
|
||||
Type fieldType = field.getGenericType();
|
||||
return customDefaultValueTypes.getValueType(fieldType);
|
||||
}
|
||||
|
||||
private void setupCustomDefaultValueTypes() {
|
||||
if (this.customDefaultValueTypes != null) {
|
||||
return; // Already setup.
|
||||
}
|
||||
this.customDefaultValueTypes = new ValueTypeRegistry();
|
||||
Class<?> configClass = this.getClass();
|
||||
this.setupCustomDefaultValueTypes(configClass);
|
||||
|
||||
// Also take into account custom default value types specified in parent classes:
|
||||
Class<?> parentClass = configClass.getSuperclass();
|
||||
assert parentClass != null;
|
||||
while (parentClass != Config.class) {
|
||||
this.setupCustomDefaultValueTypes(parentClass);
|
||||
parentClass = configClass.getSuperclass();
|
||||
assert parentClass != null;
|
||||
}
|
||||
}
|
||||
|
||||
private void setupCustomDefaultValueTypes(Class<?> configClass) {
|
||||
assert customDefaultValueTypes != null;
|
||||
// WithDefaultValueType annotations:
|
||||
WithDefaultValueType[] defaultValueTypeAnnotations = configClass.getAnnotationsByType(WithDefaultValueType.class);
|
||||
assert defaultValueTypeAnnotations != null;
|
||||
for (WithDefaultValueType defaultValueTypeAnnotation : defaultValueTypeAnnotations) {
|
||||
Class<?> fieldType = defaultValueTypeAnnotation.fieldType();
|
||||
if (customDefaultValueTypes.hasCachedValueType(fieldType)) {
|
||||
// Only the first encountered default value type specification is used:
|
||||
continue;
|
||||
}
|
||||
|
||||
Class<? extends ValueType<?>> valueTypeClass = defaultValueTypeAnnotation.valueType();
|
||||
assert valueTypeClass != null;
|
||||
ValueType<?> valueType = instantiateValueType(valueTypeClass);
|
||||
assert valueType != null; // Else: Exception is thrown.
|
||||
customDefaultValueTypes.register(fieldType, valueType);
|
||||
}
|
||||
|
||||
// WithValueTypeProvider annotations:
|
||||
WithValueTypeProvider[] valueTypeProviderAnnotations = configClass.getAnnotationsByType(WithValueTypeProvider.class);
|
||||
assert valueTypeProviderAnnotations != null;
|
||||
for (WithValueTypeProvider valueTypeProviderAnnotation : valueTypeProviderAnnotations) {
|
||||
Class<? extends ValueTypeProvider> valueTypeProviderClass = valueTypeProviderAnnotation.value();
|
||||
assert valueTypeProviderClass != null;
|
||||
ValueTypeProvider valueTypeProvider = instantiateValueTypeProvider(valueTypeProviderClass);
|
||||
assert valueTypeProvider != null; // Else: Exception is thrown.
|
||||
customDefaultValueTypes.register(valueTypeProvider);
|
||||
}
|
||||
}
|
||||
|
||||
private static ValueTypeProvider instantiateValueTypeProvider(Class<? extends ValueTypeProvider> valueTypeProviderClass) {
|
||||
assert valueTypeProviderClass != null;
|
||||
try {
|
||||
return valueTypeProviderClass.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Could not instantiate ValueTypeProvider: " + valueTypeProviderClass.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// SETTINGS
|
||||
|
||||
/**
|
||||
* Gets the setting fields.
|
||||
*
|
||||
* @return an unmodifiable view on the setting fields
|
||||
*/
|
||||
protected final Collection<Field> getSettings() {
|
||||
this.setupSettings();
|
||||
return settings;
|
||||
}
|
||||
|
||||
protected String getConfigKey(Field field) {
|
||||
return ConfigHelper.toConfigKey(field.getName());
|
||||
}
|
||||
|
||||
protected final Field getSetting(String configKey) {
|
||||
for (Field field : this.getSettings()) {
|
||||
if (this.getConfigKey(field).equals(configKey)) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Assert: Does not return null. Expects a valid setting field.
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final <T> ValueType<T> getValueType(Field field) {
|
||||
this.setupSettings();
|
||||
ValueType<T> valueType = (ValueType<T>) valueTypes.get(field);
|
||||
Validate.notNull(valueType, "Field is not a setting: " + field.getName());
|
||||
return valueType;
|
||||
}
|
||||
|
||||
// LOADING
|
||||
|
||||
public void load(ConfigurationSection config) throws ConfigLoadException {
|
||||
Validate.notNull(config, "config is null");
|
||||
this.validateConfig(config);
|
||||
for (Field field : this.getSettings()) {
|
||||
this.loadSetting(field, config);
|
||||
}
|
||||
this.validateSettings();
|
||||
}
|
||||
|
||||
protected void validateConfig(ConfigurationSection config) throws ConfigLoadException {
|
||||
}
|
||||
|
||||
protected <T> void loadSetting(Field field, ConfigurationSection config) throws ConfigLoadException {
|
||||
String configKey = this.getConfigKey(field);
|
||||
ValueType<T> valueType = this.getValueType(field);
|
||||
try {
|
||||
T value = null;
|
||||
|
||||
// Handle missing value:
|
||||
if (!config.isSet(configKey)) {
|
||||
// We use the default value, if there is one:
|
||||
value = this.getDefaultValue(field, config); // Can be null
|
||||
this.onValueMissing(field, config, value);
|
||||
} else {
|
||||
// Load value:
|
||||
value = valueType.load(config, configKey);
|
||||
assert value != null; // We expect an exception if the value cannot be loaded
|
||||
}
|
||||
|
||||
// Apply value:
|
||||
if (value != null) {
|
||||
this.setSetting(field, value);
|
||||
} // Else: Retain previous value.
|
||||
} catch (SettingLoadException e) {
|
||||
this.onSettingLoadException(field, config, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> void onValueMissing(Field field, ConfigurationSection config, T defaultValue) throws ConfigLoadException {
|
||||
String configKey = this.getConfigKey(field);
|
||||
if (defaultValue == null) {
|
||||
Log.warning(this.msgMissingValue(configKey));
|
||||
} else {
|
||||
Log.warning(this.msgUsingDefaultForMissingValue(configKey, defaultValue));
|
||||
}
|
||||
}
|
||||
|
||||
protected String msgMissingValue(String configKey) {
|
||||
return this.getLogPrefix() + "Missing config entry: " + configKey;
|
||||
}
|
||||
|
||||
protected String msgUsingDefaultForMissingValue(String configKey, Object defaultValue) {
|
||||
return this.getLogPrefix() + "Using default value for missing config entry: " + configKey;
|
||||
}
|
||||
|
||||
protected <T> void onSettingLoadException(Field field, ConfigurationSection config, SettingLoadException e) throws ConfigLoadException {
|
||||
String configKey = this.getConfigKey(field);
|
||||
Log.warning(this.msgSettingLoadException(configKey, e));
|
||||
for (String extraMessage : e.getExtraMessages()) {
|
||||
Log.warning(this.getLogPrefix() + extraMessage);
|
||||
}
|
||||
}
|
||||
|
||||
protected String msgSettingLoadException(String configKey, SettingLoadException e) {
|
||||
return this.getLogPrefix() + "Could not load setting '" + configKey + "': " + e.getMessage();
|
||||
}
|
||||
|
||||
protected void setSetting(Field field, Object value) throws SettingLoadException {
|
||||
if (value != null) {
|
||||
Class<?> fieldType = field.getType();
|
||||
if (!Utils.isAssignableFrom(fieldType, value.getClass())) {
|
||||
throw new SettingLoadException("Value is of wrong type: Got " + value.getClass().getName() + ", expected " + fieldType.getName());
|
||||
}
|
||||
}
|
||||
|
||||
boolean accessible = field.isAccessible();
|
||||
try {
|
||||
if (!accessible) {
|
||||
// Temporarily set the field accessible, for example for private fields:
|
||||
field.setAccessible(true);
|
||||
}
|
||||
// Note: The config instance is ignored if the field is static.
|
||||
field.set(this, value);
|
||||
} catch (Exception e) {
|
||||
throw new SettingLoadException(e.getMessage(), e);
|
||||
} finally {
|
||||
// Restore previous accessible state:
|
||||
try {
|
||||
field.setAccessible(accessible);
|
||||
} catch (SecurityException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation once all settings have been loaded.
|
||||
*/
|
||||
protected void validateSettings() {
|
||||
}
|
||||
|
||||
// DEFAULT VALUES
|
||||
|
||||
// Checks whether there are default values available.
|
||||
// This has to be consistent with #getDefaultValue.
|
||||
protected boolean hasDefaultValues(ConfigurationSection config) {
|
||||
return config instanceof Configuration && ((Configuration) config).getDefaults() != null;
|
||||
}
|
||||
|
||||
// Returns null if there is no default value.
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T getDefaultValue(Field field, ConfigurationSection config) {
|
||||
Configuration defaults = null;
|
||||
if (config instanceof Configuration) {
|
||||
defaults = ((Configuration) config).getDefaults();
|
||||
}
|
||||
if (defaults == null) {
|
||||
// No default config values available.
|
||||
return null;
|
||||
}
|
||||
|
||||
String configKey = this.getConfigKey(field);
|
||||
ValueType<?> valueType = this.getValueType(field);
|
||||
|
||||
// Load default value:
|
||||
try {
|
||||
// Note: This can return null if the default config does not contain a default value for this setting.
|
||||
return (T) valueType.load(defaults, configKey);
|
||||
} catch (SettingLoadException e) {
|
||||
Log.warning(this.msgDefaultSettingLoadException(configKey, e));
|
||||
for (String extraMessage : e.getExtraMessages()) {
|
||||
Log.warning(this.getLogPrefix() + extraMessage);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected String msgDefaultSettingLoadException(String configKey, SettingLoadException e) {
|
||||
return this.getLogPrefix() + "Could not load default value for setting '" + configKey + "': " + e.getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the default values for missing settings into the given config.
|
||||
*
|
||||
* @param config
|
||||
* the config
|
||||
* @return <code>true</code> if any default values have been inserted
|
||||
*/
|
||||
protected boolean insertMissingDefaultValues(ConfigurationSection config) {
|
||||
Validate.notNull(config, "config is null");
|
||||
if (!this.hasDefaultValues(config)) {
|
||||
// No default config values available.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize missing settings with their default value:
|
||||
boolean configChanged = false;
|
||||
for (Field field : this.getSettings()) {
|
||||
if (this.insertMissingDefaultValue(config, field)) {
|
||||
configChanged = true;
|
||||
}
|
||||
}
|
||||
return configChanged;
|
||||
}
|
||||
|
||||
// Returns true if the default value got inserted.
|
||||
protected <T> boolean insertMissingDefaultValue(ConfigurationSection config, Field field) {
|
||||
assert this.hasDefaultValues(config);
|
||||
String configKey = this.getConfigKey(field);
|
||||
if (config.isSet(configKey)) return false; // Not missing.
|
||||
|
||||
Log.warning(this.msgInsertingDefault(configKey));
|
||||
|
||||
// Get default value:
|
||||
T defaultValue = this.getDefaultValue(field, config);
|
||||
if (defaultValue == null) {
|
||||
Log.warning(this.msgMissingDefault(configKey));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save default value to config:
|
||||
ValueType<T> valueType = this.getValueType(field);
|
||||
valueType.save(config, configKey, defaultValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected String msgInsertingDefault(String configKey) {
|
||||
return this.getLogPrefix() + "Inserting default value for missing config entry: " + configKey;
|
||||
}
|
||||
|
||||
protected String msgMissingDefault(String configKey) {
|
||||
return this.getLogPrefix() + "Missing default value for setting: " + configKey;
|
||||
}
|
||||
}
|
|
@ -1,21 +1,7 @@
|
|||
package com.nisovin.shopkeepers.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.Configuration;
|
||||
|
||||
import com.nisovin.shopkeepers.text.Text;
|
||||
import com.nisovin.shopkeepers.util.ConfigUtils;
|
||||
import com.nisovin.shopkeepers.util.ItemData;
|
||||
import com.nisovin.shopkeepers.util.Log;
|
||||
import com.nisovin.shopkeepers.util.TextUtils;
|
||||
|
||||
public class ConfigHelper {
|
||||
|
||||
|
@ -54,119 +40,4 @@ public class ConfigHelper {
|
|||
public static String toConfigKey(String fieldName) {
|
||||
return CONFIG_KEY_PATTERN.matcher(fieldName).replaceAll("-$1").toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
public static Object loadConfigValue(Configuration config, String configKey, Set<String> noColorConversionKeys, Class<?> typeClass, Class<?> genericType) {
|
||||
if (typeClass == String.class || typeClass == Text.class) {
|
||||
String string = config.getString(configKey);
|
||||
// Colorize, if not exempted:
|
||||
if (!noColorConversionKeys.contains(configKey)) {
|
||||
string = TextUtils.colorize(string);
|
||||
}
|
||||
if (typeClass == Text.class) {
|
||||
return Text.parse(string);
|
||||
} else {
|
||||
return string;
|
||||
}
|
||||
} else if (typeClass == int.class) {
|
||||
return config.getInt(configKey);
|
||||
} else if (typeClass == short.class) {
|
||||
return (short) config.getInt(configKey);
|
||||
} else if (typeClass == boolean.class) {
|
||||
return config.getBoolean(configKey);
|
||||
} else if (typeClass == Material.class) {
|
||||
// This assumes that legacy item conversion has already been performed
|
||||
Material material = ConfigUtils.loadMaterial(config, configKey);
|
||||
if (material == null) {
|
||||
Log.warning("Config: Unknown material for config entry '" + configKey + "': " + config.get(configKey));
|
||||
Log.warning("Config: All valid material names can be found here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html");
|
||||
}
|
||||
return material;
|
||||
} else if (typeClass == ItemData.class) {
|
||||
ItemData itemData = loadItemData(config.get(configKey), configKey);
|
||||
// Normalize to not null:
|
||||
if (itemData == null) {
|
||||
itemData = new ItemData(Material.AIR);
|
||||
}
|
||||
return itemData;
|
||||
} else if (typeClass == List.class) {
|
||||
if (genericType == String.class || genericType == Text.class) {
|
||||
List<String> stringList = config.getStringList(configKey);
|
||||
// Colorize, if not exempted:
|
||||
if (!noColorConversionKeys.contains(configKey)) {
|
||||
stringList = TextUtils.colorize(stringList);
|
||||
}
|
||||
if (genericType == Text.class) {
|
||||
return Text.parse(stringList);
|
||||
} else {
|
||||
return stringList;
|
||||
}
|
||||
} else if (genericType == ItemData.class) {
|
||||
List<?> list = config.getList(configKey, Collections.emptyList());
|
||||
List<ItemData> itemDataList = new ArrayList<>(list.size());
|
||||
int index = 0;
|
||||
for (Object entry : list) {
|
||||
index += 1;
|
||||
ItemData itemData = loadItemData(entry, configKey + "[" + index + "]");
|
||||
if (itemData != null) {
|
||||
itemDataList.add(itemData);
|
||||
}
|
||||
}
|
||||
return itemDataList;
|
||||
} else {
|
||||
throw new IllegalStateException("Unsupported config setting list type: " + genericType.getName());
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Unsupported config setting type: " + typeClass.getName());
|
||||
}
|
||||
|
||||
public static ItemData loadItemData(Object dataObject, String configEntryIdentifier) {
|
||||
ItemData itemData = ItemData.deserialize(dataObject, (warning) -> {
|
||||
Log.warning("Config: Couldn't load item data for config entry '" + configEntryIdentifier + "': " + warning);
|
||||
if (warning.contains("Unknown item type")) { // TODO this is ugly
|
||||
Log.warning("Config: All valid material names can be found here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html");
|
||||
}
|
||||
});
|
||||
return itemData;
|
||||
}
|
||||
|
||||
public static void setConfigValue(Configuration config, String configKey, Set<String> noColorConversionKeys, Class<?> typeClass, Class<?> genericType, Object value) {
|
||||
if (value == null) {
|
||||
// Remove value:
|
||||
config.set(configKey, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeClass == Material.class) {
|
||||
config.set(configKey, ((Material) value).name());
|
||||
} else if (typeClass == String.class || typeClass == Text.class) {
|
||||
String stringValue;
|
||||
if (typeClass == Text.class) {
|
||||
stringValue = ((Text) value).toPlainFormatText();
|
||||
} else {
|
||||
stringValue = (String) value;
|
||||
}
|
||||
// Decolorize, if not exempted:
|
||||
if (!noColorConversionKeys.contains(configKey)) {
|
||||
value = TextUtils.decolorize(stringValue);
|
||||
}
|
||||
config.set(configKey, value);
|
||||
} else if (typeClass == List.class && (genericType == String.class || genericType == Text.class)) {
|
||||
List<String> stringList;
|
||||
if (genericType == Text.class) {
|
||||
stringList = ((List<Text>) value).stream().map(Text::toPlainFormatText).collect(Collectors.toList());
|
||||
} else {
|
||||
stringList = (List<String>) value;
|
||||
}
|
||||
|
||||
// Decolorize, if not exempted:
|
||||
if (!noColorConversionKeys.contains(configKey)) {
|
||||
value = TextUtils.decolorize(stringList);
|
||||
}
|
||||
config.set(configKey, value);
|
||||
} else if (typeClass == ItemData.class) {
|
||||
config.set(configKey, ((ItemData) value).serialize());
|
||||
} else {
|
||||
config.set(configKey, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.nisovin.shopkeepers.config.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.types.ColoredStringListValue;
|
||||
import com.nisovin.shopkeepers.config.value.types.ColoredStringValue;
|
||||
|
||||
/**
|
||||
* Shortcut for using {@link ColoredStringValue} or {@link ColoredStringListValue}.
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Colored {
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.nisovin.shopkeepers.config.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.types.StringListValue;
|
||||
import com.nisovin.shopkeepers.config.value.types.StringValue;
|
||||
|
||||
/**
|
||||
* Shortcut for using {@link StringValue} or {@link StringListValue}.
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Uncolored {
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.nisovin.shopkeepers.config.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Repeatable;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.DefaultValueTypes;
|
||||
import com.nisovin.shopkeepers.config.value.ValueType;
|
||||
|
||||
/**
|
||||
* Specifies the default {@link ValueType} to use for config fields of a specific type.
|
||||
* <p>
|
||||
* This overrides the defaults specified by {@link DefaultValueTypes}.
|
||||
* <p>
|
||||
* The specified value type has to provide a no-args constructor.
|
||||
* <p>
|
||||
* Default value types specified by this annotation are inherited to subclasses and can be overridden there by other
|
||||
* annotations for the same field type. If multiple default value types are specified for the same field type, the first
|
||||
* one is used.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Repeatable(WithDefaultValueTypes.class)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface WithDefaultValueType {
|
||||
|
||||
Class<?> fieldType();
|
||||
|
||||
Class<? extends ValueType<?>> valueType();
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.nisovin.shopkeepers.config.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Container annotation for multiple instances of {@link WithDefaultValueType}.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface WithDefaultValueTypes {
|
||||
|
||||
WithDefaultValueType[] value();
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.nisovin.shopkeepers.config.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.ValueType;
|
||||
|
||||
/**
|
||||
* Specifies the {@link ValueType} to use for a specific config field.
|
||||
* <p>
|
||||
* The specified value type has to provide a no-args constructor.
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface WithValueType {
|
||||
|
||||
Class<? extends ValueType<?>> value();
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.nisovin.shopkeepers.config.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Repeatable;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.DefaultValueTypes;
|
||||
import com.nisovin.shopkeepers.config.value.ValueType;
|
||||
import com.nisovin.shopkeepers.config.value.ValueTypeProvider;
|
||||
|
||||
/**
|
||||
* Specifies a {@link ValueTypeProvider} to use for config fields.
|
||||
* <p>
|
||||
* This overrides the defaults specified by {@link DefaultValueTypes}.
|
||||
* <p>
|
||||
* The specified value type provider has to provide a no-args constructor.
|
||||
* <p>
|
||||
* Default value type providers specified by this annotation are inherited to subclasses. The first provider (starting
|
||||
* with those specified at the most specific class) that can provide a {@link ValueType} is used.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Repeatable(WithValueTypeProviders.class)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface WithValueTypeProvider {
|
||||
|
||||
Class<? extends ValueTypeProvider> value();
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.nisovin.shopkeepers.config.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Container annotation for multiple instances of {@link WithValueTypeProvider}.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface WithValueTypeProviders {
|
||||
|
||||
WithValueTypeProvider[] value();
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package com.nisovin.shopkeepers.config.value;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Material;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.types.BooleanValue;
|
||||
import com.nisovin.shopkeepers.config.value.types.DoubleValue;
|
||||
import com.nisovin.shopkeepers.config.value.types.IntegerValue;
|
||||
import com.nisovin.shopkeepers.config.value.types.ItemDataValue;
|
||||
import com.nisovin.shopkeepers.config.value.types.ListValue;
|
||||
import com.nisovin.shopkeepers.config.value.types.LongValue;
|
||||
import com.nisovin.shopkeepers.config.value.types.MaterialValue;
|
||||
import com.nisovin.shopkeepers.config.value.types.StringValue;
|
||||
import com.nisovin.shopkeepers.config.value.types.TextValue;
|
||||
import com.nisovin.shopkeepers.text.Text;
|
||||
import com.nisovin.shopkeepers.util.ItemData;
|
||||
|
||||
/**
|
||||
* Registry of default value types of settings.
|
||||
*/
|
||||
public class DefaultValueTypes {
|
||||
|
||||
private static final ValueTypeRegistry registry = new ValueTypeRegistry();
|
||||
|
||||
static {
|
||||
registry.register(String.class, StringValue.INSTANCE);
|
||||
registry.register(Boolean.class, BooleanValue.INSTANCE);
|
||||
registry.register(boolean.class, BooleanValue.INSTANCE);
|
||||
registry.register(Integer.class, IntegerValue.INSTANCE);
|
||||
registry.register(int.class, IntegerValue.INSTANCE);
|
||||
registry.register(Double.class, DoubleValue.INSTANCE);
|
||||
registry.register(double.class, DoubleValue.INSTANCE);
|
||||
registry.register(Long.class, LongValue.INSTANCE);
|
||||
registry.register(long.class, LongValue.INSTANCE);
|
||||
|
||||
registry.register(Text.class, TextValue.INSTANCE);
|
||||
registry.register(Material.class, MaterialValue.INSTANCE);
|
||||
registry.register(ItemData.class, ItemDataValue.INSTANCE);
|
||||
|
||||
registry.register(ValueTypeProviders.forTypePattern(TypePatterns.forClass(List.class), (type) -> {
|
||||
assert type instanceof ParameterizedType;
|
||||
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
|
||||
ValueType<?> elementValueType = DefaultValueTypes.get(elementType);
|
||||
if (elementValueType == null) {
|
||||
throw new IllegalArgumentException("Unsupported element type: " + elementType.getTypeName());
|
||||
}
|
||||
return new ListValue<>(elementValueType);
|
||||
}));
|
||||
}
|
||||
|
||||
public static <T> ValueType<T> get(Type type) {
|
||||
return registry.getValueType(type);
|
||||
}
|
||||
|
||||
private DefaultValueTypes() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package com.nisovin.shopkeepers.config.value;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class SettingLoadException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -3068903999888105245L;
|
||||
|
||||
private final List<String> extraMessages;
|
||||
|
||||
public SettingLoadException(String message) {
|
||||
this(message, Collections.emptyList());
|
||||
}
|
||||
|
||||
public SettingLoadException(String message, List<String> extraMessages) {
|
||||
super(message);
|
||||
this.extraMessages = extraMessages;
|
||||
}
|
||||
|
||||
public SettingLoadException(String message, Throwable cause) {
|
||||
this(message, Collections.emptyList(), cause);
|
||||
}
|
||||
|
||||
public SettingLoadException(String message, List<String> extraMessages, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.extraMessages = extraMessages;
|
||||
}
|
||||
|
||||
public List<String> getExtraMessages() {
|
||||
return extraMessages;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.nisovin.shopkeepers.config.value;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public interface TypePattern {
|
||||
|
||||
public boolean matches(Type type);
|
||||
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package com.nisovin.shopkeepers.config.value;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import com.nisovin.shopkeepers.util.Validate;
|
||||
|
||||
public class TypePatterns {
|
||||
|
||||
public static TypePattern forClass(Class<?> clazz) {
|
||||
return new ClassTypePattern(clazz);
|
||||
}
|
||||
|
||||
private static class ClassTypePattern implements TypePattern {
|
||||
|
||||
private final Class<?> clazz;
|
||||
|
||||
public ClassTypePattern(Class<?> clazz) {
|
||||
Validate.notNull(clazz, "clazz is null");
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Type type) {
|
||||
if (this.clazz == type) return true;
|
||||
if (type instanceof ParameterizedType) {
|
||||
if (this.clazz == ((ParameterizedType) type).getRawType()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static TypePattern parameterized(Class<?> clazz, TypePattern... typeParameters) {
|
||||
return new ParameterizedTypePattern(clazz, typeParameters);
|
||||
}
|
||||
|
||||
public static TypePattern parameterized(Class<?> clazz, Class<?>... typeParameters) {
|
||||
TypePattern[] typePatterns = null;
|
||||
if (typeParameters != null) {
|
||||
typePatterns = new TypePattern[typeParameters.length];
|
||||
for (int i = 0; i < typeParameters.length; ++i) {
|
||||
Class<?> typeParameter = typeParameters[i];
|
||||
Validate.notNull(typeParameter, "One of the typeParameters is null!");
|
||||
typePatterns[i] = TypePatterns.forClass(typeParameter);
|
||||
}
|
||||
}
|
||||
return parameterized(clazz, typePatterns);
|
||||
}
|
||||
|
||||
private static class ParameterizedTypePattern extends ClassTypePattern {
|
||||
|
||||
private final TypePattern[] typeParameters;
|
||||
|
||||
public ParameterizedTypePattern(Class<?> clazz, TypePattern... typeParameters) {
|
||||
super(clazz);
|
||||
Validate.notNull(typeParameters, "typeParameters is null");
|
||||
Validate.notNull(typeParameters.length == 0, "typeParameters is empty");
|
||||
this.typeParameters = (typeParameters == null) ? null : typeParameters.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Type type) {
|
||||
if (!super.matches(type)) return false;
|
||||
if (!(type instanceof ParameterizedType)) {
|
||||
return false;
|
||||
}
|
||||
Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
|
||||
if (typeArguments == null || typeArguments.length != this.typeParameters.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < typeParameters.length; ++i) {
|
||||
if (!this.typeParameters[i].matches(typeArguments[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static TypePattern any() {
|
||||
return AnyTypePattern.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches any type.
|
||||
*/
|
||||
private static class AnyTypePattern implements TypePattern {
|
||||
|
||||
public static final AnyTypePattern INSTANCE = new AnyTypePattern();
|
||||
|
||||
public AnyTypePattern() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Type type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private TypePatterns() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package com.nisovin.shopkeepers.config.value;
|
||||
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
|
||||
import com.nisovin.shopkeepers.config.annotation.WithDefaultValueType;
|
||||
import com.nisovin.shopkeepers.config.annotation.WithValueType;
|
||||
|
||||
/**
|
||||
* Defines how values of this type are loaded from and saved to configs.
|
||||
* <p>
|
||||
* Subtypes should ideally provide a public no-args constructor so that they can be used together with the
|
||||
* {@link WithValueType} and {@link WithDefaultValueType} annotations.
|
||||
*
|
||||
* @param <T>
|
||||
* the type of value
|
||||
*/
|
||||
public abstract class ValueType<T> {
|
||||
|
||||
public ValueType() {
|
||||
}
|
||||
|
||||
public T load(ConfigurationSection config, String key) throws SettingLoadException {
|
||||
Object configValue = config.get(key);
|
||||
return this.load(configValue);
|
||||
}
|
||||
|
||||
public T load(ConfigurationSection config, String key, T defaultValue) {
|
||||
T value = null;
|
||||
try {
|
||||
value = this.load(config, key);
|
||||
} catch (SettingLoadException e) {
|
||||
}
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// Null indicates the absence of a value and should only be used if the input has been null.
|
||||
public abstract T load(Object configValue) throws SettingLoadException;
|
||||
|
||||
// A value of null will clear the config entry.
|
||||
public void save(ConfigurationSection config, String key, T value) {
|
||||
Object configValue = this.save(value); // Can be null
|
||||
config.set(key, configValue);
|
||||
}
|
||||
|
||||
public abstract Object save(T value);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.nisovin.shopkeepers.config.value;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public interface ValueTypeProvider {
|
||||
|
||||
/**
|
||||
* Gets the {@link ValueType} for the given setting type, if it can provide one.
|
||||
* <p>
|
||||
* The returned {@link ValueType} may get cached and used for future requests for the same setting type. The
|
||||
* provider is therefore only allowed to take the type itself into account, and not any other contextual state.
|
||||
*
|
||||
* @param type
|
||||
* the type of the setting's value
|
||||
* @return the value type, or <code>null</code> if none can be provided
|
||||
*/
|
||||
public ValueType<?> get(Type type);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.nisovin.shopkeepers.config.value;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.nisovin.shopkeepers.util.Validate;
|
||||
|
||||
public class ValueTypeProviders {
|
||||
|
||||
public static ValueTypeProvider forTypePattern(TypePattern typePattern, Function<Type, ValueType<?>> valueTypeProvider) {
|
||||
Validate.notNull(typePattern, "typePattern is null");
|
||||
Validate.notNull(valueTypeProvider, "valueTypeProvider is null");
|
||||
return new ValueTypeProvider() {
|
||||
@Override
|
||||
public ValueType<?> get(Type type) {
|
||||
if (typePattern.matches(type)) {
|
||||
return valueTypeProvider.apply(type);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ValueTypeProviders() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package com.nisovin.shopkeepers.config.value;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.nisovin.shopkeepers.util.Validate;
|
||||
|
||||
/**
|
||||
* Registry of value types for setting types.
|
||||
*/
|
||||
public class ValueTypeRegistry {
|
||||
|
||||
private final Map<Type, ValueType<?>> byType = new HashMap<>();
|
||||
// Ordered: The first successful provider is used.
|
||||
private final List<ValueTypeProvider> providers = new ArrayList<>();
|
||||
|
||||
public ValueTypeRegistry() {
|
||||
}
|
||||
|
||||
// Replaces any previously registered ValueType.
|
||||
public <T> void register(Type type, ValueType<? extends T> valueType) {
|
||||
Validate.notNull(type, "type is null");
|
||||
Validate.notNull(valueType, "valueType is null");
|
||||
byType.put(type, valueType);
|
||||
}
|
||||
|
||||
public boolean hasCachedValueType(Type type) {
|
||||
return byType.containsKey(type);
|
||||
}
|
||||
|
||||
public void register(ValueTypeProvider valueTypeProvider) {
|
||||
Validate.notNull(valueTypeProvider, "valueTypeProvider is null");
|
||||
providers.add(valueTypeProvider);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> ValueType<T> getValueType(Type type) {
|
||||
ValueType<T> valueType = (ValueType<T>) byType.get(type);
|
||||
if (valueType == null) {
|
||||
// Check providers:
|
||||
for (ValueTypeProvider provider : providers) {
|
||||
valueType = (ValueType<T>) provider.get(type);
|
||||
if (valueType != null) {
|
||||
// Cache result:
|
||||
this.register(type, valueType);
|
||||
break;
|
||||
} // Else: Continue searching.
|
||||
}
|
||||
}
|
||||
return valueType;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.nisovin.shopkeepers.config.value.types;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.ValueType;
|
||||
import com.nisovin.shopkeepers.config.value.SettingLoadException;
|
||||
import com.nisovin.shopkeepers.util.ConversionUtils;
|
||||
|
||||
public class BooleanValue extends ValueType<Boolean> {
|
||||
|
||||
public static final BooleanValue INSTANCE = new BooleanValue();
|
||||
|
||||
public BooleanValue() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean load(Object configValue) throws SettingLoadException {
|
||||
if (configValue == null) return null;
|
||||
Boolean value = ConversionUtils.toBoolean(configValue);
|
||||
if (value == null) {
|
||||
throw new SettingLoadException("Invalid boolean value: " + configValue);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object save(Boolean value) {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.nisovin.shopkeepers.config.value.types;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.TypePattern;
|
||||
import com.nisovin.shopkeepers.config.value.TypePatterns;
|
||||
import com.nisovin.shopkeepers.config.value.ValueType;
|
||||
import com.nisovin.shopkeepers.config.value.ValueTypeProvider;
|
||||
import com.nisovin.shopkeepers.config.value.ValueTypeProviders;
|
||||
|
||||
public class ColoredStringListValue extends ListValue<String> {
|
||||
|
||||
public static final ColoredStringListValue INSTANCE = new ColoredStringListValue();
|
||||
public static final TypePattern TYPE_PATTERN = TypePatterns.parameterized(List.class, String.class);
|
||||
public static final ValueTypeProvider PROVIDER = ValueTypeProviders.forTypePattern(TYPE_PATTERN, type -> INSTANCE);
|
||||
|
||||
public static final class Provider implements ValueTypeProvider {
|
||||
@Override
|
||||
public ValueType<?> get(Type type) {
|
||||
return PROVIDER.get(type);
|
||||
}
|
||||
}
|
||||
|
||||
public ColoredStringListValue() {
|
||||
super(ColoredStringValue.INSTANCE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.nisovin.shopkeepers.config.value.types;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.SettingLoadException;
|
||||
import com.nisovin.shopkeepers.util.TextUtils;
|
||||
|
||||
public class ColoredStringValue extends StringValue {
|
||||
|
||||
public static final ColoredStringValue INSTANCE = new ColoredStringValue();
|
||||
|
||||
public ColoredStringValue() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String load(Object configValue) throws SettingLoadException {
|
||||
return TextUtils.colorize(super.load(configValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object save(String value) {
|
||||
return TextUtils.decolorize(value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.nisovin.shopkeepers.config.value.types;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.ValueType;
|
||||
import com.nisovin.shopkeepers.config.value.SettingLoadException;
|
||||
import com.nisovin.shopkeepers.util.ConversionUtils;
|
||||
|
||||
public class DoubleValue extends ValueType<Double> {
|
||||
|
||||
public static final DoubleValue INSTANCE = new DoubleValue();
|
||||
|
||||
public DoubleValue() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double load(Object configValue) throws SettingLoadException {
|
||||
if (configValue == null) return null;
|
||||
Double value = ConversionUtils.toDouble(configValue);
|
||||
if (value == null) {
|
||||
throw new SettingLoadException("Invalid double value: " + configValue);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object save(Double value) {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.nisovin.shopkeepers.config.value.types;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.ValueType;
|
||||
import com.nisovin.shopkeepers.config.value.SettingLoadException;
|
||||
import com.nisovin.shopkeepers.util.ConversionUtils;
|
||||
|
||||
public class IntegerValue extends ValueType<Integer> {
|
||||
|
||||
public static final IntegerValue INSTANCE = new IntegerValue();
|
||||
|
||||
public IntegerValue() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer load(Object configValue) throws SettingLoadException {
|
||||
if (configValue == null) return null;
|
||||
Integer value = ConversionUtils.toInteger(configValue);
|
||||
if (value == null) {
|
||||
throw new SettingLoadException("Invalid integer value: " + configValue);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object save(Integer value) {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package com.nisovin.shopkeepers.config.value.types;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.SettingLoadException;
|
||||
import com.nisovin.shopkeepers.config.value.ValueType;
|
||||
import com.nisovin.shopkeepers.util.ItemData;
|
||||
|
||||
public class ItemDataValue extends ValueType<ItemData> {
|
||||
|
||||
public static final ItemDataValue INSTANCE = new ItemDataValue();
|
||||
|
||||
public ItemDataValue() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemData load(Object configValue) throws SettingLoadException {
|
||||
ItemData itemData = null;
|
||||
try {
|
||||
// Returns null if the config value is null. Otherwise triggers a warning, which we translate into an
|
||||
// exception.
|
||||
itemData = ItemData.deserialize(configValue, (warning) -> {
|
||||
List<String> extraMessages = Collections.emptyList();
|
||||
if (warning.contains("Unknown item type")) { // TODO this is ugly
|
||||
extraMessages = Arrays.asList(
|
||||
"All valid material names can be found here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html"
|
||||
);
|
||||
}
|
||||
// We can only throw unchecked exceptions here, so we wrap the exception here and unwrap it again
|
||||
// outside:
|
||||
throw new RuntimeException(new SettingLoadException(warning, extraMessages));
|
||||
});
|
||||
} catch (RuntimeException e) {
|
||||
if (e.getCause() instanceof SettingLoadException) {
|
||||
throw (SettingLoadException) e.getCause();
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return itemData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object save(ItemData value) {
|
||||
if (value == null) return null;
|
||||
return value.serialize();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package com.nisovin.shopkeepers.config.value.types;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.SettingLoadException;
|
||||
import com.nisovin.shopkeepers.config.value.ValueType;
|
||||
import com.nisovin.shopkeepers.util.Validate;
|
||||
|
||||
public class ListValue<E> extends ValueType<List<E>> {
|
||||
|
||||
private final ValueType<E> elementValueType;
|
||||
|
||||
public ListValue(ValueType<E> elementValueType) {
|
||||
Validate.notNull(elementValueType, "elementValueType is null!");
|
||||
this.elementValueType = elementValueType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<E> load(Object configValue) throws SettingLoadException {
|
||||
if (configValue == null) return null;
|
||||
if (!(configValue instanceof List<?>)) {
|
||||
throw new SettingLoadException("Expecting a list of values, but got " + configValue.getClass().getName());
|
||||
}
|
||||
List<?> configValues = (List<?>) configValue;
|
||||
List<E> values = new ArrayList<>(configValues.size());
|
||||
for (Object configElement : configValues) {
|
||||
values.add(elementValueType.load(configElement));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object save(List<E> value) {
|
||||
if (value == null) return null;
|
||||
List<Object> configValues = new ArrayList<>(value.size());
|
||||
for (E element : value) {
|
||||
configValues.add(elementValueType.save(element));
|
||||
}
|
||||
return configValues;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.nisovin.shopkeepers.config.value.types;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.ValueType;
|
||||
import com.nisovin.shopkeepers.config.value.SettingLoadException;
|
||||
import com.nisovin.shopkeepers.util.ConversionUtils;
|
||||
|
||||
public class LongValue extends ValueType<Long> {
|
||||
|
||||
public static final LongValue INSTANCE = new LongValue();
|
||||
|
||||
public LongValue() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long load(Object configValue) throws SettingLoadException {
|
||||
if (configValue == null) return null;
|
||||
Long value = ConversionUtils.toLong(configValue);
|
||||
if (value == null) {
|
||||
throw new SettingLoadException("Invalid long value: " + configValue);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object save(Long value) {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package com.nisovin.shopkeepers.config.value.types;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bukkit.Material;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.SettingLoadException;
|
||||
import com.nisovin.shopkeepers.config.value.ValueType;
|
||||
|
||||
public class MaterialValue extends ValueType<Material> {
|
||||
|
||||
public static final MaterialValue INSTANCE = new MaterialValue();
|
||||
|
||||
private static final StringValue stringValue = new StringValue();
|
||||
|
||||
public MaterialValue() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Material load(Object configValue) throws SettingLoadException {
|
||||
String materialName = stringValue.load(configValue);
|
||||
if (materialName == null) return null;
|
||||
// This assumes that legacy item conversion has already been performed:
|
||||
Material material = Material.matchMaterial(materialName); // Can be null
|
||||
if (material == null) {
|
||||
throw new SettingLoadException("Unknown material: " + materialName, Arrays.asList(
|
||||
"All valid material names can be found here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html"
|
||||
));
|
||||
}
|
||||
return material;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object save(Material value) {
|
||||
if (value == null) return null;
|
||||
return value.name();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.nisovin.shopkeepers.config.value.types;
|
||||
|
||||
public class StringListValue extends ListValue<String> {
|
||||
|
||||
public static final StringListValue INSTANCE = new StringListValue();
|
||||
|
||||
public StringListValue() {
|
||||
super(StringValue.INSTANCE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.nisovin.shopkeepers.config.value.types;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.SettingLoadException;
|
||||
import com.nisovin.shopkeepers.config.value.ValueType;
|
||||
|
||||
public class StringValue extends ValueType<String> {
|
||||
|
||||
public static final StringValue INSTANCE = new StringValue();
|
||||
|
||||
public StringValue() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String load(Object configValue) throws SettingLoadException {
|
||||
if (configValue == null) return null;
|
||||
return configValue.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object save(String value) {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.nisovin.shopkeepers.config.value.types;
|
||||
|
||||
import com.nisovin.shopkeepers.config.value.SettingLoadException;
|
||||
import com.nisovin.shopkeepers.config.value.ValueType;
|
||||
import com.nisovin.shopkeepers.text.Text;
|
||||
|
||||
public class TextValue extends ValueType<Text> {
|
||||
|
||||
public static final TextValue INSTANCE = new TextValue();
|
||||
|
||||
private static final ColoredStringValue coloredStringValue = new ColoredStringValue();
|
||||
|
||||
public TextValue() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Text load(Object configValue) throws SettingLoadException {
|
||||
return Text.parse(coloredStringValue.load(configValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object save(Text value) {
|
||||
if (value == null) return null;
|
||||
return coloredStringValue.save(value.toPlainFormatText());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue