diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/Moved.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/Moved.java index 07bb907e..07844d20 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/Moved.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/Moved.java @@ -16,5 +16,18 @@ import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Moved{ - public String newPath() default ""; + + /** + * The new path where the content has moved to. + * @return + */ + public String newPath() default ""; + + /** + * Only added to be able to explicitly deny moving configuration sections. + * Moving configuration sections might not actually be supported. + * + * @return If to allow moving configuration sections. + */ + public boolean configurationSection() default false; } diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/PathUtils.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/PathUtils.java index 019273f1..ee144ab1 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/PathUtils.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/PathUtils.java @@ -12,9 +12,7 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.logging.Logger; -import org.bukkit.Bukkit; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.MemoryConfiguration; @@ -25,6 +23,8 @@ import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.SimpleCharPrefixTree; public class PathUtils { + // TODO: Make this a class to be able to process multiple files with different paths and annotations. + // Deprecated paths. private static final Set deprecatedFields = new LinkedHashSet(); private static final SimpleCharPrefixTree deprecatedPrefixes = new SimpleCharPrefixTree(); @@ -34,7 +34,7 @@ public class PathUtils { private static final SimpleCharPrefixTree globalOnlyPrefixes = new SimpleCharPrefixTree(); // Paths moved to other paths. - private static final Map movedPaths = new LinkedHashMap(); + private static final Map movedPaths = new LinkedHashMap(); static{ initPaths(); @@ -44,14 +44,14 @@ public class PathUtils { * Initialize annotation-based path properties. * @return */ - private static void initPaths(){ + private static void initPaths() { deprecatedFields.clear(); deprecatedPrefixes.clear(); globalOnlyFields.clear(); globalOnlyPrefixes.clear(); movedPaths.clear(); - for (final Field field : ConfPaths.class.getDeclaredFields()){ - if (field.getType() != String.class){ + for (final Field field : ConfPaths.class.getDeclaredFields()) { + if (field.getType() != String.class) { // Only process strings. continue; } @@ -59,7 +59,7 @@ public class PathUtils { checkAddPrefixes(field, fieldName, GlobalConfig.class, globalOnlyFields, globalOnlyPrefixes); checkAddPrefixes(field, fieldName, Deprecated.class, deprecatedFields, deprecatedPrefixes); - if (field.isAnnotationPresent(Moved.class)){ + if (field.isAnnotationPresent(Moved.class)) { // TODO: Prefixes: Might later support relocating entire sections with one annotation? addMoved(field, field.getAnnotation(Moved.class)); } @@ -67,13 +67,13 @@ public class PathUtils { } private static void checkAddPrefixes(Field field, String fieldName, Class annotation, Set fieldNames, SimpleCharPrefixTree pathPrefixes) { - if (field.isAnnotationPresent(annotation)){ + if (field.isAnnotationPresent(annotation)) { fieldNames.add(fieldName); addPrefixesField(field, pathPrefixes); } - else{ - for (final String refName : fieldNames){ - if (fieldName.startsWith(refName)){ + else { + for (final String refName : fieldNames) { + if (fieldName.startsWith(refName)) { addPrefixesField(field, pathPrefixes); } } @@ -83,7 +83,7 @@ public class PathUtils { private static void addPrefixesField(Field field, SimpleCharPrefixTree pathPrefixes) { try { final String path = field.get(null).toString(); - if (path != null){ + if (path != null) { pathPrefixes.feed(path); } } catch (IllegalArgumentException e) { @@ -94,7 +94,7 @@ public class PathUtils { private static void addMoved(final Field field, final Moved rel) { try { final String path = field.get(null).toString(); - movedPaths.put(path, rel.newPath()); + movedPaths.put(path, rel); } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } @@ -107,12 +107,11 @@ public class PathUtils { * @param msgHeader * @param warnedPaths Paths which were found, can be null. */ - protected static void warnPaths(final ConfigFile config, final CharPrefixTree paths, final String msgPrefix, final Set warnedPaths){ - final Logger logger = Bukkit.getLogger(); - for (final String path : config.getKeys(true)){ - if (paths.hasPrefix(path)){ - logger.warning("[NoCheatPlus] Config path '" + path + "'" + msgPrefix); - if (warnedPaths != null){ + protected static void warnPaths(final ConfigurationSection config, final CharPrefixTree paths, final String msgPrefix, final Set warnedPaths) { + for (final String path : config.getKeys(true)) { + if (paths.hasPrefix(path)) { + StaticLog.logWarning("[NoCheatPlus] Config path '" + path + "'" + msgPrefix); + if (warnedPaths != null) { warnedPaths.add(path); } } @@ -120,36 +119,21 @@ public class PathUtils { } /** - * Run all warning checks and alter config if necessary (GlobalConfig, Deprecated, Moved). + * Run all warning checks and alter the configuration file if necessary (GlobalConfig, Deprecated, Moved). * @param file * @param configName */ - public static void processPaths(File file, String configName, boolean isWorldConfig){ + public static void processPaths(File file, String configName, boolean isWorldConfig) { ConfigFile config = new ConfigFile(); try { config.load(file); - final Set removePaths = new LinkedHashSet(); - final Map addPaths = new LinkedHashMap(); - if (isWorldConfig){ - // TODO: might remove these [though some global only paths might actually work]. - processGlobalOnlyPaths(config, configName, null); - } - processDeprecatedPaths(config, configName, removePaths); - processMovedPaths(config, configName, removePaths, addPaths); - boolean changed = false; - if (!removePaths.isEmpty()){ - config = removePaths(config, removePaths); - changed = true; - } - if (!addPaths.isEmpty()){ - setPaths(config, addPaths); - changed = true; - } - if (changed){ + ConfigFile newConfig = processPaths(config, configName, isWorldConfig); + if (newConfig != null) { + config = newConfig; try{ config.save(file); } - catch(Throwable t){ + catch(Throwable t) { // Do log this one. StaticLog.logSevere("[NoCheatPlus] Failed to save configuration (" + configName + ") with changes: " + t.getClass().getSimpleName()); StaticLog.logSevere(t); @@ -161,14 +145,67 @@ public class PathUtils { } } + /** + * Process configuration annotations, might change the given configuration or return a new one. + * @param config Configuration instance. + * @param configName A name for logging errors. + * @param isWorldConfig + * @return A new configuration instance if paths had to be removed (i.e. a + * new one was needed), null if the same configuration instance + * could be used (paths still might have been added). + */ + public static ConfigFile processPaths(ConfigFile config, final String configName, final boolean isWorldConfig) { + final Set removePaths = new LinkedHashSet(); + final Map addPaths = new LinkedHashMap(); + if (isWorldConfig) { + // TODO: might remove these [though some global only paths might actually work]. + processGlobalOnlyPaths(config, configName, null); + } + processDeprecatedPaths(config, configName, removePaths); + processMovedPaths(config, configName, removePaths, addPaths); + boolean changed = false; + if (!removePaths.isEmpty()) { + config = removePaths(config, removePaths); + changed = true; + } + if (!addPaths.isEmpty()) { + setPaths(config, addPaths, false); + changed = true; + } + if (changed) { + return config; + } else { + return null; + } + } + /** * Set paths. * @param config * @param addPaths */ - public static void setPaths(final ConfigFile config, final Map setPaths) { - for (final Entry entry : setPaths.entrySet()){ - config.set(entry.getKey(), entry.getValue()); + public static void setPaths(final ConfigurationSection config, final Map setPaths, final boolean override) { + setPaths(config, setPaths, "", override); + } + + /** + * Recursive version supporting ConfigurationSection. + * @param config + * @param setPaths + * @param pathPrefix + */ + protected static void setPaths(final ConfigurationSection config, final Map setPaths, final String pathPrefix, final boolean override) { + for (final Entry entry : setPaths.entrySet()) { + final String path = entry.getKey(); + final Object value = entry.getValue(); + if (value instanceof ConfigurationSection) { + // Deep or not should not matter here. + setPaths(config, ((ConfigurationSection) value).getValues(true), pathPrefix + path + ".", override); + } else { + if (override || !config.contains(path)) { + config.set(pathPrefix + path, value); + } + } } } @@ -180,17 +217,18 @@ public class PathUtils { */ public static ConfigFile removePaths(final ConfigFile config, final Collection removePaths) { final SimpleCharPrefixTree prefixes = new SimpleCharPrefixTree(); - for (final String path : removePaths){ + for (final String path : removePaths) { prefixes.feed(path); } final ConfigFile newConfig = new ConfigFile(); - for (final Entry entry : config.getValues(true).entrySet()){ + for (final Entry entry : config.getValues(true).entrySet()) { final String path = entry.getKey(); final Object value = entry.getValue(); - if (value instanceof ConfigurationSection){ + // TODO: To support moving entire sections, this needs to be changed. + if (value instanceof ConfigurationSection) { continue; } - if (!prefixes.hasPrefix(path)){ + if (!prefixes.hasPrefix(path)) { newConfig.set(path, value); } } @@ -203,26 +241,32 @@ public class PathUtils { * @param configName * @param removePaths * @param addPaths - * @return If entries were added (paths to be removed are processed later). */ - protected static void processMovedPaths(final ConfigFile config, final String configName, final Set removePaths, final Map addPaths) { - final Logger logger = Bukkit.getLogger(); - for (final Entry entry : movedPaths.entrySet()){ + protected static void processMovedPaths(final ConfigurationSection config, final String configName, final Set removePaths, final Map addPaths) { + for (final Entry entry : movedPaths.entrySet()) { final String path = entry.getKey(); - if (config.contains(path)){ - final String newPath = entry.getValue(); + final Object value = config.get(path); + if (config.contains(path)) { + final Moved moved = entry.getValue(); + if (value instanceof ConfigurationSection) { + if (moved.configurationSection()) { + // TODO: Ensure those can be processed at all. + } else { + // Ignore configuration sections. + continue; + } + } + final String newPath = moved.newPath(); final String to; - if (newPath == null | newPath.isEmpty()){ + if (newPath == null || newPath.isEmpty()) { to = "."; } - else{ + else { to = " to '" + newPath + "'."; - final Object value = config.get(path); - config.set(newPath, value); addPaths.put(newPath, value); removePaths.add(path); } - logger.warning("[NoCheatPlus] Config path '" + path + "' (" + configName + ") has been moved" + to); + StaticLog.logWarning("[NoCheatPlus] Config path '" + path + "' (" + configName + ") has been moved" + to); } } } @@ -233,7 +277,7 @@ public class PathUtils { * @param paths * @param configName */ - protected static void processDeprecatedPaths(ConfigFile config, String configName, final Set removePaths){ + protected static void processDeprecatedPaths(ConfigurationSection config, String configName, final Set removePaths) { warnPaths(config, deprecatedPrefixes, " (" + configName + ") is not in use anymore.", removePaths); } @@ -243,7 +287,7 @@ public class PathUtils { * @param paths * @param configName */ - protected static void processGlobalOnlyPaths(ConfigFile config, String configName, final Set removePaths){ + protected static void processGlobalOnlyPaths(ConfigurationSection config, String configName, final Set removePaths) { warnPaths(config, globalOnlyPrefixes, " (" + configName + ") should only be set in the global configuration.", removePaths); } @@ -252,41 +296,61 @@ public class PathUtils { * @param defaultConfig * @return */ - public static MemoryConfiguration getWorldsDefaultConfig(final ConfigFile defaultConfig){ + public static MemoryConfiguration getWorldsDefaultConfig(final MemoryConfiguration defaultConfig) { final char sep = defaultConfig.options().pathSeparator(); final MemoryConfiguration config = new ConfigFile(); config.options().pathSeparator(sep); final Map defaults = defaultConfig.getValues(false); - for (final Entry entry : defaults.entrySet()){ + for (final Entry entry : defaults.entrySet()) { final String part = entry.getKey(); - if (!part.isEmpty() && !mayBeInWorldConfig(part)) continue; + if (!part.isEmpty() && !mayBeInWorldConfig(part)) { + continue; + } final Object value = entry.getValue(); - if (value instanceof ConfigurationSection) addWorldConfigSection(config, (ConfigurationSection) value, part, sep); - else config.set(part, value); + if (value instanceof ConfigurationSection) { + addWorldConfigSection(config, (ConfigurationSection) value, part, sep); + } + else { + config.set(part, value); + } } return config; } - protected static void addWorldConfigSection(final MemoryConfiguration config, final ConfigurationSection section, final String path, final char sep) { + protected static void addWorldConfigSection(final ConfigurationSection config, final ConfigurationSection section, final String path, final char sep) { final Map values = section.getValues(false); - for (final Entry entry : values.entrySet()){ + for (final Entry entry : values.entrySet()) { final String fullPath = path + sep + entry.getKey(); - if (!mayBeInWorldConfig(fullPath)) continue; + if (!mayBeInWorldConfig(fullPath)) { + continue; + } final Object value = entry.getValue(); - if (value instanceof ConfigurationSection) addWorldConfigSection(config, (ConfigurationSection) value, fullPath, sep); - else config.set(fullPath, value); + if (value instanceof ConfigurationSection) { + addWorldConfigSection(config, (ConfigurationSection) value, fullPath, sep); + } + else { + config.set(fullPath, value); + } } } - public static boolean mayBeInWorldConfig(final String path){ - if (globalOnlyPrefixes.hasPrefix(path)) return false; - return mayBeInConfig(path); + public static boolean mayBeInWorldConfig(final String path) { + if (globalOnlyPrefixes.hasPrefix(path)) { + return false; + } else { + return mayBeInConfig(path); + } } - public static boolean mayBeInConfig(final String path){ - if (deprecatedPrefixes.hasPrefix(path)) return false; - if (movedPaths.containsKey(path)) return false; - return true; + public static boolean mayBeInConfig(final String path) { + if (deprecatedPrefixes.hasPrefix(path)) { + return false; + } + else if (movedPaths.containsKey(path)) { + return false; + } else { + return true; + } } } diff --git a/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestConfig.java b/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestConfig.java index b7d11566..7311fa4a 100644 --- a/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestConfig.java +++ b/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestConfig.java @@ -5,36 +5,61 @@ import static org.junit.Assert.fail; import org.bukkit.Material; import org.junit.Test; +import fr.neatmonster.nocheatplus.config.ConfPaths; +import fr.neatmonster.nocheatplus.config.ConfigFile; +import fr.neatmonster.nocheatplus.config.PathUtils; import fr.neatmonster.nocheatplus.config.RawConfigFile; public class TestConfig { - - private void testReadMaterial(String input, Material expectedMat) { - Material mat = RawConfigFile.parseMaterial(input); - if (expectedMat != mat) { - fail("Expected " + expectedMat + " for input '" + input + "', got instead: " + mat); - } - } - - @SuppressWarnings("deprecation") - @Test - public void testReadMaterial() { - // Some really needed parts first. - testReadMaterial("water lily", Material.WATER_LILY); - testReadMaterial("water-lily", Material.WATER_LILY); - testReadMaterial("watEr_lily", Material.WATER_LILY); - - testReadMaterial("flint and steel", Material.FLINT_AND_STEEL); - testReadMaterial("259", Material.FLINT_AND_STEEL); - - // Generic test. - for (final Material mat : Material.values()) { - if (mat.name().equalsIgnoreCase("LOCKED_CHEST")) { - continue; - } - testReadMaterial(mat.name(), mat); - testReadMaterial(Integer.toString(mat.getId()), mat); - } - - } + + private void testReadMaterial(String input, Material expectedMat) { + Material mat = RawConfigFile.parseMaterial(input); + if (expectedMat != mat) { + fail("Expected " + expectedMat + " for input '" + input + "', got instead: " + mat); + } + } + + @SuppressWarnings("deprecation") + @Test + public void testReadMaterial() { + // Some really needed parts first. + testReadMaterial("water lily", Material.WATER_LILY); + testReadMaterial("water-lily", Material.WATER_LILY); + testReadMaterial("watEr_lily", Material.WATER_LILY); + + testReadMaterial("flint and steel", Material.FLINT_AND_STEEL); + testReadMaterial("259", Material.FLINT_AND_STEEL); + + // Generic test. + for (final Material mat : Material.values()) { + if (mat.name().equalsIgnoreCase("LOCKED_CHEST")) { + continue; + } + testReadMaterial(mat.name(), mat); + testReadMaterial(Integer.toString(mat.getId()), mat); + } + } + + // TODO: More ConfigFile tests, once processing gets changed. + + @Test + public void testMovePaths() { + // TODO: Set StaticLog to not log. + ConfigFile config = new ConfigFile(); + + // Simple moved boolean. + config.set(ConfPaths.LOGGING_FILE, false); + config = PathUtils.processPaths(config, "test", false); + if (config == null) { + fail("Expect config to be changed at all."); + } + if (config.contains(ConfPaths.LOGGING_FILE)) { + fail("Expect path be removed: " + ConfPaths.LOGGING_FILE); + } + Boolean val = config.getBoolean(ConfPaths.LOGGING_BACKEND_FILE_ACTIVE, true); + if (val == null || val.booleanValue()) { + fail("Expect new path to be set to false: " + ConfPaths.LOGGING_BACKEND_FILE_ACTIVE); + } + } + }