From e574d508f59a8b923a4cd13b96379127b0cf16df Mon Sep 17 00:00:00 2001 From: Juno Date: Mon, 15 Jan 2024 09:48:16 -0500 Subject: [PATCH] Use JSON for config file --- config/config-readme.txt | 38 +++ config/mirrorlog.conf.json | 18 ++ config/mirrorlog.conf.yml | 86 ------- .../java/org/lavajuno/lucidjson/Json.java | 75 ++++++ .../org/lavajuno/lucidjson/JsonArray.java | 125 ++++++++++ .../org/lavajuno/lucidjson/JsonEntity.java | 222 ++++++++++++++++++ .../org/lavajuno/lucidjson/JsonLiteral.java | 54 +++++ .../org/lavajuno/lucidjson/JsonNumber.java | 100 ++++++++ .../org/lavajuno/lucidjson/JsonObject.java | 133 +++++++++++ .../org/lavajuno/lucidjson/JsonString.java | 42 ++++ .../org/lavajuno/lucidjson/util/Pair.java | 25 ++ .../mirrorlog/config/ApplicationConfig.java | 82 +++---- .../org/lavajuno/mirrorlog/main/LogMap.java | 7 +- .../mirrorlog/main/MirrorLogApplication.java | 13 +- .../mirrorlog/server/ServerController.java | 3 +- .../mirrorlog/server/ServerThread.java | 3 - .../lavajuno/mirrorlog/yaml/YamlElement.java | 126 ---------- .../org/lavajuno/mirrorlog/yaml/YamlList.java | 90 ------- .../org/lavajuno/mirrorlog/yaml/YamlMap.java | 91 ------- .../org/lavajuno/mirrorlog/yaml/YamlPair.java | 41 ---- .../lavajuno/mirrorlog/yaml/YamlValue.java | 55 ----- .../java/org/lavajuno/mirrorlog/TestYaml.java | 188 --------------- 22 files changed, 868 insertions(+), 749 deletions(-) create mode 100644 config/config-readme.txt create mode 100644 config/mirrorlog.conf.json delete mode 100644 config/mirrorlog.conf.yml create mode 100644 src/main/java/org/lavajuno/lucidjson/Json.java create mode 100644 src/main/java/org/lavajuno/lucidjson/JsonArray.java create mode 100644 src/main/java/org/lavajuno/lucidjson/JsonEntity.java create mode 100644 src/main/java/org/lavajuno/lucidjson/JsonLiteral.java create mode 100644 src/main/java/org/lavajuno/lucidjson/JsonNumber.java create mode 100644 src/main/java/org/lavajuno/lucidjson/JsonObject.java create mode 100644 src/main/java/org/lavajuno/lucidjson/JsonString.java create mode 100644 src/main/java/org/lavajuno/lucidjson/util/Pair.java delete mode 100644 src/main/java/org/lavajuno/mirrorlog/yaml/YamlElement.java delete mode 100644 src/main/java/org/lavajuno/mirrorlog/yaml/YamlList.java delete mode 100644 src/main/java/org/lavajuno/mirrorlog/yaml/YamlMap.java delete mode 100644 src/main/java/org/lavajuno/mirrorlog/yaml/YamlPair.java delete mode 100644 src/main/java/org/lavajuno/mirrorlog/yaml/YamlValue.java delete mode 100644 src/test/java/org/lavajuno/mirrorlog/TestYaml.java diff --git a/config/config-readme.txt b/config/config-readme.txt new file mode 100644 index 0000000..d118e3d --- /dev/null +++ b/config/config-readme.txt @@ -0,0 +1,38 @@ +--- Instructions for configuring MirrorLog --- +MirrorLog's configuration is stored in "config/mirrorlog.conf.json". +Available configuration options are listed below: + +"server": + "threads" (int): + - How many connections should the server be able to simultaneously handle? + - Having a lot of threads will slightly increase resource usage, but + having too few threads will cause connections to be rejected. + + "port" (int): + - Which port should the server listen on? + + "timeout" (int): + - How long should the server wait before disconnecting inactive clients? (integer) + - This duration is measured in milliseconds. (15 min is 900000 ms) + - Note that if a client disconnects or a socket error occurs, the connection + will be automatically terminated, so this only handles inactive clients. + + "restricted" (boolean): + - Should the server ignore requests from unknown addresses? + + "allowed_addresses" (list of strings): + - If 'restricted' is true, which addresses should we allow connections from? + +"output": + "component_pad" (int): + - What length should component names be padded up to? + - This makes the log more readable, provided most component names are under this length. + + "log_to_file" (boolean): + - Should the server log to files as well as the console? + + "file_duration" (int): + - How often (in hours) should the server create a new log file? + + "file_history" (int): + - How many old log files should the server retain? diff --git a/config/mirrorlog.conf.json b/config/mirrorlog.conf.json new file mode 100644 index 0000000..4c2a92b --- /dev/null +++ b/config/mirrorlog.conf.json @@ -0,0 +1,18 @@ +{ + "revision": 0, + "server": { + "threads": 32, + "port": 4001, + "timeout": 1800000, + "restricted": false, + "allowed_addresses": [ + "127.0.0.1" + ] + }, + "output": { + "component_pad": 24, + "log_to_file": true, + "file_duration": 24, + "file_history": 10 + } +} diff --git a/config/mirrorlog.conf.yml b/config/mirrorlog.conf.yml deleted file mode 100644 index 6496c26..0000000 --- a/config/mirrorlog.conf.yml +++ /dev/null @@ -1,86 +0,0 @@ -# mirrorlog.conf.yml -# Configuration file for MirrorLog - -# Please note that the YAML parser is very small and has some limitations: -# 1- Lists must be in multiple-line form with proper indentation. -# This will work: -# MyList: -# - MyValue -# This will NOT work: -# MyList: [MyValue] -# MyList: -# - MyValue -# 2- Values must be a single line, spanning & folding are not recognized. -# However, escape characters will be preserved, so you can make newlines this way. -# This will work: -# MyElement: "My\nValue" -# This will NOT work: -# MyElement1: | -# My -# Value -# 3- Comments must be on their own line. -# This will work: -# # This is what MyElement does. -# MyElement: -# This will NOT work: -# MyElement: # This is what MyElement does. - -# Config file format revision (do not change this!) -revision: 0 - -# Configuration for the log server itself -server: - # How many connections should the server be able to simultaneously handle? - # Having a lot of threads will slightly increase resource usage, but - # having too few threads will cause connections to be rejected. - # -- Example -- - # threads: 32 - threads: 32 - - # Which port should the server listen on? (integer) - # -- Example -- - # port: 1234 - port: 4001 - - # How long should the server wait before disconnecting inactive clients? (integer) - # This duration is measured in milliseconds. (15 min is 900000 ms) - # Note that if a client disconnects or a socket error occurs, the connection - # will be automatically terminated, so this only handles inactive clients. - timeout: 1800000 - - # Should the server ignore requests from unknown addresses? (true/false) - # -- Example -- - # restricted: true - restricted: false - - # If 'restricted' is true, which addresses should we allow connections from? (list) - # -- Example -- - # allowed_addresses: - # - 127.0.0.1 - allowed_addresses: - - 127.0.0.1 - -# Configuration for log output -output: - # What length should component names be padded up to? (integer) - # This makes the log more readable, provided most component names are under this length. - # -- Example -- - # component_pad: 24 - component_pad: 24 - - # Should the server log to files as well as the console? (true/false) - # -- Example -- - # log_to_file: true - log_to_file: true - - # How often (in hours) should the server create a new log file? (integer) - # -- Example -- - # file_duration: 24 - file_duration: 24 - - # How many old log files should the server retain? (integer) - # -- Example -- - # file_history: 10 - file_history: 10 - -# That's it! You're done setting up MirrorLog. diff --git a/src/main/java/org/lavajuno/lucidjson/Json.java b/src/main/java/org/lavajuno/lucidjson/Json.java new file mode 100644 index 0000000..7ef5a61 --- /dev/null +++ b/src/main/java/org/lavajuno/lucidjson/Json.java @@ -0,0 +1,75 @@ +/** + * LucidJSON v0.0.1 - Experimental + */ + +package org.lavajuno.lucidjson; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.ParseException; +import java.util.List; +import java.util.Scanner; + +/** + * Provides functionality for serializing/deserializing JSON to/from Strings and files. + */ +@SuppressWarnings("unused") +public class Json { + /** + * Deserializes JSON object from a String. + * @param text Input string + * @return Deserialized JSON object + * @throws ParseException If parsing fails + */ + public static JsonObject read(String text) throws ParseException { + return new JsonObject(text.replace("\n", "")); + } + + /** + * Deserializes a JSON object from a list of lines (Strings). + * @param lines Input lines + * @return Deserialized JSON object + * @throws ParseException If parsing fails + */ + public static JsonObject read(List lines) throws ParseException { + StringBuilder sb = new StringBuilder(); + for(String i : lines) { sb.append(i); } + return read(sb.toString()); + } + + /** + * Deserializes a JSON object from a file. + * @param file_path Path to the input file + * @return Deserialized JSON object + * @throws IOException If reading the file fails + * @throws ParseException If parsing fails + */ + public static JsonObject readFile(String file_path) throws IOException, ParseException { + Scanner file = new Scanner(new FileInputStream(file_path)); + StringBuilder lines = new StringBuilder(); + while(file.hasNextLine()) { lines.append(file.nextLine()); } + file.close(); + return read(lines.toString()); + } + + /** + * Serializes a JSON object to a String. + * @param e Input JSON object + * @return String containing the serialized JSON object + */ + public static String write(JsonObject e) { return e.toString(0); } + + /** + * Serializes a JSON object to a file. + * @param e Input JSON object + * @param file_path Path to the target file + * @throws IOException If writing to the file fails + */ + public static void writeFile(JsonObject e, String file_path) throws IOException { + PrintWriter file = new PrintWriter(new FileOutputStream(file_path)); + file.print(e.toString(0)); + file.close(); + } +} diff --git a/src/main/java/org/lavajuno/lucidjson/JsonArray.java b/src/main/java/org/lavajuno/lucidjson/JsonArray.java new file mode 100644 index 0000000..c3fffe9 --- /dev/null +++ b/src/main/java/org/lavajuno/lucidjson/JsonArray.java @@ -0,0 +1,125 @@ +package org.lavajuno.lucidjson; + +import java.text.ParseException; +import java.util.Vector; + +/** + * Represents a JSON array. + * Provides functionality for accessing and modifying its values. + */ +@SuppressWarnings("unused") +public class JsonArray extends JsonEntity { + private final Vector values; + + /** + * Constructs an empty JsonArray. + */ + public JsonArray() { values = new Vector<>(); } + + /** + * Constructs a JsonArray from the given vector of elements. + * @param values Values to initialize array with + */ + public JsonArray(Vector values) { this.values = values; } + + /** + * Constructs a JsonArray by parsing the input. + * @param text JSON to parse + * @throws ParseException If an error is encountered while parsing the input + */ + protected JsonArray(String text) throws ParseException { + values = parseValues(text.strip()); + } + + /** + * @param text JSON to parse + * @return Vector created from the input + * @throws ParseException If an error is encountered while parsing the input + */ + private static Vector parseValues(String text) throws ParseException { + Vector values = new Vector<>(); + Vector raw_values = splitValues(text); + for(String i : raw_values) { + if(!i.isEmpty()) { values.add(parseEntity(i.strip())); } + } + return values; + } + + /** + * @param index Index of the target JsonEntity + * @return JsonEntity at the given index (null if it does not exist) + */ + public JsonEntity get(int index) { + try { + return values.get(index); + } catch(ArrayIndexOutOfBoundsException e) { + return null; + } + } + + /** + * @param index Index of the target JsonEntity + * @param value New value for the target JsonEntity + */ + public void set(int index, JsonEntity value) { values.set(index, value); } + + /** + * @param value JsonEntity to be added to this JsonArray + */ + public void add(JsonEntity value) { values.add(value); } + + /** + * @param index Index of the JsonEntity to remove + */ + public void remove(int index) { values.remove(index); } + + /** + * Clears this JsonArray + */ + public void clear() { values.clear(); } + + /** + * @return The number of entities contained by this JsonArray + */ + public int size() { return values.size(); } + + /** + * @return This JsonArray's elements + */ + public Vector getValues() { return values; } + + /** + * Serializes this JsonArray to a String, with indentation and newlines. + * @param indent Indent of this JsonEntity (0) + * @return Returns this JsonEntity as a string. + */ + @Override + protected String toString(int indent) { + StringBuilder sb = new StringBuilder(); + String pad_elem = " ".repeat(indent + 4); + String pad_close = " ".repeat(indent); + sb.append("[\n"); + for(int i = 0; i < values.size() - 1; i++) { + sb.append(pad_elem).append(values.get(i).toString(indent + 4)).append(",\n"); + } + if(!values.isEmpty()) { + sb.append(pad_elem).append(values.get(values.size() - 1)).append("\n"); + } + sb.append(pad_close).append("]"); + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("["); + for(int i = 0; i < values.size() - 1; i++) { + sb.append(values.get(i)).append(",\n"); + } + if(!values.isEmpty()) { + sb.append(values.get(values.size() - 1)); + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/src/main/java/org/lavajuno/lucidjson/JsonEntity.java b/src/main/java/org/lavajuno/lucidjson/JsonEntity.java new file mode 100644 index 0000000..5b02b0c --- /dev/null +++ b/src/main/java/org/lavajuno/lucidjson/JsonEntity.java @@ -0,0 +1,222 @@ +package org.lavajuno.lucidjson; + +import org.lavajuno.lucidjson.util.Pair; + +import java.text.ParseException; +import java.util.Vector; + +/** + * Abstract representation of a single JSON entity. + * Instances of JsonEntity can be objects, arrays, strings, numbers, or literals. + * JsonEntity provides functionality for validating and parsing JSON for its inheritors. + */ +@SuppressWarnings("unused") +public abstract class JsonEntity { + protected static final String PAIR_RGX = "^\".+\" *: *.+$"; + protected static final String OBJECT_RGX = "^\\{.*}$"; + protected static final String ARRAY_RGX = "^\\[.*]$"; + protected static final String STRING_RGX = "^ *\".*\"$"; + protected static final String NUMBER_RGX = "^-?[0-9]+(\\.[0-9]+)?([Ee][0-9]+)?$"; + protected static final String LITERAL_RGX = "^(true|false|null)$"; + + /** + * Possible matches for a segment of text + */ + protected enum Matches { + OBJECT, ARRAY, STRING, NUMBER, LITERAL, NONE + } + + /** + * Matches the input text to a predefined category. + * @param text Text to match + * @return The category that the text is matched to. Can be NONE. + */ + protected static Matches match(String text) { + if(text.matches(OBJECT_RGX)) { return Matches.OBJECT; } + if(text.matches(ARRAY_RGX)) { return Matches.ARRAY; } + if(text.matches(STRING_RGX)) { return Matches.STRING; } + if(text.matches(NUMBER_RGX)) { return Matches.NUMBER; } + if(text.matches(LITERAL_RGX)) { return Matches.LITERAL; } + return Matches.NONE; + } + + /** + * Scans a line and finds the index of the character that closes + * an entity in a scoped structure. ( ex. open->{<- { { } } { } close->}<- ) + * @param line Line to scan + * @param open Opening character + * @param close Closing character + * @param start Index to start scanning at + * @return Index of the closing character. -1 if no character is found. + */ + protected static int findClosing(String line, char open, char close, int start) { + int scope = 0; + boolean enclosed = false; + for(int i = start + 1; i < line.length(); i++) { + if(line.charAt(i) == '"' && line.charAt(i - 1) != '\\') { + enclosed = !enclosed; // double quotes, not escaped + } else if(!enclosed && line.charAt(i) == open && line.charAt(i - 1) != '\\') { + scope++; // not enclosed, opening character, not escaped + } else if(!enclosed && line.charAt(i) == close && line.charAt(i - 1) != '\\') { + // not enclosed, closing character, not escaped + if(scope == 0) { return i; } + scope--; + } + } + return -1; // fail + } + + /** + * Scans the input text and finds the index of the next instance of a + * character that is not enclosed by a pair of another given character. + * @param text Text to scan + * @param c Character to find + * @param start Index to start scanning at + * @return Index of the found character. -1 if not character is found. + */ + protected static int findNext(String text, char c, int start) { + boolean enclosed = text.charAt(start) == '\"'; + for(int i = start + 1; i < text.length(); i++) { + if(text.charAt(i) == '"' && text.charAt(i - 1) != '\\') { + enclosed = !enclosed; // double quotes, not escaped + } else if(!enclosed && text.charAt(i) == c && text.charAt(i - 1) != '\\') { + return i; // not enclosed, target character, not escaped + } + } + return -1; // fail + } + + /** + * Scans the input text and finds the index of the next instance of a + * delimiter character that is not enclosed by an object or array. + * @param text Text to scan + * @param c Delimiter character to find + * @param start Index to start scanning at + * @return Index of the found character. -1 if not character is found. + */ + protected static int findDelimiter(String text, char c, int start) { + boolean enclosed = false; + int i = start; + for(; i < text.length(); i++) { + if(text.charAt(i) == '"' && text.charAt(i - 1) != '\\') { + + enclosed = !enclosed; // double quotes, not escaped + } else if(!enclosed && text.charAt(i) == '{') { + i = findClosing(text, '{', '}', i); // ignore commas in objects + } else if(!enclosed && text.charAt(i) == '[') { + i = findClosing(text, '[', ']', i); // ignore commas in arrays + } else if(!enclosed && text.charAt(i) == c) { + return i; // found comma + } + } + return -1; // fail + } + + /** + * Scans the input and splits comma-delimited values of a JSON + * object or array into a Vector of Strings. + * @param text Text to scan + * @return Elements of the JSON entity as a Vector of Strings. + */ + protected static Vector splitValues(String text) { + Vector values = new Vector<>(); + int e_start = 1; // ignore opening '[' / '{' + int e_end = 0; + while(true) { + e_end = findDelimiter(text, ',', e_start); + if(e_end == -1) { + // last element (no trailing comma) + values.add(text.substring(e_start, text.length() - 1)); // ignore closing ']' / '}' + break; + } + values.add(text.substring(e_start, e_end)); + e_start = e_end + 1; // next element, skip comma + } + return values; + } + + /** + * Scans text containing a key-value pair and returns the key. + * @param text Text to scan + * @param end Index of the end of the key + * @return Key found in the pair + * @throws ParseException If the input does not contain a key. + */ + protected static String parseKey(String text, int end) throws ParseException { + String key_raw = text.substring(0, end).strip(); + if(!key_raw.matches(STRING_RGX)) { + printError(text.substring(0, end), "Expected a string."); + throw new ParseException("Expected a string.", 0); + } + return key_raw.substring(1, key_raw.length() - 1); + } + + /** + * Prints a parse error to stderr. + * @param text The text that caused the parse error + * @param explanation Why the parse error happened + */ + protected static void printError(String text, String explanation) { + System.err.println("vvv JSON - Parse error on input: vvv"); + System.err.println(text); + System.err.println("^^^ ---------------------------- ^^^"); + System.err.println(explanation + "\n"); /* extra newline */ + } + + /** + * Constructs a single JsonEntity by parsing the input. + * @param text Text to parse + * @return JsonEntity created from the input. + * @throws ParseException If the input does not match any type of entity + */ + protected static JsonEntity parseEntity(String text) throws ParseException { + JsonEntity entity = switch(match(text)) { + case OBJECT -> new JsonObject(text); + case ARRAY -> new JsonArray(text); + case STRING -> new JsonString(text); + case NUMBER -> new JsonNumber(text); + case LITERAL -> new JsonLiteral(text); + case NONE -> null; + }; + if(entity == null) { + printError(text, "Expected an entity."); + throw new ParseException("Expected an entity.", 0); + } + return entity; + } + + /** + * Constructs a key-value pair (String : JsonEntity) by parsing the input. + * @param text Text to parse + * @return Key-value pair created from the input. + * @throws ParseException If the input does not match a pair containing a String and JsonEntity + */ + protected static Pair parsePair(String text) throws ParseException { + if(text.matches(PAIR_RGX)) { + int split_index = findNext(text, ':', 0); + if(split_index == -1) { + printError(text, "Expected a key-value pair."); + throw new ParseException("Expected a key-value pair.", 0); + } + String key = parseKey(text, split_index); + JsonEntity value = parseEntity(text.substring(split_index + 1).strip()); + return new Pair<>(key, value); + } + printError(text, "Expected a key-value pair. (Not matched)"); + throw new ParseException("Expected a key-value pair. (Not matched)", 0); + } + + /** + * Serializes this JsonEntity to a String with newlines and indentation. + * @param indent Indent of this JsonEntity + * @return This JsonEntity as a string. + */ + protected abstract String toString(int indent); + + /** + * Serializes this JsonEntity to a String without any formatting. + * @return This JsonEntity as a string. + */ + @Override + public abstract String toString(); +} diff --git a/src/main/java/org/lavajuno/lucidjson/JsonLiteral.java b/src/main/java/org/lavajuno/lucidjson/JsonLiteral.java new file mode 100644 index 0000000..4df7b2c --- /dev/null +++ b/src/main/java/org/lavajuno/lucidjson/JsonLiteral.java @@ -0,0 +1,54 @@ +package org.lavajuno.lucidjson; + +/** + * Represents a JSON literal value (true/false/null). + * Provides functionality for getting and setting the value. + */ +@SuppressWarnings("unused") +public class JsonLiteral extends JsonEntity { + Boolean value; + + /** + * Constructs a JsonLiteral with a value of null. + */ + public JsonLiteral() { this.value = null; } + + /** + * Constructs a JsonLiteral with the given value. + * @param value Value of this JsonLiteral (true/false/null) + */ + public JsonLiteral(Boolean value) { this.value = value; } + + /** + * Constructs a JsonLiteral by parsing the input. + * @param text JSON to parse + */ + protected JsonLiteral(String text) { + switch (text) { + case "null" -> value = null; + case "true" -> value = true; + case "false" -> value = false; + } + } + + /** + * Sets the value of this JsonLiteral. + * @param value Value of this JsonLiteral (true/false/null) + */ + public void setValue(Boolean value) { this.value = value; } + + /** + * Gets the value of this JsonLiteral. + * @return Value of this JsonLiteral (true/false/null) + */ + public Boolean getValue() { return value; } + + @Override + public String toString() { + if(value == null) { return "null"; } + return value ? "true" : "false"; + } + + @Override + protected String toString(int indent) { return this.toString(); } +} diff --git a/src/main/java/org/lavajuno/lucidjson/JsonNumber.java b/src/main/java/org/lavajuno/lucidjson/JsonNumber.java new file mode 100644 index 0000000..23f96f9 --- /dev/null +++ b/src/main/java/org/lavajuno/lucidjson/JsonNumber.java @@ -0,0 +1,100 @@ +package org.lavajuno.lucidjson; + +/** + * Represents a JSON number value. + * Provides functionality for getting and setting the value as an int, long, float, or double. + */ +@SuppressWarnings("unused") +public class JsonNumber extends JsonEntity { + private String value; + + /** + * Constructs a JsonNumber from an int. + * @param value Value of this JsonNumber + */ + public JsonNumber(int value) { this.value = Integer.toString(value); } + + /** + * Constructs a JsonNumber from a long. + * @param value Value of this JsonNumber + */ + public JsonNumber(long value) { this.value = Long.toString(value); } + + /** + * Constructs a JsonNumber from a float. + * @param value Value of this JsonNumber + */ + public JsonNumber(float value) { this.value = Float.toString(value); } + + /** + * Constructs a JsonNumber from a double. + * @param value Value of this JsonNumber + */ + public JsonNumber(double value) { this.value = Double.toString(value); } + + /** + * Constructs a JsonNumber from the input. + * @param value JSON number + */ + protected JsonNumber(String value) { + this.value = value; + } + + /** + * Gets the value of this JsonNumber. + * @return Value of this JsonNumber as an int + * @throws NumberFormatException If this JsonNumber cannot be parsed as an int + */ + public int getInt() throws NumberFormatException { return Integer.parseInt(value); } + + /** + * Gets the value of this JsonNumber. + * @return Value of this JsonNumber as a long + * @throws NumberFormatException If this JsonNumber cannot be parsed as a long + */ + public long getLong() throws NumberFormatException { return Long.parseLong(value); } + + /** + * Gets the value of this JsonNumber. + * @return Value of this JsonNumber as a float + * @throws NumberFormatException If this JsonNumber cannot be parsed as a float + */ + public float getFloat() throws NumberFormatException { return Float.parseFloat(value); } + + /** + * Gets the value of this JsonNumber. + * @return Value of this JsonNumber as a double + * @throws NumberFormatException If this JsonNumber cannot be parsed as a double + */ + public double getDouble() throws NumberFormatException { return Double.parseDouble(value); } + + /** + * Sets the value of this JsonNumber. + * @param value Int value of this JsonNumber + */ + public void set(int value) { this.value = Integer.toString(value); } + + /** + * Sets the value of this JsonNumber. + * @param value Long value of this JsonNumber + */ + public void set(long value) { this.value = Long.toString(value); } + + /** + * Sets the value of this JsonNumber. + * @param value Float value of this JsonNumber + */ + public void set(float value) { this.value = Float.toString(value); } + + /** + * Sets the value of this JsonNumber. + * @param value Double value of this JsonNumber + */ + public void set(double value) { this.value = Double.toString(value); } + + @Override + public String toString() { return value; } + + @Override + protected String toString(int indent) { return this.toString(); } +} diff --git a/src/main/java/org/lavajuno/lucidjson/JsonObject.java b/src/main/java/org/lavajuno/lucidjson/JsonObject.java new file mode 100644 index 0000000..af9ed29 --- /dev/null +++ b/src/main/java/org/lavajuno/lucidjson/JsonObject.java @@ -0,0 +1,133 @@ +package org.lavajuno.lucidjson; + +import org.lavajuno.lucidjson.util.Pair; + +import java.text.ParseException; +import java.util.Collection; +import java.util.Set; +import java.util.TreeMap; +import java.util.Vector; + +/** + * Represents a JSON object. + * Provides functionality for accessing and modifying its values. + */ +@SuppressWarnings("unused") +public class JsonObject extends JsonEntity { + private final TreeMap values; + + /** + * Constructs an empty JsonObject. + */ + public JsonObject() { values = new TreeMap<>(); } + + /** + * Constructs a JsonObject from the given map. + * @param values Values to initialize map with + */ + public JsonObject(TreeMap values) { this.values = values; } + + /** + * Constructs a JsonObject by parsing the input. + * @param text JSON to parse + * @throws ParseException If an error is encountered while parsing the input + */ + protected JsonObject(String text) throws ParseException { + values = parseValues(text.strip()); + } + + /** + * @param text JSON to parse + * @return Key-value map created from the input + * @throws ParseException If an error is encountered while parsing the input + */ + private static TreeMap parseValues(String text) throws ParseException { + TreeMap values = new TreeMap<>(); + Vector raw_values = splitValues(text); + for(String i : raw_values) { + if(!i.isEmpty()) { + Pair p = parsePair(i.strip()); + values.put(p.first, p.second); + } + } + return values; + } + + /** + * Gets a JsonEntity with a given key. + * @param key Key of the target JsonEntity + * @return JsonEntity with the specified key, or null if it does not exist + */ + public JsonEntity get(String key) { return values.get(key); } + + /** + * Puts a JsonEntity under a given key. Will overwrite the previous + * entity if it already exists. + * @param key Key of the target JsonEntity + * @param value New value for the target JsonEntity + */ + public void put(String key, JsonEntity value) { values.put(key, value); } + + /** + * Removes the JsonEntity at the specified key. + * @param key Key of the JsonEntity to remove + */ + public void remove(String key) { values.remove(key); } + + /** + * Clears this JsonObject's map. + */ + public void clear() { values.clear(); } + + /** + * Gets a collection of all the keys in this JsonObject + * @return This JsonObject's keys + */ + public Collection getKeys() { return values.keySet(); } + + /** + * Gets a collection of all the values in this JsonObject + * @return This JsonObject's values + */ + public Collection getValues() { return values.values(); } + + /** + * Gets ths size of this JsonObject + * @return The number of entities contained by this JsonObject + */ + public int size() { return values.size(); } + + @Override + protected String toString(int indent) { + StringBuilder sb = new StringBuilder(); + String pad_elem = " ".repeat(indent + 4); + String pad_close = " ".repeat(indent); + sb.append("{\n"); + Set keys = values.keySet(); + int i = 0; + for(String j : keys) { + i++; + sb.append(pad_elem).append("\"").append(j).append("\": "); + sb.append(values.get(j).toString(indent + 4)); + if(i < keys.size()) { sb.append(","); } + sb.append("\n"); + } + sb.append(pad_close).append("}"); + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + Set keys = values.keySet(); + int i = 0; + for(String j : keys) { + i++; + sb.append("\"").append(j).append("\":").append(values.get(j)); + if(i < keys.size()) { sb.append(","); } + } + sb.append("}"); + return sb.toString(); + } +} diff --git a/src/main/java/org/lavajuno/lucidjson/JsonString.java b/src/main/java/org/lavajuno/lucidjson/JsonString.java new file mode 100644 index 0000000..3b51ed5 --- /dev/null +++ b/src/main/java/org/lavajuno/lucidjson/JsonString.java @@ -0,0 +1,42 @@ +package org.lavajuno.lucidjson; + +/** + * Represents a JSON string value. + * Provides functionality for getting and setting the value. + */ +@SuppressWarnings("unused") +public class JsonString extends JsonEntity { + private String value; + + /** + * Constructs a JsonValue by parsing the input. + * @param text JSON string to parse + */ + public JsonString(String text) { + if(text.charAt(0) == '"' && text.charAt(text.length() - 1) == '"') { + value = text.substring(1, text.length() - 1); + } else { + value = text; + } + } + + /** + * Gets the value of this JsonString. + * @return Value of this JsonString + */ + public String getValue() { return value; } + + /** + * Sets the value of this JsonString + * @param value Value of this JsonString + */ + public void setValue(String value) { this.value = value; } + + @Override + public String toString() { + return "\"" + value + "\""; + } + + @Override + protected String toString(int indent) { return this.toString(); } +} diff --git a/src/main/java/org/lavajuno/lucidjson/util/Pair.java b/src/main/java/org/lavajuno/lucidjson/util/Pair.java new file mode 100644 index 0000000..b614e03 --- /dev/null +++ b/src/main/java/org/lavajuno/lucidjson/util/Pair.java @@ -0,0 +1,25 @@ +package org.lavajuno.lucidjson.util; + +/** + * A pair of any two types. + * @param First item's type + * @param Second item's type + */ +public class Pair { + /** + * First item of this Pair + */ + public A first; + + /** + * Second item of this Pair + */ + public B second; + + /** + * Constructs a Pair. + * @param first First item of this Pair + * @param second Second item of this Pair + */ + public Pair(A first, B second) { this.first = first; this.second = second; } +} diff --git a/src/main/java/org/lavajuno/mirrorlog/config/ApplicationConfig.java b/src/main/java/org/lavajuno/mirrorlog/config/ApplicationConfig.java index e6f5cc5..9ee2610 100644 --- a/src/main/java/org/lavajuno/mirrorlog/config/ApplicationConfig.java +++ b/src/main/java/org/lavajuno/mirrorlog/config/ApplicationConfig.java @@ -1,14 +1,11 @@ package org.lavajuno.mirrorlog.config; -import org.lavajuno.mirrorlog.yaml.*; +import org.lavajuno.lucidjson.*; -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.List; +import java.text.ParseException; import java.util.Vector; /** @@ -35,63 +32,64 @@ public class ApplicationConfig { * @param config_file_path Path to the configuration file to load * @throws IOException If loading the configuration fails. */ - public ApplicationConfig(String config_file_path) throws IOException { + public ApplicationConfig(String config_file_path) throws IOException, ParseException { /* Parse configuration file */ - final YamlMap config_root = new YamlMap(readLinesFromFile(config_file_path)); + final JsonObject config_root = Json.readFile(config_file_path); /* Get configuration revision */ - final YamlValue config_revision = ((YamlPair) config_root.getElement("revision")).getValue(); + final JsonNumber config_revision = (JsonNumber) config_root.get("revision"); /* Get server configuration */ - final YamlMap config_server = (YamlMap) config_root.getElement("server"); - final YamlValue config_threads = ((YamlPair) config_server.getElement("threads")).getValue(); - final YamlValue config_port = ((YamlPair) config_server.getElement("port")).getValue(); - final YamlValue config_timeout = ((YamlPair) config_server.getElement("timeout")).getValue(); - final YamlValue config_restricted = ((YamlPair) config_server.getElement("restricted")).getValue(); - final YamlList config_addresses = (YamlList) config_server.getElement("allowed_addresses"); + final JsonObject config_server = (JsonObject) config_root.get("server"); + final JsonNumber config_threads = (JsonNumber) config_server.get("threads"); + final JsonNumber config_port = (JsonNumber) config_server.get("port"); + final JsonNumber config_timeout = (JsonNumber) config_server.get("timeout"); + final JsonLiteral config_restricted = (JsonLiteral) config_server.get("restricted"); + final JsonArray config_addresses = (JsonArray) config_server.get("allowed_addresses"); /* Get log file configuration */ - final YamlMap config_output = (YamlMap) config_root.getElement("output"); - final YamlValue config_component_pad = ((YamlPair) config_output.getElement("component_pad")).getValue(); - final YamlValue config_log_to_file = ((YamlPair) config_output.getElement("log_to_file")).getValue(); - final YamlValue config_file_duration = ((YamlPair) config_output.getElement("file_duration")).getValue(); - final YamlValue config_file_history = ((YamlPair) config_output.getElement("file_history")).getValue(); + final JsonObject config_output = (JsonObject) config_root.get("output"); + final JsonNumber config_component_pad = (JsonNumber) config_output.get("component_pad"); + final JsonLiteral config_log_to_file = (JsonLiteral) config_output.get("log_to_file"); + final JsonNumber config_file_duration = (JsonNumber) config_output.get("file_duration"); + final JsonNumber config_file_history = (JsonNumber) config_output.get("file_history"); /* Read in and set configuration values */ try { - this.revision = config_revision.toInt(); + this.revision = config_revision.getInt(); } catch(NumberFormatException e) { System.err.println("Illegal value for key \"version\"."); throw new IOException("Illegal value for key \"version\"."); } try { - this.threads = config_threads.toInt(); + this.threads = config_threads.getInt(); } catch(NumberFormatException e) { System.err.println("Illegal value for key \"threads\"."); throw new IOException("Illegal value for key \"threads\"."); } try { - this.port = config_port.toInt(); + this.port = config_port.getInt(); } catch(NumberFormatException e) { System.err.println("Illegal value for key \"port\"."); throw new IOException("Illegal value for key \"port\"."); } try { - this.timeout = config_timeout.toInt(); + this.timeout = config_timeout.getInt(); } catch(NumberFormatException e) { System.err.println("Illegal value for key \"timeout\"."); throw new IOException("Illegal value for key \"timeout\"."); } - this.restricted = Boolean.parseBoolean(config_restricted.toString()); + this.restricted = config_restricted.getValue(); allowed_addresses = new Vector<>(); try { - for(YamlElement i : config_addresses.getElements()) { - allowed_addresses.add(InetAddress.getByName( i.toString()) ); + for(JsonEntity i : config_addresses.getValues()) { + JsonString j = (JsonString) i; + allowed_addresses.add(InetAddress.getByName(j.getValue()) ); } } catch(UnknownHostException e) { System.err.println("Illegal value for key \"allowed_addresses\"."); @@ -99,23 +97,23 @@ public ApplicationConfig(String config_file_path) throws IOException { } try { - this.component_pad = config_component_pad.toInt(); + this.component_pad = config_component_pad.getInt(); } catch(NumberFormatException e) { System.err.println("Illegal value for key \"component_pad\"."); throw new IOException("Illegal value for key \"component_pad\"."); } - this.log_to_file = Boolean.parseBoolean(config_log_to_file.toString()); + this.log_to_file = config_log_to_file.getValue(); try { - this.file_duration = config_file_duration.toInt(); + this.file_duration = config_file_duration.getInt(); } catch(NumberFormatException e) { System.err.println("Illegal value for key \"file_duration\"."); throw new IOException("Illegal value for key \"file_duration\"."); } try { - this.file_history = config_file_history.toInt(); + this.file_history = config_file_history.getInt(); } catch(NumberFormatException e) { System.err.println("Illegal value for key \"file_history\"."); throw new IOException("Illegal value for key \"file_history\"."); @@ -179,30 +177,6 @@ public ApplicationConfig(String config_file_path) throws IOException { */ public int getFileHistory() { return file_history; } - /** - * Reads a list of lines from a file - * @param file_path File path to read - * @return Lines in the file - * @throws IOException If reading from the file fails - */ - private static List readLinesFromFile(String file_path) throws IOException { - try { - BufferedReader f = new BufferedReader(new FileReader(file_path)); - Vector lines = new Vector<>(); - for(String line = f.readLine(); line != null; line = f.readLine()) { - lines.add(line); - } - f.close(); - return lines; - } catch(FileNotFoundException e) { - System.err.println("File \"" + file_path + "\" could not be read. (Not Found)"); - throw new IOException("File \"" + file_path + "\" could not be read. (Not Found)"); - } catch(IOException e) { - System.err.println("File \"" + file_path + "\" could not be read. (IOException)"); - throw(e); - } - } - @Override public String toString() { return String.format( diff --git a/src/main/java/org/lavajuno/mirrorlog/main/LogMap.java b/src/main/java/org/lavajuno/mirrorlog/main/LogMap.java index 71d9514..fb34800 100644 --- a/src/main/java/org/lavajuno/mirrorlog/main/LogMap.java +++ b/src/main/java/org/lavajuno/mirrorlog/main/LogMap.java @@ -11,12 +11,7 @@ public class LogMap { /** * The path to the program's configuration file */ - public static final String CONFIG_FILE_PATH = "config/mirrorlog.conf.yml"; - - /** - * The size of the input buffer for log events (bytes) - */ - public static final int INPUT_BUFFER_SIZE = 4096; + public static final String CONFIG_FILE_PATH = "config/mirrorlog.conf.json"; /** * How long ServerController should wait for OutputController to shut down (ms) diff --git a/src/main/java/org/lavajuno/mirrorlog/main/MirrorLogApplication.java b/src/main/java/org/lavajuno/mirrorlog/main/MirrorLogApplication.java index b1d7f87..6ec199e 100644 --- a/src/main/java/org/lavajuno/mirrorlog/main/MirrorLogApplication.java +++ b/src/main/java/org/lavajuno/mirrorlog/main/MirrorLogApplication.java @@ -1,6 +1,8 @@ package org.lavajuno.mirrorlog.main; import java.io.IOException; +import java.text.ParseException; + import org.lavajuno.mirrorlog.server.ServerController; /** @@ -12,15 +14,10 @@ public class MirrorLogApplication { * Starts MirrorLog. * @param args Unused */ - public static void main(String[] args) { + public static void main(String[] args) throws IOException, ParseException { ServerController server_controller; System.out.println("Starting MirrorLog server..."); - try { - server_controller = new ServerController(); - server_controller.start(); - } catch(IOException e) { - System.err.println("Failed to start server! (IOException)"); - System.err.println(e.getMessage()); - } + server_controller = new ServerController(); + server_controller.start(); } } diff --git a/src/main/java/org/lavajuno/mirrorlog/server/ServerController.java b/src/main/java/org/lavajuno/mirrorlog/server/ServerController.java index f8412f2..56a048d 100644 --- a/src/main/java/org/lavajuno/mirrorlog/server/ServerController.java +++ b/src/main/java/org/lavajuno/mirrorlog/server/ServerController.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.net.ServerSocket; +import java.text.ParseException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; @@ -39,7 +40,7 @@ public class ServerController extends Thread { * Instantiates a ServerController. * @throws IOException if the socket cannot be created */ - public ServerController() throws IOException { + public ServerController() throws IOException, ParseException { application_config = new ApplicationConfig(LogMap.CONFIG_FILE_PATH); output_controller = new OutputController(application_config); threadPool = Executors.newFixedThreadPool(application_config.getThreads()); diff --git a/src/main/java/org/lavajuno/mirrorlog/server/ServerThread.java b/src/main/java/org/lavajuno/mirrorlog/server/ServerThread.java index 21c807f..f26e885 100644 --- a/src/main/java/org/lavajuno/mirrorlog/server/ServerThread.java +++ b/src/main/java/org/lavajuno/mirrorlog/server/ServerThread.java @@ -5,13 +5,10 @@ import java.net.InetAddress; import java.net.Socket; import java.net.SocketException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.Scanner; import org.lavajuno.mirrorlog.config.ApplicationConfig; import org.lavajuno.mirrorlog.io.OutputController; -import org.lavajuno.mirrorlog.main.LogMap; /** * ServerThread serves a single client and queues events diff --git a/src/main/java/org/lavajuno/mirrorlog/yaml/YamlElement.java b/src/main/java/org/lavajuno/mirrorlog/yaml/YamlElement.java deleted file mode 100644 index eaa940e..0000000 --- a/src/main/java/org/lavajuno/mirrorlog/yaml/YamlElement.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.lavajuno.mirrorlog.yaml; - -import java.util.InvalidPropertiesFormatException; -import java.util.List; - -/** - * YamlElement represents a single YAML element that can contain one of the following: - * - An ordered list of elements - * - An unordered map of elements - * - A key-value pair - * - A value by itself - */ -public abstract class YamlElement { - /** - * Matches lines to ignore (blank or commented) - */ - static final String IGNORE_RGX = "^( *#.*)|( *)$"; - - /** - * Matches lines containing an element (ex. "ElementName: ") - */ - static final String ELEMENT_RGX = "^ *(- +)?[A-Za-z0-9_-]+: *$"; - - /** - * Matches lines containing a key-value pair (ex. "ElementName: ElementValue") - */ - static final String PAIR_RGX = "^ *(- +)?[A-Za-z0-9_-]+: +.+$"; - - /** - * Matches lines containing a list item (ex. " - ListItem") - */ - static final String LIST_RGX = "^ *- +[^ ].+$"; - - /** - * Possible matches for a line - */ - protected enum LINE_MATCHES { - IGNORE, ELEMENT, PAIR, LIST_ELEMENT, LIST_PAIR, LIST_VALUE, NONE - } - - /** - * Constructs and returns a list/map element dependent on the input. - * @param key Key for the new element - * @param lines Lines we are parsing - * @param begin Where to begin parsing - * @param indent Indent of the new element's elements - * @return A new YamlElement. (Either a YamlList or a YamlMap) - * @throws InvalidPropertiesFormatException If an error is encountered while parsing - */ - protected static YamlElement parseElement(String key, List lines, int begin, int indent) - throws InvalidPropertiesFormatException { - int i = begin + 1; - for(; i < lines.size(); i++) { - if(!lines.get(i).matches(IGNORE_RGX)) { break; } - } - if (i >= lines.size()) { - throw new InvalidPropertiesFormatException("Reached end of input."); - } - return switch (matchLine(lines.get(i))) { - case LIST_ELEMENT, LIST_PAIR, LIST_VALUE -> new YamlList(key, lines, i, indent); - case ELEMENT, PAIR -> new YamlMap(key, lines, i, indent); - default -> { - printParseError(lines.get(i), i, "Unexpected line."); - throw new InvalidPropertiesFormatException("Parse error on line " + i + "."); - } - }; - } - - /** - * Matches a line to a predefined category - * @param line Line to match - * @return Category this line is matched to (can be NONE) - */ - protected static LINE_MATCHES matchLine(String line) { - /* Filter comments and blank lines */ - if(line.matches(IGNORE_RGX)) { return LINE_MATCHES.IGNORE; } - /* Ordered list elements */ - if(line.matches(LIST_RGX)) { - if(line.matches(ELEMENT_RGX)) { return LINE_MATCHES.LIST_ELEMENT; } - if(line.matches(PAIR_RGX)) { return LINE_MATCHES.LIST_PAIR; } - return LINE_MATCHES.LIST_VALUE; - } - /* Unordered map elements */ - if(line.matches(ELEMENT_RGX)) { return LINE_MATCHES.ELEMENT; } - if(line.matches(PAIR_RGX)) { return LINE_MATCHES.PAIR; } - /* No match */ - return LINE_MATCHES.NONE; - } - - /** - * Parses a line to return the key it contains. - * If the line does not contain a key, this function may behave unexpectedly. - * @param line The line to parse - * @return The key contained in this line - */ - protected static String parseKey(String line) { return line.stripLeading().split(":", 2)[0]; } - - /** - * Parses a line to return the number of spaces it is indented by. - * Probably not the best way to do this, but not a huge issue for now. - * @param line The line to parse - * @return How many spaces this line is indented by - */ - protected static int parseIndent(String line) { return line.length() - line.stripLeading().length(); } - - /** - * Prints a parse error to stderr. - * @param line Line to show in the message - * @param line_index Line index to show in the message - * @param explanation Explanation to show in the message - */ - protected static void printParseError(String line, int line_index, String explanation) { - System.err.println("vvv YAML - Parse error on line: vvv"); - System.err.println(line); - System.err.println("^^^ --------------------------- ^^^"); - System.err.println("(Line " + line_index + " of input.)"); - System.err.println(explanation + "\n"); /* extra newline */ - } - - /** - * @param indent Indent of this element - * @param list Whether this element is a list entry - * @return This element as a String - */ - protected abstract String toString(int indent, boolean list); -} diff --git a/src/main/java/org/lavajuno/mirrorlog/yaml/YamlList.java b/src/main/java/org/lavajuno/mirrorlog/yaml/YamlList.java deleted file mode 100644 index 8a0fe1a..0000000 --- a/src/main/java/org/lavajuno/mirrorlog/yaml/YamlList.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.lavajuno.mirrorlog.yaml; - -import java.util.InvalidPropertiesFormatException; -import java.util.Vector; -import java.util.List; - -/** - * YamlList represents an element containing an ordered list of elements. - */ -public class YamlList extends YamlElement { - protected final String KEY; - protected final Vector ELEMENTS; - /** - * Constructs a YamlMap. - * @param key This YamlMap's key - * @param lines Input we are parsing - * @param begin Where to begin parsing - * @param indent Indent of this YamlMap's elements - * @throws InvalidPropertiesFormatException If an error is encountered while parsing - */ - protected YamlList(String key, List lines, int begin, int indent) throws InvalidPropertiesFormatException { - this.KEY = key; - this.ELEMENTS = new Vector<>(); - - for(int i = begin; i < lines.size(); i++) { - String line = lines.get(i); - int line_indent = parseIndent(line); - LINE_MATCHES line_match = matchLine(line); - - if(line_indent == indent) { /* If indent matches */ - switch(line_match) { - case LIST_ELEMENT: - String curr_key = parseKey(line.split("-", 2)[1]); - ELEMENTS.add(parseElement(curr_key, lines, i, indent + 2)); - break; - case LIST_PAIR: - ELEMENTS.add(new YamlPair(line.split("-", 2)[1].stripLeading())); - break; - case LIST_VALUE: - ELEMENTS.add(new YamlValue(line.split("-", 2)[1].stripLeading())); - break; - case IGNORE: - break; - default: - /* If no match was found, we don't know what to do with the line */ - printParseError(line, i, "Unexpected element. (Not part of an ordered list)"); - throw new InvalidPropertiesFormatException("Parse error on line " + i + " of input."); - } - } else if(line_indent < indent && line_match != LINE_MATCHES.IGNORE) { - break; /* End of indented block */ - } - /* If the indent is greater, skip the line. */ - } - } - - /** - * @return This YamlList's key - */ - public String getKey() { return KEY; } - - /** - * @return This YamlList's elements - */ - public List getElements() { return ELEMENTS; } - - /** - * @param index Index of the element to get - * @return Gets the element at the specified index - */ - public YamlElement getElement(int index) { return ELEMENTS.get(index); } - - /** - * @return The number of elements in this YamlList - */ - public int getSize() { return ELEMENTS.size(); } - - protected String toString(int indent, boolean list) { - StringBuilder sb = new StringBuilder(); - sb.append(" ".repeat(indent)); - if(list) { sb.append("- "); } - sb.append(KEY).append(":\n"); - for(YamlElement i : ELEMENTS) { - sb.append(i.toString(indent + 2, true)); - } - return sb.toString(); - } - - @Override - public String toString() { return this.toString(0, false); } -} diff --git a/src/main/java/org/lavajuno/mirrorlog/yaml/YamlMap.java b/src/main/java/org/lavajuno/mirrorlog/yaml/YamlMap.java deleted file mode 100644 index afcc34f..0000000 --- a/src/main/java/org/lavajuno/mirrorlog/yaml/YamlMap.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.lavajuno.mirrorlog.yaml; - -import java.util.*; - -/** - * YamlMap represents an element containing an unordered map of elements. - */ -public class YamlMap extends YamlElement { - protected final String KEY; - protected final TreeMap ELEMENTS; - - /** - * Constructs a YamlMap by parsing the input. - * @param lines YAML to parse - * @throws InvalidPropertiesFormatException If an error is encountered while parsing - */ - public YamlMap(List lines) throws InvalidPropertiesFormatException { - this("root", lines, 0, 0); - } - - /** - * Constructs a YamlMap. - * @param key This YamlMap's key - * @param lines Input we are parsing - * @param begin Where to begin parsing - * @param indent Indent of this YamlMap's elements - * @throws InvalidPropertiesFormatException If an error is encountered while parsing - */ - protected YamlMap(String key, List lines, int begin, int indent) throws InvalidPropertiesFormatException { - this.KEY = key; - this.ELEMENTS = new TreeMap<>(); - for(int i = begin; i < lines.size(); i++) { - String line = lines.get(i); - int line_indent = parseIndent(line); - LINE_MATCHES line_match = matchLine(line); - - if(line_indent == indent) { /* If indent matches */ - String curr_key; - switch(line_match) { - case ELEMENT: - curr_key = parseKey(line); - ELEMENTS.put(curr_key, parseElement(curr_key, lines, i, indent + 2)); - break; - case PAIR: - curr_key = parseKey(line); - ELEMENTS.put(curr_key, new YamlPair(line)); - break; - case IGNORE: - break; - default: - /* If no match was found, we don't know what to do with the line */ - printParseError(line, i, "Unexpected element. (Not part of an unordered map)"); - throw new InvalidPropertiesFormatException("Parse error on line " + i + " of input."); - } - } else if(line_indent < indent && line_match != LINE_MATCHES.IGNORE) { - break; /* End of indented block */ - } - /* If the indent is greater, skip the line. */ - } - } - - /** - * @return This YamlMap's key - */ - public String getKey() { return KEY; } - - /** - * @return A key-value map of this YamlMap's elements - */ - public Map getElements() { return ELEMENTS; } - - /** - * @param key Key of the element to get - * @return Gets the element with the specified key - */ - public YamlElement getElement(String key) { return ELEMENTS.get(key); } - - protected String toString(int indent, boolean list) { - StringBuilder sb = new StringBuilder(); - sb.append(" ".repeat(indent)); - if(list) { sb.append("- "); } - sb.append(KEY).append(":\n"); - for(String i : ELEMENTS.keySet()) { - sb.append(ELEMENTS.get(i).toString(indent + 2, false)); - } - return sb.toString(); - } - - @Override - public String toString() { return this.toString(0, false); } -} diff --git a/src/main/java/org/lavajuno/mirrorlog/yaml/YamlPair.java b/src/main/java/org/lavajuno/mirrorlog/yaml/YamlPair.java deleted file mode 100644 index ca7bab8..0000000 --- a/src/main/java/org/lavajuno/mirrorlog/yaml/YamlPair.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.lavajuno.mirrorlog.yaml; - -/** - * YamlPair represents an element containing a key-value pair. - */ -public class YamlPair extends YamlElement { - protected final String KEY; - protected final YamlValue VALUE; - - /** - * Constructs a YamlValue from a line of YAML. - * We assume that we have checked the input line to make sure it actually contains - * a value, otherwise this function may behave unexpectedly. - * @param line Line of YAML to parse - */ - protected YamlPair(String line) { - KEY = parseKey(line); - VALUE = new YamlValue(line.split(":", 2)[1].stripLeading()); - } - - /** - * @return This YamlPair's value - */ - public YamlValue getValue() { return VALUE; } - - /** - * @return This YamlPair's key - */ - public String getKey() { return KEY; } - - protected String toString(int indent, boolean list) { - StringBuilder sb = new StringBuilder(); - sb.append(" ".repeat(indent)); - if(list) { sb.append("- "); } - sb.append(this.KEY).append(": ").append(this.VALUE.toString()).append("\n"); - return sb.toString(); - } - - @Override - public String toString() { return this.toString(0, false); } -} diff --git a/src/main/java/org/lavajuno/mirrorlog/yaml/YamlValue.java b/src/main/java/org/lavajuno/mirrorlog/yaml/YamlValue.java deleted file mode 100644 index cbab479..0000000 --- a/src/main/java/org/lavajuno/mirrorlog/yaml/YamlValue.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.lavajuno.mirrorlog.yaml; - -/** - * YamlValue represents a string or numeric value. - */ -public class YamlValue extends YamlElement { - private final String STR_VALUE; - - /** - * Constructs a YamlValue from a string. - * @param raw String value of this YamlValue - */ - protected YamlValue(String raw) { - if(raw.matches("^\".*\"$")) { - STR_VALUE = raw.substring(1, raw.length() - 1); - } else { - STR_VALUE = raw; - } - } - - /** - * @return Integer value of this YamlValue - * @throws NumberFormatException If this YamlValue cannot be converted to an Integer - */ - public int toInt() throws NumberFormatException { return Integer.parseInt(STR_VALUE); } - - /** - * @return Long value of this YamlValue - * @throws NumberFormatException If this YamlValue cannot be converted to a Long - */ - public long toLong() throws NumberFormatException { return Long.parseLong(STR_VALUE); } - - /** - * @return Float value of this YamlValue - * @throws NumberFormatException If this YamlValue cannot be converted to a Float - */ - public float toFloat() throws NumberFormatException { return Float.parseFloat(STR_VALUE); } - - /** - * @return Double value of this YamlValue - * @throws NumberFormatException If this YamlValue cannot be converted to a Double - */ - public double toDouble() throws NumberFormatException { return Double.parseDouble(STR_VALUE); } - - protected String toString(int indent, boolean list) { - if(!list) { return STR_VALUE; } - StringBuilder sb = new StringBuilder(); - sb.append(" ".repeat(indent)).append("- "); - sb.append(STR_VALUE).append("\n"); - return sb.toString(); - } - - @Override - public String toString() { return this.toString(0, false); } -} diff --git a/src/test/java/org/lavajuno/mirrorlog/TestYaml.java b/src/test/java/org/lavajuno/mirrorlog/TestYaml.java deleted file mode 100644 index 8ae1d2a..0000000 --- a/src/test/java/org/lavajuno/mirrorlog/TestYaml.java +++ /dev/null @@ -1,188 +0,0 @@ -package org.lavajuno.mirrorlog; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.lavajuno.mirrorlog.yaml.*; - -import java.io.IOException; -import java.util.InvalidPropertiesFormatException; -import java.util.Vector; - -public class TestYaml { - /** - * Tests basic YAML comprehension. - * Instantiates an element with a few child elements of different types. - * @throws IOException Passes on IOExceptions from the YAML class - */ - @Test - public void testBasicYaml() throws IOException { - Vector test_lines = new Vector<>(); - test_lines.add("MapElement1:"); - test_lines.add(" PairElement1: ValueElement1"); - test_lines.add(" ListElement1:"); - test_lines.add(" - ListElement2:"); - test_lines.add(" - ValueElement2"); - test_lines.add(" - PairElement2: ValueElement3"); - - YamlMap root = new YamlMap(test_lines); - System.out.println(root.toString()); - - YamlMap map_element_1 = (YamlMap) root.getElement("MapElement1"); - YamlPair pair_element_1 = (YamlPair) map_element_1.getElement("PairElement1"); - YamlValue value_element_1 = pair_element_1.getValue(); - YamlList list_element_1 = (YamlList) map_element_1.getElement("ListElement1"); - YamlList list_element_2 = (YamlList) list_element_1.getElement(0); - YamlValue value_element_2 = (YamlValue) list_element_2.getElement(0); - YamlPair pair_element_2 = (YamlPair) list_element_1.getElement(1); - YamlValue value_element_3 = pair_element_2.getValue(); - - Assertions.assertEquals("MapElement1", map_element_1.getKey()); - Assertions.assertEquals("PairElement1", pair_element_1.getKey()); - Assertions.assertEquals("ValueElement1", value_element_1.toString()); - Assertions.assertEquals("ListElement1", list_element_1.getKey()); - Assertions.assertEquals("ListElement2", list_element_2.getKey()); - Assertions.assertEquals("ValueElement2", value_element_2.toString()); - Assertions.assertEquals("PairElement2", pair_element_2.getKey()); - Assertions.assertEquals("ValueElement3", value_element_3.toString()); - } - - /** - * Tests response to bad indentation. - * @throws IOException Passes on IOExceptions from the YAML class - */ - @Test - public void testBadIndentation() throws IOException { - Vector test_lines = new Vector<>(); - test_lines.add("Element1:"); - test_lines.add(" Element2:"); - test_lines.add(" Element3:"); - - YamlMap root = new YamlMap(test_lines); - YamlMap element_1 = (YamlMap) root.getElement("Element1"); - Assertions.assertNotNull(element_1); - Assertions.assertEquals(element_1.getElements().size(), 0); - } - - /** - * Tests response to messy indentation. - * @throws IOException Passes on IOExceptions from the YAML class - */ - @Test - public void testMessyIndentation() throws IOException { - Vector test_lines = new Vector<>(); - test_lines.add("Element1:"); - test_lines.add(" Element2:"); - test_lines.add(" Element3:"); - test_lines.add(" Element4: Value5"); - - YamlMap root = new YamlMap(test_lines); - YamlMap element_1 = (YamlMap) root.getElement("Element1"); - Assertions.assertNotNull(element_1); - Assertions.assertEquals(element_1.getElements().size(), 1); - YamlMap element_3 = (YamlMap) element_1.getElement("Element3"); - Assertions.assertNotNull(element_3); - } - - /** - * Tests response to bad keys. - * @throws IOException Passes on IOExceptions from the YAML class - */ - @Test - public void testBadKeys() throws IOException { - Vector test_lines = new Vector<>(); - - test_lines.add("Element1:"); - test_lines.add(" not valid"); - try { - new YamlMap(test_lines); - throw new IOException("Accepted bad key."); - } catch(InvalidPropertiesFormatException ignored) {} - test_lines.clear(); - - test_lines.add("Element1: "); - test_lines.add(" :"); - try { - new YamlMap(test_lines); - throw new IOException("Accepted bad key."); - } catch(InvalidPropertiesFormatException ignored) {} - test_lines.clear(); - - test_lines.add("Element1 : "); - test_lines.add(" Element2"); - try { - new YamlMap(test_lines); - throw new IOException("Accepted bad key."); - } catch(InvalidPropertiesFormatException ignored) {} - } - - /** - * Tests response to bad lists. - * @throws IOException Passes on IOExceptions from the YAML class - */ - @Test - public void testBadLists() throws IOException { - Vector test_lines = new Vector<>(); - - test_lines.add("Element1:"); - test_lines.add(" - "); - try { - new YamlMap(test_lines); - throw new IOException("Accepted bad list."); - } catch(InvalidPropertiesFormatException ignored) {} - test_lines.clear(); - - test_lines.add("Element1: "); - test_lines.add(" -bad"); - try { - new YamlMap(test_lines); - throw new IOException("Accepted bad list."); - } catch(InvalidPropertiesFormatException ignored) {} - } - - /** - * Tests response to bad strings. - * @throws IOException Passes on IOExceptions from the YAML class - */ - @Test - public void testBadStrings() throws IOException { - Vector test_lines = new Vector<>(); - test_lines.add("Element1:bad"); - try { - new YamlMap(test_lines); - throw new IOException("YamlMap accepted bad string."); - } catch(InvalidPropertiesFormatException ignored) {} - - test_lines.clear(); - test_lines.add("-element2bad"); - try { - new YamlMap(test_lines); - throw new IOException("YamlMap accepted bad string."); - } catch(InvalidPropertiesFormatException ignored) {} - } - - /** - * Tests a more complex YAML structure. - * Instantiates an element with a few child elements of different types. - * @throws IOException Passes on IOExceptions from the YAML class - */ - @Test - public void testComplexYaml() throws IOException { - Vector test_lines = new Vector<>(); - test_lines.add("config:"); - test_lines.add(" version: 1.0"); - test_lines.add(" server: "); - test_lines.add(" port: 1234"); - test_lines.add(" firewall: 0"); - test_lines.add(" allowed_ips:"); - test_lines.add(" - 10.0.0.1"); - test_lines.add(" - \"10.0.0.2\""); - test_lines.add(" container:"); - test_lines.add(" provider: screen"); - test_lines.add(" user: server"); - test_lines.add(" args:"); - test_lines.add(" - arg1"); - - YamlMap root = new YamlMap(test_lines); - System.out.println(root); - } -}