#1467 Fix export issues (style, encoding)

- Override yaml file resource to ensure that lines aren't wrapped
- Override yaml file reader to ensure the file is always read as UTF-8
This commit is contained in:
ljacqu 2018-01-29 21:46:58 +01:00
parent f714e9d564
commit 760a2a909c
2 changed files with 182 additions and 4 deletions

View File

@ -0,0 +1,131 @@
package fr.xephi.authme.message.updater;
import ch.jalu.configme.exception.ConfigMeException;
import ch.jalu.configme.resource.PropertyReader;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* Duplication of ConfigMe's {@link ch.jalu.configme.resource.YamlFileReader} with a character encoding
* fix in {@link #reload}.
*/
public class MessageMigraterPropertyReader implements PropertyReader {
private final File file;
private Map<String, Object> root;
/** See same field in {@link ch.jalu.configme.resource.YamlFileReader} for details. */
private boolean hasObjectAsRoot = false;
/**
* Constructor.
*
* @param file the file to load
*/
public MessageMigraterPropertyReader(File file) {
this.file = file;
reload();
}
@Override
public Object getObject(String path) {
if (path.isEmpty()) {
return hasObjectAsRoot ? root.get("") : root;
}
Object node = root;
String[] keys = path.split("\\.");
for (String key : keys) {
node = getIfIsMap(key, node);
if (node == null) {
return null;
}
}
return node;
}
@Override
public <T> T getTypedObject(String path, Class<T> clazz) {
Object value = getObject(path);
if (clazz.isInstance(value)) {
return clazz.cast(value);
}
return null;
}
@Override
public void set(String path, Object value) {
Objects.requireNonNull(path);
if (path.isEmpty()) {
root.clear();
root.put("", value);
hasObjectAsRoot = true;
} else if (hasObjectAsRoot) {
throw new ConfigMeException("The root path is a bean property; you cannot set values to any subpath. "
+ "Modify the bean at the root or set a new one instead.");
} else {
setValueInChildPath(path, value);
}
}
/**
* Sets the value at the given path. This method is used when the root is a map and not a specific object.
*
* @param path the path to set the value at
* @param value the value to set
*/
@SuppressWarnings("unchecked")
private void setValueInChildPath(String path, Object value) {
Map<String, Object> node = root;
String[] keys = path.split("\\.");
for (int i = 0; i < keys.length - 1; ++i) {
Object child = node.get(keys[i]);
if (child instanceof Map<?, ?>) {
node = (Map<String, Object>) child;
} else { // child is null or some other value - replace with map
Map<String, Object> newEntry = new HashMap<>();
node.put(keys[i], newEntry);
if (value == null) {
// For consistency, replace whatever value/null here with an empty map,
// but if the value is null our work here is done.
return;
}
node = newEntry;
}
}
// node now contains the parent map (existing or newly created)
if (value == null) {
node.remove(keys[keys.length - 1]);
} else {
node.put(keys[keys.length - 1], value);
}
}
@Override
public void reload() {
try (FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)) {
Object obj = new Yaml().load(isr);
root = obj == null ? new HashMap<>() : (Map<String, Object>) obj;
} catch (IOException e) {
throw new ConfigMeException("Could not read file '" + file + "'", e);
} catch (ClassCastException e) {
throw new ConfigMeException("Top-level is not a map in '" + file + "'", e);
}
}
private static Object getIfIsMap(String key, Object value) {
if (value instanceof Map<?, ?>) {
return ((Map<?, ?>) value).get(key);
}
return null;
}
}

View File

@ -1,12 +1,16 @@
package fr.xephi.authme.message.updater; package fr.xephi.authme.message.updater;
import ch.jalu.configme.SettingsManager; import ch.jalu.configme.SettingsManager;
import ch.jalu.configme.beanmapper.leafproperties.LeafPropertiesGenerator;
import ch.jalu.configme.configurationdata.PropertyListBuilder;
import ch.jalu.configme.properties.Property; import ch.jalu.configme.properties.Property;
import ch.jalu.configme.properties.StringProperty; import ch.jalu.configme.properties.StringProperty;
import ch.jalu.configme.resource.YamlFileResource; import ch.jalu.configme.resource.YamlFileResource;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
@ -40,7 +44,7 @@ public class MessageUpdater {
*/ */
boolean migrateAndSave(File userFile, JarMessageSource jarMessageSource) { boolean migrateAndSave(File userFile, JarMessageSource jarMessageSource) {
// YamlConfiguration escapes all special characters when saving, making the file hard to use, so use ConfigMe // YamlConfiguration escapes all special characters when saving, making the file hard to use, so use ConfigMe
YamlFileResource userResource = new YamlFileResource(userFile); YamlFileResource userResource = new MigraterYamlFileResource(userFile);
SettingsManager settingsManager = SettingsManager.createWithProperties(userResource, null, TEXT_PROPERTIES); SettingsManager settingsManager = SettingsManager.createWithProperties(userResource, null, TEXT_PROPERTIES);
// Step 1: Migrate any old keys in the file to the new paths // Step 1: Migrate any old keys in the file to the new paths
@ -81,10 +85,53 @@ public class MessageUpdater {
} }
private static List<Property<String>> buildPropertyEntriesForMessageKeys() { private static List<Property<String>> buildPropertyEntriesForMessageKeys() {
ImmutableList.Builder<Property<String>> listBuilder = ImmutableList.builder(); StringPropertyListBuilder builder = new StringPropertyListBuilder();
for (MessageKey messageKey : MessageKey.values()) { for (MessageKey messageKey : MessageKey.values()) {
listBuilder.add(new StringProperty(messageKey.getKey(), "")); builder.add(messageKey.getKey());
}
return ImmutableList.copyOf(builder.create());
}
/**
* Wraps a {@link PropertyListBuilder} for easier construction of string properties.
* ConfigMe's property list builder ensures that properties are grouped together by path.
*/
private static final class StringPropertyListBuilder {
private PropertyListBuilder propertyListBuilder = new PropertyListBuilder();
void add(String path) {
propertyListBuilder.add(new StringProperty(path, ""));
}
@SuppressWarnings("unchecked")
List<Property<String>> create() {
return (List) propertyListBuilder.create();
}
}
/**
* Extension of {@link YamlFileResource} to fine-tune the export style.
*/
private static final class MigraterYamlFileResource extends YamlFileResource {
private Yaml singleQuoteYaml;
MigraterYamlFileResource(File file) {
super(file, new MessageMigraterPropertyReader(file), new LeafPropertiesGenerator());
}
@Override
protected Yaml getSingleQuoteYaml() {
if (singleQuoteYaml == null) {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
options.setAllowUnicode(true);
options.setDefaultScalarStyle(DumperOptions.ScalarStyle.SINGLE_QUOTED);
// Overridden setting: don't split lines
options.setSplitLines(false);
singleQuoteYaml = new Yaml(options);
}
return singleQuoteYaml;
} }
return listBuilder.build();
} }
} }