Partly cleanup handling moved paths.

* Add a test for moving a simple config value (not sections).
* Add a flag to @Moved to allow explicitly skipping sections.
  (Should enable moving values into a child path of the previous one.)
* Don't set values before inside of processMoved...
This commit is contained in:
asofold 2015-01-25 03:18:40 +01:00
parent be68ecba60
commit 52d46450e2
3 changed files with 209 additions and 107 deletions

View File

@ -16,5 +16,18 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface Moved{ 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;
} }

View File

@ -12,9 +12,7 @@ import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.MemoryConfiguration; import org.bukkit.configuration.MemoryConfiguration;
@ -25,6 +23,8 @@ import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.SimpleCharPrefixTree;
public class PathUtils { public class PathUtils {
// TODO: Make this a class to be able to process multiple files with different paths and annotations.
// Deprecated paths. // Deprecated paths.
private static final Set<String> deprecatedFields = new LinkedHashSet<String>(); private static final Set<String> deprecatedFields = new LinkedHashSet<String>();
private static final SimpleCharPrefixTree deprecatedPrefixes = new SimpleCharPrefixTree(); private static final SimpleCharPrefixTree deprecatedPrefixes = new SimpleCharPrefixTree();
@ -34,7 +34,7 @@ public class PathUtils {
private static final SimpleCharPrefixTree globalOnlyPrefixes = new SimpleCharPrefixTree(); private static final SimpleCharPrefixTree globalOnlyPrefixes = new SimpleCharPrefixTree();
// Paths moved to other paths. // Paths moved to other paths.
private static final Map<String, String> movedPaths = new LinkedHashMap<String, String>(); private static final Map<String, Moved> movedPaths = new LinkedHashMap<String, Moved>();
static{ static{
initPaths(); initPaths();
@ -44,14 +44,14 @@ public class PathUtils {
* Initialize annotation-based path properties. * Initialize annotation-based path properties.
* @return * @return
*/ */
private static void initPaths(){ private static void initPaths() {
deprecatedFields.clear(); deprecatedFields.clear();
deprecatedPrefixes.clear(); deprecatedPrefixes.clear();
globalOnlyFields.clear(); globalOnlyFields.clear();
globalOnlyPrefixes.clear(); globalOnlyPrefixes.clear();
movedPaths.clear(); movedPaths.clear();
for (final Field field : ConfPaths.class.getDeclaredFields()){ for (final Field field : ConfPaths.class.getDeclaredFields()) {
if (field.getType() != String.class){ if (field.getType() != String.class) {
// Only process strings. // Only process strings.
continue; continue;
} }
@ -59,7 +59,7 @@ public class PathUtils {
checkAddPrefixes(field, fieldName, GlobalConfig.class, globalOnlyFields, globalOnlyPrefixes); checkAddPrefixes(field, fieldName, GlobalConfig.class, globalOnlyFields, globalOnlyPrefixes);
checkAddPrefixes(field, fieldName, Deprecated.class, deprecatedFields, deprecatedPrefixes); 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? // TODO: Prefixes: Might later support relocating entire sections with one annotation?
addMoved(field, field.getAnnotation(Moved.class)); addMoved(field, field.getAnnotation(Moved.class));
} }
@ -67,13 +67,13 @@ public class PathUtils {
} }
private static void checkAddPrefixes(Field field, String fieldName, Class<? extends Annotation> annotation, Set<String> fieldNames, SimpleCharPrefixTree pathPrefixes) { private static void checkAddPrefixes(Field field, String fieldName, Class<? extends Annotation> annotation, Set<String> fieldNames, SimpleCharPrefixTree pathPrefixes) {
if (field.isAnnotationPresent(annotation)){ if (field.isAnnotationPresent(annotation)) {
fieldNames.add(fieldName); fieldNames.add(fieldName);
addPrefixesField(field, pathPrefixes); addPrefixesField(field, pathPrefixes);
} }
else{ else {
for (final String refName : fieldNames){ for (final String refName : fieldNames) {
if (fieldName.startsWith(refName)){ if (fieldName.startsWith(refName)) {
addPrefixesField(field, pathPrefixes); addPrefixesField(field, pathPrefixes);
} }
} }
@ -83,7 +83,7 @@ public class PathUtils {
private static void addPrefixesField(Field field, SimpleCharPrefixTree pathPrefixes) { private static void addPrefixesField(Field field, SimpleCharPrefixTree pathPrefixes) {
try { try {
final String path = field.get(null).toString(); final String path = field.get(null).toString();
if (path != null){ if (path != null) {
pathPrefixes.feed(path); pathPrefixes.feed(path);
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@ -94,7 +94,7 @@ public class PathUtils {
private static void addMoved(final Field field, final Moved rel) { private static void addMoved(final Field field, final Moved rel) {
try { try {
final String path = field.get(null).toString(); final String path = field.get(null).toString();
movedPaths.put(path, rel.newPath()); movedPaths.put(path, rel);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
} }
@ -107,12 +107,11 @@ public class PathUtils {
* @param msgHeader * @param msgHeader
* @param warnedPaths Paths which were found, can be null. * @param warnedPaths Paths which were found, can be null.
*/ */
protected static void warnPaths(final ConfigFile config, final CharPrefixTree<?, ?> paths, final String msgPrefix, final Set<String> warnedPaths){ protected static void warnPaths(final ConfigurationSection config, final CharPrefixTree<?, ?> paths, final String msgPrefix, final Set<String> warnedPaths) {
final Logger logger = Bukkit.getLogger(); for (final String path : config.getKeys(true)) {
for (final String path : config.getKeys(true)){ if (paths.hasPrefix(path)) {
if (paths.hasPrefix(path)){ StaticLog.logWarning("[NoCheatPlus] Config path '" + path + "'" + msgPrefix);
logger.warning("[NoCheatPlus] Config path '" + path + "'" + msgPrefix); if (warnedPaths != null) {
if (warnedPaths != null){
warnedPaths.add(path); 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 file
* @param configName * @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(); ConfigFile config = new ConfigFile();
try { try {
config.load(file); config.load(file);
final Set<String> removePaths = new LinkedHashSet<String>(); ConfigFile newConfig = processPaths(config, configName, isWorldConfig);
final Map<String, Object> addPaths = new LinkedHashMap<String, Object>(); if (newConfig != null) {
if (isWorldConfig){ config = newConfig;
// 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){
try{ try{
config.save(file); config.save(file);
} }
catch(Throwable t){ catch(Throwable t) {
// Do log this one. // Do log this one.
StaticLog.logSevere("[NoCheatPlus] Failed to save configuration (" + configName + ") with changes: " + t.getClass().getSimpleName()); StaticLog.logSevere("[NoCheatPlus] Failed to save configuration (" + configName + ") with changes: " + t.getClass().getSimpleName());
StaticLog.logSevere(t); 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<String> removePaths = new LinkedHashSet<String>();
final Map<String, Object> addPaths = new LinkedHashMap<String, Object>();
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. * Set paths.
* @param config * @param config
* @param addPaths * @param addPaths
*/ */
public static void setPaths(final ConfigFile config, final Map<String, Object> setPaths) { public static void setPaths(final ConfigurationSection config, final Map<String, Object> setPaths, final boolean override) {
for (final Entry<String, Object> entry : setPaths.entrySet()){ setPaths(config, setPaths, "", override);
config.set(entry.getKey(), entry.getValue()); }
/**
* Recursive version supporting ConfigurationSection.
* @param config
* @param setPaths
* @param pathPrefix
*/
protected static void setPaths(final ConfigurationSection config, final Map<String, Object> setPaths, final String pathPrefix, final boolean override) {
for (final Entry<String, Object> 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<String> removePaths) { public static ConfigFile removePaths(final ConfigFile config, final Collection<String> removePaths) {
final SimpleCharPrefixTree prefixes = new SimpleCharPrefixTree(); final SimpleCharPrefixTree prefixes = new SimpleCharPrefixTree();
for (final String path : removePaths){ for (final String path : removePaths) {
prefixes.feed(path); prefixes.feed(path);
} }
final ConfigFile newConfig = new ConfigFile(); final ConfigFile newConfig = new ConfigFile();
for (final Entry<String, Object> entry : config.getValues(true).entrySet()){ for (final Entry<String, Object> entry : config.getValues(true).entrySet()) {
final String path = entry.getKey(); final String path = entry.getKey();
final Object value = entry.getValue(); 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; continue;
} }
if (!prefixes.hasPrefix(path)){ if (!prefixes.hasPrefix(path)) {
newConfig.set(path, value); newConfig.set(path, value);
} }
} }
@ -203,26 +241,32 @@ public class PathUtils {
* @param configName * @param configName
* @param removePaths * @param removePaths
* @param addPaths * @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<String> removePaths, final Map<String, Object> addPaths) { protected static void processMovedPaths(final ConfigurationSection config, final String configName, final Set<String> removePaths, final Map<String, Object> addPaths) {
final Logger logger = Bukkit.getLogger(); for (final Entry<String, Moved> entry : movedPaths.entrySet()) {
for (final Entry<String, String> entry : movedPaths.entrySet()){
final String path = entry.getKey(); final String path = entry.getKey();
if (config.contains(path)){ final Object value = config.get(path);
final String newPath = entry.getValue(); 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; final String to;
if (newPath == null | newPath.isEmpty()){ if (newPath == null || newPath.isEmpty()) {
to = "."; to = ".";
} }
else{ else {
to = " to '" + newPath + "'."; to = " to '" + newPath + "'.";
final Object value = config.get(path);
config.set(newPath, value);
addPaths.put(newPath, value); addPaths.put(newPath, value);
removePaths.add(path); 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 paths
* @param configName * @param configName
*/ */
protected static void processDeprecatedPaths(ConfigFile config, String configName, final Set<String> removePaths){ protected static void processDeprecatedPaths(ConfigurationSection config, String configName, final Set<String> removePaths) {
warnPaths(config, deprecatedPrefixes, " (" + configName + ") is not in use anymore.", removePaths); warnPaths(config, deprecatedPrefixes, " (" + configName + ") is not in use anymore.", removePaths);
} }
@ -243,7 +287,7 @@ public class PathUtils {
* @param paths * @param paths
* @param configName * @param configName
*/ */
protected static void processGlobalOnlyPaths(ConfigFile config, String configName, final Set<String> removePaths){ protected static void processGlobalOnlyPaths(ConfigurationSection config, String configName, final Set<String> removePaths) {
warnPaths(config, globalOnlyPrefixes, " (" + configName + ") should only be set in the global configuration.", removePaths); warnPaths(config, globalOnlyPrefixes, " (" + configName + ") should only be set in the global configuration.", removePaths);
} }
@ -252,41 +296,61 @@ public class PathUtils {
* @param defaultConfig * @param defaultConfig
* @return * @return
*/ */
public static MemoryConfiguration getWorldsDefaultConfig(final ConfigFile defaultConfig){ public static MemoryConfiguration getWorldsDefaultConfig(final MemoryConfiguration defaultConfig) {
final char sep = defaultConfig.options().pathSeparator(); final char sep = defaultConfig.options().pathSeparator();
final MemoryConfiguration config = new ConfigFile(); final MemoryConfiguration config = new ConfigFile();
config.options().pathSeparator(sep); config.options().pathSeparator(sep);
final Map<String, Object> defaults = defaultConfig.getValues(false); final Map<String, Object> defaults = defaultConfig.getValues(false);
for (final Entry<String, Object> entry : defaults.entrySet()){ for (final Entry<String, Object> entry : defaults.entrySet()) {
final String part = entry.getKey(); final String part = entry.getKey();
if (!part.isEmpty() && !mayBeInWorldConfig(part)) continue; if (!part.isEmpty() && !mayBeInWorldConfig(part)) {
continue;
}
final Object value = entry.getValue(); final Object value = entry.getValue();
if (value instanceof ConfigurationSection) addWorldConfigSection(config, (ConfigurationSection) value, part, sep); if (value instanceof ConfigurationSection) {
else config.set(part, value); addWorldConfigSection(config, (ConfigurationSection) value, part, sep);
}
else {
config.set(part, value);
}
} }
return config; 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<String, Object> values = section.getValues(false); final Map<String, Object> values = section.getValues(false);
for (final Entry<String, Object> entry : values.entrySet()){ for (final Entry<String, Object> entry : values.entrySet()) {
final String fullPath = path + sep + entry.getKey(); final String fullPath = path + sep + entry.getKey();
if (!mayBeInWorldConfig(fullPath)) continue; if (!mayBeInWorldConfig(fullPath)) {
continue;
}
final Object value = entry.getValue(); final Object value = entry.getValue();
if (value instanceof ConfigurationSection) addWorldConfigSection(config, (ConfigurationSection) value, fullPath, sep); if (value instanceof ConfigurationSection) {
else config.set(fullPath, value); addWorldConfigSection(config, (ConfigurationSection) value, fullPath, sep);
}
else {
config.set(fullPath, value);
}
} }
} }
public static boolean mayBeInWorldConfig(final String path){ public static boolean mayBeInWorldConfig(final String path) {
if (globalOnlyPrefixes.hasPrefix(path)) return false; if (globalOnlyPrefixes.hasPrefix(path)) {
return mayBeInConfig(path); return false;
} else {
return mayBeInConfig(path);
}
} }
public static boolean mayBeInConfig(final String path){ public static boolean mayBeInConfig(final String path) {
if (deprecatedPrefixes.hasPrefix(path)) return false; if (deprecatedPrefixes.hasPrefix(path)) {
if (movedPaths.containsKey(path)) return false; return false;
return true; }
else if (movedPaths.containsKey(path)) {
return false;
} else {
return true;
}
} }
} }

View File

@ -5,36 +5,61 @@ import static org.junit.Assert.fail;
import org.bukkit.Material; import org.bukkit.Material;
import org.junit.Test; 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; import fr.neatmonster.nocheatplus.config.RawConfigFile;
public class TestConfig { public class TestConfig {
private void testReadMaterial(String input, Material expectedMat) { private void testReadMaterial(String input, Material expectedMat) {
Material mat = RawConfigFile.parseMaterial(input); Material mat = RawConfigFile.parseMaterial(input);
if (expectedMat != mat) { if (expectedMat != mat) {
fail("Expected " + expectedMat + " for input '" + input + "', got instead: " + mat); fail("Expected " + expectedMat + " for input '" + input + "', got instead: " + mat);
} }
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Test @Test
public void testReadMaterial() { public void testReadMaterial() {
// Some really needed parts first. // Some really needed parts first.
testReadMaterial("water lily", Material.WATER_LILY); testReadMaterial("water lily", Material.WATER_LILY);
testReadMaterial("water-lily", Material.WATER_LILY); 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("flint and steel", Material.FLINT_AND_STEEL);
testReadMaterial("259", Material.FLINT_AND_STEEL); testReadMaterial("259", Material.FLINT_AND_STEEL);
// Generic test. // Generic test.
for (final Material mat : Material.values()) { for (final Material mat : Material.values()) {
if (mat.name().equalsIgnoreCase("LOCKED_CHEST")) { if (mat.name().equalsIgnoreCase("LOCKED_CHEST")) {
continue; continue;
} }
testReadMaterial(mat.name(), mat); testReadMaterial(mat.name(), mat);
testReadMaterial(Integer.toString(mat.getId()), 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);
}
}
} }