mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-02-02 21:41:28 +01:00
New Config code (Untested)
This commit is contained in:
parent
557fa83177
commit
2fcb9590e5
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Risto Lahtela
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.system.settings.config;
|
||||||
|
|
||||||
|
import com.djrapitops.plugin.utilities.Verify;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration utility for storing settings in a .yml file.
|
||||||
|
* <p>
|
||||||
|
* Based on
|
||||||
|
* https://github.com/Rsl1122/Abstract-Plugin-Framework/blob/72e221d3571ef200727713d10d3684c51e9f469d/AbstractPluginFramework/api/src/main/java/com/djrapitops/plugin/config/Config.java
|
||||||
|
*
|
||||||
|
* @author Rsl1122
|
||||||
|
*/
|
||||||
|
public class Config extends ConfigNode {
|
||||||
|
|
||||||
|
private final Path configFilePath;
|
||||||
|
|
||||||
|
public Config(File configFile) {
|
||||||
|
super("", null, null);
|
||||||
|
File folder = configFile.getParentFile();
|
||||||
|
this.configFilePath = configFile.toPath();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Verify.isTrue(folder.exists() || folder.mkdirs(), () ->
|
||||||
|
new FileNotFoundException("Folders could not be created for config file " + configFile.getAbsolutePath()));
|
||||||
|
Verify.isTrue(configFile.exists() || configFile.createNewFile(), () ->
|
||||||
|
new FileNotFoundException("Could not create file: " + configFile.getAbsolutePath()));
|
||||||
|
read();
|
||||||
|
save();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Config(File configFile, ConfigNode defaults) {
|
||||||
|
this(configFile);
|
||||||
|
copyMissing(defaults);
|
||||||
|
try {
|
||||||
|
save();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Config() {
|
||||||
|
super("", null, null);
|
||||||
|
configFilePath = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void read() throws IOException {
|
||||||
|
copyMissing(new ConfigReader(Files.newInputStream(configFilePath)).read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save() throws IOException {
|
||||||
|
new ConfigWriter(configFilePath).write(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Risto Lahtela
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.system.settings.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a single node in a configuration file
|
||||||
|
* <p>
|
||||||
|
* Based on
|
||||||
|
* https://github.com/Rsl1122/Abstract-Plugin-Framework/blob/72e221d3571ef200727713d10d3684c51e9f469d/AbstractPluginFramework/api/src/main/java/com/djrapitops/plugin/config/ConfigNode.java
|
||||||
|
*
|
||||||
|
* @author Rsl1122
|
||||||
|
*/
|
||||||
|
public class ConfigNode {
|
||||||
|
|
||||||
|
protected final String key;
|
||||||
|
protected ConfigNode parent;
|
||||||
|
|
||||||
|
protected List<String> nodeOrder;
|
||||||
|
protected Map<String, ConfigNode> childNodes;
|
||||||
|
protected List<String> comment;
|
||||||
|
|
||||||
|
protected String value;
|
||||||
|
|
||||||
|
public ConfigNode(String key, ConfigNode parent, String value) {
|
||||||
|
this.key = key;
|
||||||
|
this.parent = parent;
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
|
nodeOrder = new ArrayList<>();
|
||||||
|
childNodes = new HashMap<>();
|
||||||
|
comment = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateParent(ConfigNode newParent) {
|
||||||
|
parent = newParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ConfigNode> getNode(String path) {
|
||||||
|
String[] parts = path.split("\\.", 2);
|
||||||
|
String key = parts[0];
|
||||||
|
String leftover = parts[1];
|
||||||
|
|
||||||
|
if (leftover.isEmpty()) {
|
||||||
|
return Optional.ofNullable(childNodes.get(key));
|
||||||
|
} else {
|
||||||
|
return getNode(key).flatMap(child -> child.getNode(leftover));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addNode(String path) {
|
||||||
|
ConfigNode newParent = this;
|
||||||
|
if (!path.isEmpty()) {
|
||||||
|
String[] parts = path.split("\\.", 2);
|
||||||
|
String key = parts[0];
|
||||||
|
String leftover = parts[1];
|
||||||
|
|
||||||
|
if (!childNodes.containsKey(key)) {
|
||||||
|
addChild(new ConfigNode(key, newParent, null));
|
||||||
|
}
|
||||||
|
ConfigNode child = childNodes.get(key);
|
||||||
|
child.addNode(leftover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a node at a certain path.
|
||||||
|
*
|
||||||
|
* @param path Path to the node that is up for removal.
|
||||||
|
* @return {@code true} if the node was present and is now removed. {@code false} if the path did not have a node.
|
||||||
|
*/
|
||||||
|
public boolean removeNode(String path) {
|
||||||
|
Optional<ConfigNode> node = getNode(path);
|
||||||
|
node.ifPresent(ConfigNode::remove);
|
||||||
|
return node.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove() {
|
||||||
|
parent.childNodes.remove(key);
|
||||||
|
parent.nodeOrder.remove(key);
|
||||||
|
updateParent(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addChild(ConfigNode child) {
|
||||||
|
getNode(child.key).ifPresent(ConfigNode::remove);
|
||||||
|
childNodes.put(child.key, child);
|
||||||
|
nodeOrder.add(child.key);
|
||||||
|
child.updateParent(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeChild(ConfigNode child) {
|
||||||
|
removeNode(child.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves a node from old path to new path.
|
||||||
|
*
|
||||||
|
* @param oldPath Old path of the node.
|
||||||
|
* @param newPath New path of the node.
|
||||||
|
* @return {@code true} if the move was successful. {@code false} if the new node is not present
|
||||||
|
*/
|
||||||
|
public boolean moveChild(String oldPath, String newPath) {
|
||||||
|
Optional<ConfigNode> found = getNode(oldPath);
|
||||||
|
if (!found.isPresent()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
addNode(newPath);
|
||||||
|
|
||||||
|
ConfigNode moveFrom = found.get();
|
||||||
|
ConfigNode moveTo = getNode(newPath).orElseThrow(() -> new IllegalStateException("Config node was not added properly: " + newPath));
|
||||||
|
ConfigNode oldParent = moveFrom.parent;
|
||||||
|
ConfigNode newParent = moveTo.parent;
|
||||||
|
oldParent.removeChild(moveFrom);
|
||||||
|
newParent.addChild(moveTo);
|
||||||
|
|
||||||
|
return getNode(newPath).isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey(boolean deep) {
|
||||||
|
if (deep && parent != null) {
|
||||||
|
String deepKey = parent.getKey(true) + "." + key;
|
||||||
|
if (deepKey.startsWith(".")) {
|
||||||
|
return deepKey.substring(1);
|
||||||
|
}
|
||||||
|
return deepKey;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sort() {
|
||||||
|
Collections.sort(nodeOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean reorder(List<String> newOrder) {
|
||||||
|
if (nodeOrder.containsAll(newOrder)) {
|
||||||
|
nodeOrder = newOrder;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the root node and save.
|
||||||
|
*
|
||||||
|
* @throws IOException If the save can not be performed.
|
||||||
|
*/
|
||||||
|
public void save() throws IOException {
|
||||||
|
ConfigNode root = this.parent;
|
||||||
|
while (root.parent != null) {
|
||||||
|
root = root.parent;
|
||||||
|
}
|
||||||
|
root.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void set(String path, T value) {
|
||||||
|
addNode(path);
|
||||||
|
ConfigNode node = getNode(path).orElseThrow(() -> new IllegalStateException("Config node was not added properly: " + path));
|
||||||
|
node.set(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void set(T value) {
|
||||||
|
if (value instanceof ConfigNode) {
|
||||||
|
addChild(((ConfigNode) value));
|
||||||
|
} else {
|
||||||
|
ConfigValueParser<T> parser = ConfigValueParser.getParserFor(value.getClass());
|
||||||
|
this.value = parser.decompose(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getComment() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComment(List<String> comment) {
|
||||||
|
this.comment = comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getStringList() {
|
||||||
|
return new ConfigValueParser.StringListParser().compose(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getInteger() {
|
||||||
|
return new ConfigValueParser.IntegerParser().compose(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getLong() {
|
||||||
|
return new ConfigValueParser.LongParser().compose(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString() {
|
||||||
|
return new ConfigValueParser.StringParser().compose(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTrue() {
|
||||||
|
return new ConfigValueParser.BooleanParser().compose(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getStringList(String path) {
|
||||||
|
return getNode(path).map(ConfigNode::getStringList).orElse(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getInteger(String path) {
|
||||||
|
return getNode(path).map(ConfigNode::getInteger).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getLong(String path) {
|
||||||
|
return getNode(path).map(ConfigNode::getLong).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(String path) {
|
||||||
|
return getNode(path).map(ConfigNode::getString).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTrue(String path) {
|
||||||
|
return getNode(path).map(ConfigNode::isTrue).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyMissing(ConfigNode from) {
|
||||||
|
if (comment.size() < from.comment.size()) {
|
||||||
|
comment = from.comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == null && from.value != null) {
|
||||||
|
value = from.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String key : from.nodeOrder) {
|
||||||
|
ConfigNode newChild = from.childNodes.get(key);
|
||||||
|
|
||||||
|
if (childNodes.containsKey(key)) {
|
||||||
|
ConfigNode oldChild = childNodes.get(key);
|
||||||
|
oldChild.copyMissing(newChild);
|
||||||
|
} else {
|
||||||
|
addChild(newChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getNodeDepth() {
|
||||||
|
return parent != null ? parent.getNodeDepth() + 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.system.settings.config;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reader for parsing {@link Config} out of file-lines.
|
||||||
|
* <p>
|
||||||
|
* ConfigReader can read a single file at a time, so it is NOT thread safe.
|
||||||
|
*
|
||||||
|
* @author Rsl1122
|
||||||
|
*/
|
||||||
|
public class ConfigReader implements Closeable {
|
||||||
|
|
||||||
|
private static final ConfigValueParser.StringParser STRING_PARSER = new ConfigValueParser.StringParser();
|
||||||
|
private final InputStream in;
|
||||||
|
private final Scanner scanner;
|
||||||
|
private ConfigNode previousNode;
|
||||||
|
private ConfigNode parent;
|
||||||
|
|
||||||
|
public ConfigReader(InputStream in) {
|
||||||
|
this.in = in;
|
||||||
|
this.scanner = new Scanner(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Config read() {
|
||||||
|
Config config = new Config();
|
||||||
|
|
||||||
|
previousNode = config;
|
||||||
|
parent = config;
|
||||||
|
|
||||||
|
while (scanner.hasNextLine()) {
|
||||||
|
String line = readNewLine();
|
||||||
|
// Determine where the node belongs
|
||||||
|
parent = findParent(previousNode.getNodeDepth(), findCurrentDepth(line));
|
||||||
|
previousNode = parseNode(line.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readNewLine() {
|
||||||
|
String line = scanner.nextLine();
|
||||||
|
|
||||||
|
// Removing any dangling comments
|
||||||
|
int danglingComment = line.indexOf(" #");
|
||||||
|
if (danglingComment != -1) {
|
||||||
|
line = line.substring(0, danglingComment);
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConfigNode parseNode(String line) {
|
||||||
|
if (line.startsWith("#")) {
|
||||||
|
return handleCommentLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] keyAndValue = line.split(":", 2);
|
||||||
|
if (keyAndValue.length <= 1) {
|
||||||
|
return handleMultiline(line);
|
||||||
|
}
|
||||||
|
String key = keyAndValue[0].trim();
|
||||||
|
String value = keyAndValue[1].trim();
|
||||||
|
return handleNewNode(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConfigNode handleMultiline(String line) {
|
||||||
|
if (line.startsWith("- ")) {
|
||||||
|
return handleListCase(line);
|
||||||
|
} else {
|
||||||
|
return handleMultilineString(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConfigNode handleCommentLine(String line) {
|
||||||
|
previousNode.comment.add(line.substring(1).trim());
|
||||||
|
return previousNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConfigNode handleMultilineString(String line) {
|
||||||
|
if (previousNode.value == null) {
|
||||||
|
previousNode.value = "";
|
||||||
|
}
|
||||||
|
// Append the new line to the end of the value.
|
||||||
|
previousNode.value += STRING_PARSER.compose(line.substring(2).trim());
|
||||||
|
return previousNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConfigNode handleNewNode(String key, String value) {
|
||||||
|
ConfigNode newNode = new ConfigNode(key, parent, STRING_PARSER.compose(value));
|
||||||
|
parent.addChild(newNode);
|
||||||
|
return newNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConfigNode handleListCase(String line) {
|
||||||
|
if (previousNode.value == null) {
|
||||||
|
previousNode.value = "";
|
||||||
|
}
|
||||||
|
// Append list item to the value.
|
||||||
|
previousNode.value += "\n- " + STRING_PARSER.compose(line.substring(2).trim());
|
||||||
|
return previousNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConfigNode findParent(int previousDepth, int currentDepth) {
|
||||||
|
if (previousDepth < currentDepth) {
|
||||||
|
return previousNode;
|
||||||
|
} else if (previousDepth > currentDepth) {
|
||||||
|
// Prevents incorrect indent in the case:
|
||||||
|
// 1:
|
||||||
|
// 2:
|
||||||
|
// 3:
|
||||||
|
// 1:
|
||||||
|
int helperDepth = previousDepth;
|
||||||
|
ConfigNode foundParent = parent;
|
||||||
|
while (helperDepth > currentDepth) {
|
||||||
|
helperDepth = parent.getNodeDepth();
|
||||||
|
foundParent = foundParent.parent; // Moves one level up the tree
|
||||||
|
}
|
||||||
|
return foundParent;
|
||||||
|
} else {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int findCurrentDepth(String line) {
|
||||||
|
int indent = readIndent(line);
|
||||||
|
int depth;
|
||||||
|
if (indent % 4 == 0) {
|
||||||
|
depth = indent / 4;
|
||||||
|
} else {
|
||||||
|
depth = (indent - (indent % 4)) / 4;
|
||||||
|
}
|
||||||
|
return depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readIndent(String line) {
|
||||||
|
int indentation = 0;
|
||||||
|
for (char c : line.toCharArray()) {
|
||||||
|
if (c == ' ') {
|
||||||
|
indentation++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indentation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
scanner.close();
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.system.settings.config;
|
||||||
|
|
||||||
|
import com.djrapitops.plugin.utilities.Verify;
|
||||||
|
import org.apache.commons.lang3.math.NumberUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for parsing config values.
|
||||||
|
*
|
||||||
|
* @param <T> Type of the object being parsed.
|
||||||
|
* @author Rsl1122
|
||||||
|
*/
|
||||||
|
public interface ConfigValueParser<T> {
|
||||||
|
|
||||||
|
static ConfigValueParser getParserFor(Class type) {
|
||||||
|
if (String.class.isAssignableFrom(type)) {
|
||||||
|
return new StringParser();
|
||||||
|
} else if (List.class.isAssignableFrom(type)) {
|
||||||
|
return new StringListParser();
|
||||||
|
} else if (Boolean.class.isAssignableFrom(type)) {
|
||||||
|
return new BooleanParser();
|
||||||
|
} else if (Long.class.isAssignableFrom(type)) {
|
||||||
|
return new LongParser();
|
||||||
|
} else if (Integer.class.isAssignableFrom(type)) {
|
||||||
|
return new IntegerParser();
|
||||||
|
}
|
||||||
|
return new StringParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a String value in the config into the appropriate object.
|
||||||
|
*
|
||||||
|
* @param fromValue String value in the config.
|
||||||
|
* @return Config value or null if it could not be parsed.
|
||||||
|
*/
|
||||||
|
T compose(String fromValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an object into a String value to save in the config.
|
||||||
|
*
|
||||||
|
* @param ofValue Value to save, not null.
|
||||||
|
* @return String format to save in the config.
|
||||||
|
* @throws IllegalArgumentException If null value is given.
|
||||||
|
*/
|
||||||
|
String decompose(T ofValue);
|
||||||
|
|
||||||
|
class StringParser implements ConfigValueParser<String> {
|
||||||
|
@Override
|
||||||
|
public String compose(String fromValue) {
|
||||||
|
String parsed = fromValue;
|
||||||
|
boolean surroundedWithSingleQuotes = parsed.startsWith("'") && parsed.endsWith("'");
|
||||||
|
boolean surroundedWithDoubleQuotes = parsed.startsWith("\"") && parsed.endsWith("\"");
|
||||||
|
if (surroundedWithSingleQuotes || surroundedWithDoubleQuotes) {
|
||||||
|
parsed = parsed.substring(1, parsed.length() - 1);
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String decompose(String ofValue) {
|
||||||
|
Verify.nullCheck(ofValue, () -> new IllegalArgumentException("Null value is not valid for saving"));
|
||||||
|
|
||||||
|
boolean surroundedWithSingleQuotes = ofValue.startsWith("'") && ofValue.endsWith("'");
|
||||||
|
if (surroundedWithSingleQuotes) {
|
||||||
|
return "\"" + ofValue + "\"";
|
||||||
|
}
|
||||||
|
boolean surroundedWithDoubleQuotes = ofValue.startsWith("\"") && ofValue.endsWith("\"");
|
||||||
|
if (surroundedWithDoubleQuotes) {
|
||||||
|
return "'" + ofValue + "'";
|
||||||
|
}
|
||||||
|
return ofValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IntegerParser implements ConfigValueParser<Integer> {
|
||||||
|
@Override
|
||||||
|
public Integer compose(String fromValue) {
|
||||||
|
if (NumberUtils.isParsable(fromValue)) {
|
||||||
|
return NumberUtils.createInteger(fromValue);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String decompose(Integer ofValue) {
|
||||||
|
Verify.nullCheck(ofValue, () -> new IllegalArgumentException("Null value is not valid for saving"));
|
||||||
|
return Integer.toString(ofValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LongParser implements ConfigValueParser<Long> {
|
||||||
|
@Override
|
||||||
|
public Long compose(String fromValue) {
|
||||||
|
try {
|
||||||
|
return Long.parseLong(fromValue);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String decompose(Long ofValue) {
|
||||||
|
Verify.nullCheck(ofValue, () -> new IllegalArgumentException("Null value is not valid for saving"));
|
||||||
|
return Long.toString(ofValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BooleanParser implements ConfigValueParser<Boolean> {
|
||||||
|
@Override
|
||||||
|
public Boolean compose(String fromValue) {
|
||||||
|
return Boolean.valueOf(fromValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String decompose(Boolean ofValue) {
|
||||||
|
Verify.nullCheck(ofValue, () -> new IllegalArgumentException("Null value is not valid for saving"));
|
||||||
|
return Boolean.toString(ofValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StringListParser implements ConfigValueParser<List<String>> {
|
||||||
|
private static final StringParser STRING_PARSER = new StringParser();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> compose(String fromValue) {
|
||||||
|
List<String> values = new ArrayList<>();
|
||||||
|
for (String line : fromValue.split("\\n")) {
|
||||||
|
// Removes '- ' in front of the value.
|
||||||
|
line = line.substring(2).trim();
|
||||||
|
// Handle quotes around the string
|
||||||
|
line = STRING_PARSER.compose(line);
|
||||||
|
if (!line.isEmpty()) {
|
||||||
|
values.add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String decompose(List<String> ofValue) {
|
||||||
|
Verify.nullCheck(ofValue, () -> new IllegalArgumentException("Null value is not valid for saving"));
|
||||||
|
|
||||||
|
StringBuilder decomposedString = new StringBuilder();
|
||||||
|
for (String value : ofValue) {
|
||||||
|
decomposedString.append("\n- ").append(STRING_PARSER.decompose(value));
|
||||||
|
}
|
||||||
|
return decomposedString.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Player Analytics (Plan).
|
||||||
|
*
|
||||||
|
* Plan is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Plan is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.djrapitops.plan.system.settings.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writer for parsing {@link Config} into file-lines.
|
||||||
|
* <p>
|
||||||
|
* ConfigReader can write a single file at a time, so it is NOT thread safe.
|
||||||
|
*
|
||||||
|
* @author Rsl1122
|
||||||
|
*/
|
||||||
|
public class ConfigWriter {
|
||||||
|
|
||||||
|
private final Path outputPath;
|
||||||
|
private int indent;
|
||||||
|
|
||||||
|
public ConfigWriter(Path outputPath) {
|
||||||
|
this.outputPath = outputPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(ConfigNode writing) throws IOException {
|
||||||
|
ConfigNode storedParent = writing.parent;
|
||||||
|
writing.updateParent(null);
|
||||||
|
|
||||||
|
Files.write(outputPath, parseLines(writing), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
writing.updateParent(storedParent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> parseLines(ConfigNode writing) {
|
||||||
|
List<String> lines = new ArrayList<>();
|
||||||
|
|
||||||
|
indent = writing.getNodeDepth() * 4;
|
||||||
|
|
||||||
|
addComment(writing.comment, lines);
|
||||||
|
addValue(writing.key, writing.value, lines);
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addValue(String key, String value, Collection<String> lines) {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value.contains("\n")) {
|
||||||
|
addListValue(key, value.split("\\n"), lines);
|
||||||
|
} else {
|
||||||
|
addNormalValue(key, value, lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNormalValue(String key, String value, Collection<String> lines) {
|
||||||
|
StringBuilder lineBuilder = indentedBuilder().append(key).append(": ").append(value);
|
||||||
|
lines.add(lineBuilder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addListValue(String key, String[] listItems, Collection<String> lines) {
|
||||||
|
addNormalValue(key, "", lines);
|
||||||
|
for (String listItem : listItems) {
|
||||||
|
listItem = listItem.trim();
|
||||||
|
if (listItem.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
StringBuilder lineBuilder = indentedBuilder().append(listItem);
|
||||||
|
lines.add(lineBuilder.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addComment(Iterable<String> comments, Collection<String> lines) {
|
||||||
|
for (String comment : comments) {
|
||||||
|
StringBuilder lineBuilder = indentedBuilder().append("# ").append(comment);
|
||||||
|
lines.add(lineBuilder.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringBuilder indentedBuilder() {
|
||||||
|
StringBuilder lineBuilder = new StringBuilder();
|
||||||
|
indent(indent, lineBuilder);
|
||||||
|
return lineBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void indent(int indent, StringBuilder lineBuilder) {
|
||||||
|
for (int i = 0; i < indent; i++) {
|
||||||
|
lineBuilder.append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user