diff --git a/build.gradle b/build.gradle index 1c7d114..ea9732a 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ loom { splitEnvironmentSourceSets() mods { - "modid" { + "votifier" { sourceSet sourceSets.main sourceSet sourceSets.client } diff --git a/src/main/java/com/kryeit/votifier/Votifier.java b/src/main/java/com/kryeit/votifier/Votifier.java index f9ef3dc..72a5a1f 100644 --- a/src/main/java/com/kryeit/votifier/Votifier.java +++ b/src/main/java/com/kryeit/votifier/Votifier.java @@ -19,20 +19,21 @@ package com.kryeit.votifier; import java.io.*; +import java.nio.file.Path; import java.security.KeyPair; import java.util.ArrayList; import java.util.List; import java.util.logging.*; +import com.kryeit.votifier.config.ConfigReader; import net.fabricmc.api.DedicatedServerModInitializer; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.plugin.java.JavaPlugin; import com.kryeit.votifier.crypto.RSAIO; import com.kryeit.votifier.crypto.RSAKeygen; import com.kryeit.votifier.model.ListenerLoader; import com.kryeit.votifier.model.VoteListener; import com.kryeit.votifier.net.VoteReceiver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The main Votifier plugin class. @@ -43,7 +44,7 @@ public class Votifier implements DedicatedServerModInitializer { /** The logger instance. */ - private static final Logger LOG = Logger.getLogger("Votifier"); + public static final Logger LOGGER = LoggerFactory.getLogger(Votifier.class); /** Log entry prefix */ private static final String logPrefix = "[Votifier] "; @@ -66,30 +67,23 @@ public class Votifier implements DedicatedServerModInitializer { /** Debug mode flag */ private boolean debug; - /** - * Attach custom log filter to logger. - */ - static { - LOG.setFilter(new LogFilter(logPrefix)); - } - @Override public void onInitializeServer() { Votifier.instance = this; // Set the plugin version. - version = getDescription().getVersion(); + version = "1.0"; // Handle configuration. - if (!getDataFolder().exists()) { - getDataFolder().mkdir(); + try { + LOGGER.info("Reading config file..."); + ConfigReader.readFile(Path.of("config/votifier")); + } catch (IOException e) { + throw new RuntimeException(e); } - File config = new File(getDataFolder() + "/config.yml"); - YamlConfiguration cfg = YamlConfiguration.loadConfiguration(config); - File rsaDirectory = new File(getDataFolder() + "/rsa"); + File rsaDirectory = new File("mods/votifier/rsa"); // Replace to remove a bug with Windows paths - SmilingDevil - String listenerDirectory = getDataFolder().toString() - .replace("\\", "/") + "/listeners"; + String listenerDirectory = "mods/votifier/listeners"; /* * Use IP address from server.properties as a default for @@ -101,44 +95,6 @@ public void onInitializeServer() { if (hostAddr == null || hostAddr.length() == 0) hostAddr = "0.0.0.0"; - /* - * Create configuration file if it does not exists; otherwise, load it - */ - if (!config.exists()) { - try { - // First time run - do some initialization. - LOG.info("Configuring Votifier for the first time..."); - - // Initialize the configuration file. - config.createNewFile(); - - cfg.set("host", hostAddr); - cfg.set("port", 8192); - cfg.set("debug", false); - - /* - * Remind hosted server admins to be sure they have the right - * port number. - */ - LOG.info("------------------------------------------------------------------------------"); - LOG.info("Assigning Votifier to listen on port 8192. If you are hosting Craftbukkit on a"); - LOG.info("shared server please check with your hosting provider to verify that this port"); - LOG.info("is available for your use. Chances are that your hosting provider will assign"); - LOG.info("a different port, which you need to specify in config.yml"); - LOG.info("------------------------------------------------------------------------------"); - - cfg.set("listener_folder", listenerDirectory); - cfg.save(config); - } catch (Exception ex) { - LOG.log(Level.SEVERE, "Error creating configuration file", ex); - gracefulExit(); - return; - } - } else { - // Load configuration. - cfg = YamlConfiguration.loadConfiguration(config); - } - /* * Create RSA directory and keys if it does not exist; otherwise, read * keys. @@ -153,28 +109,27 @@ public void onInitializeServer() { keyPair = RSAIO.load(rsaDirectory); } } catch (Exception ex) { - LOG.log(Level.SEVERE, - "Error reading configuration file or RSA keys", ex); + LOGGER.warn("Error reading configuration file or RSA keys", ex); gracefulExit(); return; } // Load the vote listeners. - listenerDirectory = cfg.getString("listener_folder"); + listenerDirectory = ConfigReader.LISTENER_FOLDER; listeners.addAll(ListenerLoader.load(listenerDirectory)); // Initialize the receiver. - String host = cfg.getString("host", hostAddr); - int port = cfg.getInt("port", 8192); - debug = cfg.getBoolean("debug", false); + String host = ConfigReader.HOST; + int port = ConfigReader.PORT; + debug = ConfigReader.DEBUG; if (debug) - LOG.info("DEBUG mode enabled!"); + LOGGER.info("DEBUG mode enabled!"); try { voteReceiver = new VoteReceiver(this, host, port); voteReceiver.start(); - LOG.info("Votifier enabled."); + LOGGER.info("Votifier enabled."); } catch (Exception ex) { gracefulExit(); return; @@ -188,11 +143,11 @@ public void registerDisableEvent() { if (voteReceiver != null) { voteReceiver.shutdown(); } - LOG.info("Votifier disabled."); + LOGGER.info("Votifier disabled."); } private void gracefulExit() { - LOG.log(Level.SEVERE, "Votifier did not initialize properly!"); + LOGGER.warn("Votifier did not initialize properly!"); } /** diff --git a/src/main/java/com/kryeit/votifier/config/ConfigReader.java b/src/main/java/com/kryeit/votifier/config/ConfigReader.java new file mode 100644 index 0000000..f2ba7c3 --- /dev/null +++ b/src/main/java/com/kryeit/votifier/config/ConfigReader.java @@ -0,0 +1,44 @@ +package com.kryeit.votifier.config; + + +import com.kryeit.votifier.utils.JSONObject; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +public class ConfigReader { + + public static String HOST; + public static int PORT; + public static boolean DEBUG; + public static String LISTENER_FOLDER; + + private ConfigReader() { + + } + + public static void readFile(Path path) throws IOException { + String config = readOrCopyFile(path.resolve("config.json"), "/config.json"); + JSONObject configObject = new JSONObject(config); + HOST = configObject.getString("host"); + PORT = Integer.parseInt(configObject.getString("port")); + DEBUG = configObject.getBoolean("debug"); + LISTENER_FOLDER = configObject.getString("listener-folder"); + } + + public static String readOrCopyFile(Path path, String exampleFile) throws IOException { + File file = path.toFile(); + if (!file.exists()) { + InputStream stream = ConfigReader.class.getResourceAsStream(exampleFile); + if (stream == null) throw new NullPointerException("Cannot load example file"); + + //noinspection ResultOfMethodCallIgnored + file.getParentFile().mkdirs(); + Files.copy(stream, path); + } + return Files.readString(path); + } +} diff --git a/src/main/java/com/kryeit/votifier/utils/JSONObject.java b/src/main/java/com/kryeit/votifier/utils/JSONObject.java new file mode 100644 index 0000000..7226908 --- /dev/null +++ b/src/main/java/com/kryeit/votifier/utils/JSONObject.java @@ -0,0 +1,269 @@ +package com.kryeit.votifier.utils; + +import java.text.ParseException; +import java.util.*; +import java.util.function.BiFunction; + +public class JSONObject { + private final Map data; + public static final Null NULL = new Null(); + + public JSONObject(String input) { + data = new Parser(input.toCharArray()).parseJSONObject().data; + } + + public JSONObject(Map data) { + this.data = data; + } + + public Object get(String key) { + return data.get(key); + } + + public boolean has(String key) { + return data.containsKey(key); + } + + public boolean getBoolean(String key) { + return (boolean) data.get(key); + } + + public String getString(String key) { + return (String) data.get(key); + } + + public long getLong(String key) { + return (long) data.get(key); + } + + public JSONArray getArray(String key) { + return (JSONArray) data.get(key); + } + + public JSONObject getObject(String key) { + return (JSONObject) data.get(key); + } + + public Set> entrySet() { + return data.entrySet(); + } + + public Set keySet() { + return data.keySet(); + } + + public static class JSONArray implements Iterable { + private final List data; + + public JSONArray(List data) { + this.data = data; + } + + public JSONArray(String input) { + Parser parser = new Parser(input.toCharArray()); + data = parser.parseJSONArray().data; + } + + public boolean getBoolean(int i) { + return (boolean) data.get(i); + } + + public String getString(int i) { + return (String) data.get(i); + } + + public long getLong(int i) { + return (long) data.get(i); + } + + public JSONArray getArray(int i) { + return (JSONArray) data.get(i); + } + + public JSONObject getObject(int i) { + return (JSONObject) data.get(i); + } + + @Override + public Iterator iterator() { + return data.iterator(); + } + + public int size() { + return data.size(); + } + + public List asList(BiFunction mapper) { + List out = new ArrayList<>(size()); + for (int i = 0; i < size(); i++) { + out.add(mapper.apply(this, i)); + } + return out; + } + } + + private static class Parser { + private final char[] data; + private int pos = 0; + + private Parser(char[] data) { + this.data = data; + } + + private char currentChar() { + return data[pos]; + } + + private void incrementPosition() { + pos++; + } + + private void skipWhiteSpaces() { + while (Character.isWhitespace(currentChar())) { + incrementPosition(); + } + } + + private String parseString() { + if (currentChar() == '\'') throw exception("JSON standard does not allow single quoted strings"); + if (currentChar() != '\"') throw exception("JSON standard allows only one top-level value"); + + StringBuilder string = new StringBuilder(); + boolean escapeNextChar = false; + + while (true) { + incrementPosition(); + if (escapeNextChar) { + escapeNextChar = false; + if (currentChar() == '\\') string.append('\\'); + else if (currentChar() == '"') string.append('"'); + else throw exception("Invalid escape sequence \\" + currentChar()); + continue; + } else if (currentChar() == '"') { + return string.toString(); + } + + if (currentChar() == '\\') { + escapeNextChar = true; + continue; + } + string.append(currentChar()); + } + } + + private Object parseValue() { + if (currentChar() == '"') { + return parseString(); + } else if (Character.isDigit(currentChar())) { + return parseNumber(); + } else if (currentChar() == 'f' || currentChar() == 't' || currentChar() == 'n') { + return parseConstant(); + } else if (currentChar() == '{') { + return parseJSONObject(); + } else if (currentChar() == '[') { + return parseJSONArray(); + } else { + throw exception("JSON standard does not allow such tokens"); + } + } + + private Object parseConstant() { + StringBuilder word = new StringBuilder(); + while (Character.isLetter(currentChar())) { + word.append(currentChar()); + incrementPosition(); + } + pos--; + + String wordString = word.toString(); + return switch (wordString) { + case "true" -> true; + case "false" -> false; + case "null" -> NULL; + default -> throw exception("JSON standard does not allow such tokens"); + }; + } + + private Number parseNumber() { + StringBuilder number = new StringBuilder(); + while (currentChar() == '.' || Character.isDigit(currentChar())) { + number.append(currentChar()); + incrementPosition(); + } + pos--; + return Double.parseDouble(number.toString()); + } + + private Map parseKeyValue() { + String key = parseString(); + incrementPosition(); + skipWhiteSpaces(); + if (currentChar() != ':') throw exception("Expected '" + ':' + "', got '" + currentChar() + '\''); + incrementPosition(); + skipWhiteSpaces(); + return Map.of(key, parseValue()); + } + + private JSONObject parseJSONObject() { + incrementPosition(); + skipWhiteSpaces(); + Map out = new HashMap<>(parseKeyValue()); + incrementPosition(); + skipWhiteSpaces(); + while (currentChar() != '}') { + if (currentChar() == ',') { + incrementPosition(); + skipWhiteSpaces(); + out.putAll(parseKeyValue()); + } else throw exception("',' or '}' expected"); + incrementPosition(); + skipWhiteSpaces(); + } + return new JSONObject(out); + } + + private JSONArray parseJSONArray() { + incrementPosition(); + skipWhiteSpaces(); + List list = new ArrayList<>(); + list.add(parseValue()); + incrementPosition(); + skipWhiteSpaces(); + + while (currentChar() != ']') { + if (currentChar() == ',') { + incrementPosition(); + skipWhiteSpaces(); + list.add(parseValue()); + } else throw exception("',' or ']' expected"); + incrementPosition(); + skipWhiteSpaces(); + } + return new JSONArray(list); + } + + private RuntimeException exception(String message) { + return new RuntimeException(new ParseException(message, pos)); + } + } + + public static final class Null { + @Override + public String toString() { + return "null"; + } + } + + @Override + public String toString() { + return data.toString(); + } + + public static void main(String[] args) { + JSONObject obj = new JSONObject(""" + { "object": {"a" :59}, "a": [42 , "deine mudda"], + "key": 42.69, "key2": null, "key3": false, "key4":true} + """); + System.out.println(obj); + } +} \ No newline at end of file diff --git a/src/main/resources/config.json b/src/main/resources/config.json new file mode 100644 index 0000000..dc3addf --- /dev/null +++ b/src/main/resources/config.json @@ -0,0 +1,6 @@ +{ + "host": "0.0.0.0", + "port": 8192, + "debug": false, + "listener-folder": "listeners" +} \ No newline at end of file