| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| package dev.plex.toml; | ||
|
|
||
| import java.util.concurrent.atomic.AtomicInteger; | ||
|
|
||
| class NumberValueReaderWriter implements dev.plex.toml.ValueReader, dev.plex.toml.ValueWriter | ||
| { | ||
| static final NumberValueReaderWriter NUMBER_VALUE_READER_WRITER = new NumberValueReaderWriter(); | ||
|
|
||
| @Override | ||
| public boolean canRead(String s) | ||
| { | ||
| char firstChar = s.charAt(0); | ||
|
|
||
| return firstChar == '+' || firstChar == '-' || Character.isDigit(firstChar); | ||
| } | ||
|
|
||
| @Override | ||
| public Object read(String s, AtomicInteger index, dev.plex.toml.Context context) | ||
| { | ||
| boolean signable = true; | ||
| boolean dottable = false; | ||
| boolean exponentable = false; | ||
| boolean terminatable = false; | ||
| boolean underscorable = false; | ||
| String type = ""; | ||
| StringBuilder sb = new StringBuilder(); | ||
|
|
||
| for (int i = index.get(); i < s.length(); i = index.incrementAndGet()) | ||
| { | ||
| char c = s.charAt(i); | ||
| boolean notLastChar = s.length() > i + 1; | ||
|
|
||
| if (Character.isDigit(c)) | ||
| { | ||
| sb.append(c); | ||
| signable = false; | ||
| terminatable = true; | ||
| if (type.isEmpty()) | ||
| { | ||
| type = "integer"; | ||
| dottable = true; | ||
| } | ||
| underscorable = notLastChar; | ||
| exponentable = !type.equals("exponent"); | ||
| } | ||
| else if ((c == '+' || c == '-') && signable && notLastChar) | ||
| { | ||
| signable = false; | ||
| terminatable = false; | ||
| if (c == '-') | ||
| { | ||
| sb.append('-'); | ||
| } | ||
| } | ||
| else if (c == '.' && dottable && notLastChar) | ||
| { | ||
| sb.append('.'); | ||
| type = "float"; | ||
| terminatable = false; | ||
| dottable = false; | ||
| exponentable = false; | ||
| underscorable = false; | ||
| } | ||
| else if ((c == 'E' || c == 'e') && exponentable && notLastChar) | ||
| { | ||
| sb.append('E'); | ||
| type = "exponent"; | ||
| terminatable = false; | ||
| signable = true; | ||
| dottable = false; | ||
| exponentable = false; | ||
| underscorable = false; | ||
| } | ||
| else if (c == '_' && underscorable && notLastChar && Character.isDigit(s.charAt(i + 1))) | ||
| { | ||
| underscorable = false; | ||
| } | ||
| else | ||
| { | ||
| if (!terminatable) | ||
| { | ||
| type = ""; | ||
| } | ||
| index.decrementAndGet(); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (type.equals("integer")) | ||
| { | ||
| return Long.valueOf(sb.toString()); | ||
| } | ||
| else if (type.equals("float")) | ||
| { | ||
| return Double.valueOf(sb.toString()); | ||
| } | ||
| else if (type.equals("exponent")) | ||
| { | ||
| String[] exponentString = sb.toString().split("E"); | ||
|
|
||
| return Double.parseDouble(exponentString[0]) * Math.pow(10, Double.parseDouble(exponentString[1])); | ||
| } | ||
| else | ||
| { | ||
| dev.plex.toml.Results.Errors errors = new dev.plex.toml.Results.Errors(); | ||
| errors.invalidValue(context.identifier.getName(), sb.toString(), context.line.get()); | ||
| return errors; | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public boolean canWrite(Object value) | ||
| { | ||
| return value instanceof Number; | ||
| } | ||
|
|
||
| @Override | ||
| public void write(Object value, dev.plex.toml.WriterContext context) | ||
| { | ||
| context.write(value.toString()); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isPrimitiveType() | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() | ||
| { | ||
| return "number"; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| package dev.plex.toml; | ||
|
|
||
| import com.google.gson.annotations.SerializedName; | ||
| import java.lang.reflect.Field; | ||
| import java.lang.reflect.Modifier; | ||
| import java.util.Arrays; | ||
| import java.util.Iterator; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.LinkedHashSet; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
|
|
||
| class ObjectValueWriter implements ValueWriter | ||
| { | ||
| static final ValueWriter OBJECT_VALUE_WRITER = new ObjectValueWriter(); | ||
|
|
||
| @Override | ||
| public boolean canWrite(Object value) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public void write(Object value, WriterContext context) | ||
| { | ||
| Map<String, Object> to = new LinkedHashMap<String, Object>(); | ||
| Set<Field> fields = getFields(value.getClass()); | ||
| for (Field field : fields) | ||
| { | ||
| if (field.isAnnotationPresent(SerializedName.class)) | ||
| { | ||
| to.put(field.getDeclaredAnnotation(SerializedName.class).value(), getFieldValue(field, value)); | ||
| } | ||
| else | ||
| { | ||
| to.put(field.getName(), getFieldValue(field, value)); | ||
| } | ||
| } | ||
|
|
||
| MapValueWriter.MAP_VALUE_WRITER.write(to, context); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isPrimitiveType() | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| private static Set<Field> getFields(Class<?> cls) | ||
| { | ||
| Set<Field> fields = new LinkedHashSet<Field>(Arrays.asList(cls.getDeclaredFields())); | ||
| while (cls != Object.class) | ||
| { | ||
| fields.addAll(Arrays.asList(cls.getDeclaredFields())); | ||
| cls = cls.getSuperclass(); | ||
| } | ||
| removeConstantsAndSyntheticFields(fields); | ||
|
|
||
| return fields; | ||
| } | ||
|
|
||
| private static void removeConstantsAndSyntheticFields(Set<Field> fields) | ||
| { | ||
| Iterator<Field> iterator = fields.iterator(); | ||
| while (iterator.hasNext()) | ||
| { | ||
| Field field = iterator.next(); | ||
| if ((Modifier.isFinal(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) || field.isSynthetic() || Modifier.isTransient(field.getModifiers())) | ||
| { | ||
| iterator.remove(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private static Object getFieldValue(Field field, Object o) | ||
| { | ||
| boolean isAccessible = field.isAccessible(); | ||
| field.setAccessible(true); | ||
| Object value = null; | ||
| try | ||
| { | ||
| value = field.get(o); | ||
| } | ||
| catch (IllegalAccessException ignored) | ||
| { | ||
| } | ||
| field.setAccessible(isAccessible); | ||
|
|
||
| return value; | ||
| } | ||
|
|
||
| private ObjectValueWriter() | ||
| { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| package dev.plex.toml; | ||
|
|
||
| import java.util.Collection; | ||
|
|
||
| class PrimitiveArrayValueWriter extends ArrayValueWriter | ||
| { | ||
| static final ValueWriter PRIMITIVE_ARRAY_VALUE_WRITER = new PrimitiveArrayValueWriter(); | ||
|
|
||
| @Override | ||
| public boolean canWrite(Object value) | ||
| { | ||
| return isArrayish(value) && isArrayOfPrimitive(value); | ||
| } | ||
|
|
||
| @Override | ||
| public void write(Object o, WriterContext context) | ||
| { | ||
| Collection<?> values = normalize(o); | ||
|
|
||
| context.write('['); | ||
| context.writeArrayDelimiterPadding(); | ||
|
|
||
| boolean first = true; | ||
| ValueWriter firstWriter = null; | ||
|
|
||
| for (Object value : values) | ||
| { | ||
| if (first) | ||
| { | ||
| firstWriter = ValueWriters.WRITERS.findWriterFor(value); | ||
| first = false; | ||
| } | ||
| else | ||
| { | ||
| ValueWriter writer = ValueWriters.WRITERS.findWriterFor(value); | ||
| if (writer != firstWriter) | ||
| { | ||
| throw new IllegalStateException( | ||
| context.getContextPath() + | ||
| ": cannot write a heterogeneous array; first element was of type " + firstWriter + | ||
| " but found " + writer | ||
| ); | ||
| } | ||
| context.write(", "); | ||
| } | ||
|
|
||
| ValueWriters.WRITERS.findWriterFor(value).write(value, context); | ||
| } | ||
|
|
||
| context.writeArrayDelimiterPadding(); | ||
| context.write(']'); | ||
| } | ||
|
|
||
| private PrimitiveArrayValueWriter() | ||
| { | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() | ||
| { | ||
| return "primitive-array"; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,361 @@ | ||
| package dev.plex.toml; | ||
|
|
||
| import java.util.ArrayDeque; | ||
| import java.util.Deque; | ||
| import java.util.HashSet; | ||
| import java.util.Iterator; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
|
|
||
| class Results | ||
| { | ||
|
|
||
| static class Errors | ||
| { | ||
|
|
||
| private final StringBuilder sb = new StringBuilder(); | ||
|
|
||
| void duplicateTable(String table, int line) | ||
| { | ||
| sb.append("Duplicate table definition on line ") | ||
| .append(line) | ||
| .append(": [") | ||
| .append(table) | ||
| .append("]"); | ||
| } | ||
|
|
||
| public void tableDuplicatesKey(String table, AtomicInteger line) | ||
| { | ||
| sb.append("Key already exists for table defined on line ") | ||
| .append(line.get()) | ||
| .append(": [") | ||
| .append(table) | ||
| .append("]"); | ||
| } | ||
|
|
||
| public void keyDuplicatesTable(String key, AtomicInteger line) | ||
| { | ||
| sb.append("Table already exists for key defined on line ") | ||
| .append(line.get()) | ||
| .append(": ") | ||
| .append(key); | ||
| } | ||
|
|
||
| void emptyImplicitTable(String table, int line) | ||
| { | ||
| sb.append("Invalid table definition due to empty implicit table name: ") | ||
| .append(table); | ||
| } | ||
|
|
||
| void invalidTable(String table, int line) | ||
| { | ||
| sb.append("Invalid table definition on line ") | ||
| .append(line) | ||
| .append(": ") | ||
| .append(table) | ||
| .append("]"); | ||
| } | ||
|
|
||
| void duplicateKey(String key, int line) | ||
| { | ||
| sb.append("Duplicate key"); | ||
| if (line > -1) | ||
| { | ||
| sb.append(" on line ") | ||
| .append(line); | ||
| } | ||
| sb.append(": ") | ||
| .append(key); | ||
| } | ||
|
|
||
| void invalidTextAfterIdentifier(dev.plex.toml.Identifier identifier, char text, int line) | ||
| { | ||
| sb.append("Invalid text after key ") | ||
| .append(identifier.getName()) | ||
| .append(" on line ") | ||
| .append(line) | ||
| .append(". Make sure to terminate the value or add a comment (#)."); | ||
| } | ||
|
|
||
| void invalidKey(String key, int line) | ||
| { | ||
| sb.append("Invalid key on line ") | ||
| .append(line) | ||
| .append(": ") | ||
| .append(key); | ||
| } | ||
|
|
||
| void invalidTableArray(String tableArray, int line) | ||
| { | ||
| sb.append("Invalid table array definition on line ") | ||
| .append(line) | ||
| .append(": ") | ||
| .append(tableArray); | ||
| } | ||
|
|
||
| void invalidValue(String key, String value, int line) | ||
| { | ||
| sb.append("Invalid value on line ") | ||
| .append(line) | ||
| .append(": ") | ||
| .append(key) | ||
| .append(" = ") | ||
| .append(value); | ||
| } | ||
|
|
||
| void unterminatedKey(String key, int line) | ||
| { | ||
| sb.append("Key is not followed by an equals sign on line ") | ||
| .append(line) | ||
| .append(": ") | ||
| .append(key); | ||
| } | ||
|
|
||
| void unterminated(String key, String value, int line) | ||
| { | ||
| sb.append("Unterminated value on line ") | ||
| .append(line) | ||
| .append(": ") | ||
| .append(key) | ||
| .append(" = ") | ||
| .append(value.trim()); | ||
| } | ||
|
|
||
| public void heterogenous(String key, int line) | ||
| { | ||
| sb.append(key) | ||
| .append(" becomes a heterogeneous array on line ") | ||
| .append(line); | ||
| } | ||
|
|
||
| boolean hasErrors() | ||
| { | ||
| return sb.length() > 0; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() | ||
| { | ||
| return sb.toString(); | ||
| } | ||
|
|
||
| public void add(Errors other) | ||
| { | ||
| sb.append(other.sb); | ||
| } | ||
| } | ||
|
|
||
| final Errors errors = new Errors(); | ||
| private final Set<String> tables = new HashSet<String>(); | ||
| private final Deque<Container> stack = new ArrayDeque<Container>(); | ||
|
|
||
| Results() | ||
| { | ||
| stack.push(new Container.Table("")); | ||
| } | ||
|
|
||
| void addValue(String key, Object value, AtomicInteger line) | ||
| { | ||
| Container currentTable = stack.peek(); | ||
|
|
||
| if (value instanceof Map) | ||
| { | ||
| String path = getInlineTablePath(key); | ||
| if (path == null) | ||
| { | ||
| startTable(key, line); | ||
| } | ||
| else if (path.isEmpty()) | ||
| { | ||
| startTables(dev.plex.toml.Identifier.from(key, null), line); | ||
| } | ||
| else | ||
| { | ||
| startTables(dev.plex.toml.Identifier.from(path, null), line); | ||
| } | ||
| @SuppressWarnings("unchecked") | ||
| Map<String, Object> valueMap = (Map<String, Object>) value; | ||
| for (Map.Entry<String, Object> entry : valueMap.entrySet()) | ||
| { | ||
| addValue(entry.getKey(), entry.getValue(), line); | ||
| } | ||
| stack.pop(); | ||
| } | ||
| else if (currentTable.accepts(key)) | ||
| { | ||
| currentTable.put(key, value); | ||
| } | ||
| else | ||
| { | ||
| if (currentTable.get(key) instanceof Container) | ||
| { | ||
| errors.keyDuplicatesTable(key, line); | ||
| } | ||
| else | ||
| { | ||
| errors.duplicateKey(key, line != null ? line.get() : -1); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void startTableArray(dev.plex.toml.Identifier identifier, AtomicInteger line) | ||
| { | ||
| String tableName = identifier.getBareName(); | ||
| while (stack.size() > 1) | ||
| { | ||
| stack.pop(); | ||
| } | ||
|
|
||
| dev.plex.toml.Keys.Key[] tableParts = dev.plex.toml.Keys.split(tableName); | ||
| for (int i = 0; i < tableParts.length; i++) | ||
| { | ||
| String tablePart = tableParts[i].name; | ||
| Container currentContainer = stack.peek(); | ||
|
|
||
| if (currentContainer.get(tablePart) instanceof Container.TableArray) | ||
| { | ||
| Container.TableArray currentTableArray = (Container.TableArray) currentContainer.get(tablePart); | ||
| stack.push(currentTableArray); | ||
|
|
||
| if (i == tableParts.length - 1) | ||
| { | ||
| currentTableArray.put(tablePart, new Container.Table()); | ||
| } | ||
|
|
||
| stack.push(currentTableArray.getCurrent()); | ||
| currentContainer = stack.peek(); | ||
| } | ||
| else if (currentContainer.get(tablePart) instanceof Container.Table && i < tableParts.length - 1) | ||
| { | ||
| Container nextTable = (Container) currentContainer.get(tablePart); | ||
| stack.push(nextTable); | ||
| } | ||
| else if (currentContainer.accepts(tablePart)) | ||
| { | ||
| Container newContainer = i == tableParts.length - 1 ? new Container.TableArray() : new Container.Table(); | ||
| addValue(tablePart, newContainer, line); | ||
| stack.push(newContainer); | ||
|
|
||
| if (newContainer instanceof Container.TableArray) | ||
| { | ||
| stack.push(((Container.TableArray) newContainer).getCurrent()); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| errors.duplicateTable(tableName, line.get()); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void startTables(dev.plex.toml.Identifier id, AtomicInteger line) | ||
| { | ||
| String tableName = id.getBareName(); | ||
|
|
||
| while (stack.size() > 1) | ||
| { | ||
| stack.pop(); | ||
| } | ||
|
|
||
| dev.plex.toml.Keys.Key[] tableParts = dev.plex.toml.Keys.split(tableName); | ||
| for (int i = 0; i < tableParts.length; i++) | ||
| { | ||
| String tablePart = tableParts[i].name; | ||
| Container currentContainer = stack.peek(); | ||
| if (currentContainer.get(tablePart) instanceof Container) | ||
| { | ||
| Container nextTable = (Container) currentContainer.get(tablePart); | ||
| if (i == tableParts.length - 1 && !nextTable.isImplicit()) | ||
| { | ||
| errors.duplicateTable(tableName, line.get()); | ||
| return; | ||
| } | ||
| stack.push(nextTable); | ||
| if (stack.peek() instanceof Container.TableArray) | ||
| { | ||
| stack.push(((Container.TableArray) stack.peek()).getCurrent()); | ||
| } | ||
| } | ||
| else if (currentContainer.accepts(tablePart)) | ||
| { | ||
| startTable(tablePart, i < tableParts.length - 1, line); | ||
| } | ||
| else | ||
| { | ||
| errors.tableDuplicatesKey(tablePart, line); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Warning: After this method has been called, this instance is no longer usable. | ||
| */ | ||
| Map<String, Object> consume() | ||
| { | ||
| Container values = stack.getLast(); | ||
| stack.clear(); | ||
|
|
||
| return ((Container.Table) values).consume(); | ||
| } | ||
|
|
||
| private Container startTable(String tableName, AtomicInteger line) | ||
| { | ||
| Container newTable = new Container.Table(tableName); | ||
| addValue(tableName, newTable, line); | ||
| stack.push(newTable); | ||
|
|
||
| return newTable; | ||
| } | ||
|
|
||
| private Container startTable(String tableName, boolean implicit, AtomicInteger line) | ||
| { | ||
| Container newTable = new Container.Table(tableName, implicit); | ||
| addValue(tableName, newTable, line); | ||
| stack.push(newTable); | ||
|
|
||
| return newTable; | ||
| } | ||
|
|
||
| private String getInlineTablePath(String key) | ||
| { | ||
| Iterator<Container> descendingIterator = stack.descendingIterator(); | ||
| StringBuilder sb = new StringBuilder(); | ||
|
|
||
| while (descendingIterator.hasNext()) | ||
| { | ||
| Container next = descendingIterator.next(); | ||
| if (next instanceof Container.TableArray) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| Container.Table table = (Container.Table) next; | ||
|
|
||
| if (table.name == null) | ||
| { | ||
| break; | ||
| } | ||
|
|
||
| if (sb.length() > 0) | ||
| { | ||
| sb.append('.'); | ||
| } | ||
|
|
||
| sb.append(table.name); | ||
| } | ||
|
|
||
| if (sb.length() > 0) | ||
| { | ||
| sb.append('.'); | ||
| } | ||
|
|
||
| sb.append(key) | ||
| .insert(0, '[') | ||
| .append(']'); | ||
|
|
||
| return sb.toString(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| package dev.plex.toml; | ||
|
|
||
| import java.net.URI; | ||
| import java.net.URL; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
| import java.util.regex.Matcher; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| class StringValueReaderWriter implements ValueReader, ValueWriter | ||
| { | ||
|
|
||
| static final StringValueReaderWriter STRING_VALUE_READER_WRITER = new StringValueReaderWriter(); | ||
| private static final Pattern UNICODE_REGEX = Pattern.compile("\\\\[uU](.{4})"); | ||
|
|
||
| static private final String[] specialCharacterEscapes = new String[93]; | ||
|
|
||
| static | ||
| { | ||
| specialCharacterEscapes['\b'] = "\\b"; | ||
| specialCharacterEscapes['\t'] = "\\t"; | ||
| specialCharacterEscapes['\n'] = "\\n"; | ||
| specialCharacterEscapes['\f'] = "\\f"; | ||
| specialCharacterEscapes['\r'] = "\\r"; | ||
| specialCharacterEscapes['"'] = "\\\""; | ||
| specialCharacterEscapes['\\'] = "\\\\"; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean canRead(String s) | ||
| { | ||
| return s.startsWith("\""); | ||
| } | ||
|
|
||
| @Override | ||
| public Object read(String s, AtomicInteger index, Context context) | ||
| { | ||
| int startIndex = index.incrementAndGet(); | ||
| int endIndex = -1; | ||
|
|
||
| for (int i = index.get(); i < s.length(); i = index.incrementAndGet()) | ||
| { | ||
| char ch = s.charAt(i); | ||
| if (ch == '"' && s.charAt(i - 1) != '\\') | ||
| { | ||
| endIndex = i; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (endIndex == -1) | ||
| { | ||
| Results.Errors errors = new Results.Errors(); | ||
| errors.unterminated(context.identifier.getName(), s.substring(startIndex - 1), context.line.get()); | ||
| return errors; | ||
| } | ||
|
|
||
| String raw = s.substring(startIndex, endIndex); | ||
| s = replaceUnicodeCharacters(raw); | ||
| s = replaceSpecialCharacters(s); | ||
|
|
||
| if (s == null) | ||
| { | ||
| Results.Errors errors = new Results.Errors(); | ||
| errors.invalidValue(context.identifier.getName(), raw, context.line.get()); | ||
| return errors; | ||
| } | ||
|
|
||
| return s; | ||
| } | ||
|
|
||
| String replaceUnicodeCharacters(String value) | ||
| { | ||
| Matcher unicodeMatcher = UNICODE_REGEX.matcher(value); | ||
|
|
||
| while (unicodeMatcher.find()) | ||
| { | ||
| value = value.replace(unicodeMatcher.group(), new String(Character.toChars(Integer.parseInt(unicodeMatcher.group(1), 16)))); | ||
| } | ||
| return value; | ||
| } | ||
|
|
||
| String replaceSpecialCharacters(String s) | ||
| { | ||
| for (int i = 0; i < s.length() - 1; i++) | ||
| { | ||
| char ch = s.charAt(i); | ||
| char next = s.charAt(i + 1); | ||
|
|
||
| if (ch == '\\' && next == '\\') | ||
| { | ||
| i++; | ||
| } | ||
| else if (ch == '\\' && !(next == 'b' || next == 'f' || next == 'n' || next == 't' || next == 'r' || next == '"' || next == '\\')) | ||
| { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| return s.replace("\\n", "\n") | ||
| .replace("\\\"", "\"") | ||
| .replace("\\t", "\t") | ||
| .replace("\\r", "\r") | ||
| .replace("\\\\", "\\") | ||
| .replace("\\/", "/") | ||
| .replace("\\b", "\b") | ||
| .replace("\\f", "\f"); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean canWrite(Object value) | ||
| { | ||
| return value instanceof String || value instanceof Character || value instanceof URL || value instanceof URI || value instanceof Enum; | ||
| } | ||
|
|
||
| @Override | ||
| public void write(Object value, WriterContext context) | ||
| { | ||
| context.write('"'); | ||
| escapeUnicode(value.toString(), context); | ||
| context.write('"'); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isPrimitiveType() | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| private void escapeUnicode(String in, WriterContext context) | ||
| { | ||
| for (int i = 0; i < in.length(); i++) | ||
| { | ||
| int codePoint = in.codePointAt(i); | ||
| if (codePoint < specialCharacterEscapes.length && specialCharacterEscapes[codePoint] != null) | ||
| { | ||
| context.write(specialCharacterEscapes[codePoint]); | ||
| } | ||
| else | ||
| { | ||
| context.write(in.charAt(i)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private StringValueReaderWriter() | ||
| { | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() | ||
| { | ||
| return "string"; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package dev.plex.toml; | ||
|
|
||
| import java.util.Collection; | ||
| import static dev.plex.toml.ValueWriters.WRITERS; | ||
|
|
||
| class TableArrayValueWriter extends ArrayValueWriter | ||
| { | ||
| static final ValueWriter TABLE_ARRAY_VALUE_WRITER = new TableArrayValueWriter(); | ||
|
|
||
| @Override | ||
| public boolean canWrite(Object value) | ||
| { | ||
| return isArrayish(value) && !isArrayOfPrimitive(value); | ||
| } | ||
|
|
||
| @Override | ||
| public void write(Object from, WriterContext context) | ||
| { | ||
| Collection<?> values = normalize(from); | ||
|
|
||
| WriterContext subContext = context.pushTableFromArray(); | ||
|
|
||
| for (Object value : values) | ||
| { | ||
| WRITERS.findWriterFor(value).write(value, subContext); | ||
| } | ||
| } | ||
|
|
||
| private TableArrayValueWriter() | ||
| { | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() | ||
| { | ||
| return "table-array"; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| package dev.plex.toml; | ||
|
|
||
| import java.util.concurrent.atomic.AtomicInteger; | ||
|
|
||
| class TomlParser | ||
| { | ||
|
|
||
| static dev.plex.toml.Results run(String tomlString) | ||
| { | ||
| final dev.plex.toml.Results results = new dev.plex.toml.Results(); | ||
|
|
||
| if (tomlString.isEmpty()) | ||
| { | ||
| return results; | ||
| } | ||
|
|
||
| AtomicInteger index = new AtomicInteger(); | ||
| boolean inComment = false; | ||
| AtomicInteger line = new AtomicInteger(1); | ||
| dev.plex.toml.Identifier identifier = null; | ||
| Object value = null; | ||
|
|
||
| for (int i = index.get(); i < tomlString.length(); i = index.incrementAndGet()) | ||
| { | ||
| char c = tomlString.charAt(i); | ||
|
|
||
| if (results.errors.hasErrors()) | ||
| { | ||
| break; | ||
| } | ||
|
|
||
| if (c == '#' && !inComment) | ||
| { | ||
| inComment = true; | ||
| } | ||
| else if (!Character.isWhitespace(c) && !inComment && identifier == null) | ||
| { | ||
| dev.plex.toml.Identifier id = dev.plex.toml.IdentifierConverter.IDENTIFIER_CONVERTER.convert(tomlString, index, new dev.plex.toml.Context(null, line, results.errors)); | ||
|
|
||
| if (id != dev.plex.toml.Identifier.INVALID) | ||
| { | ||
| if (id.isKey()) | ||
| { | ||
| identifier = id; | ||
| } | ||
| else if (id.isTable()) | ||
| { | ||
| results.startTables(id, line); | ||
| } | ||
| else if (id.isTableArray()) | ||
| { | ||
| results.startTableArray(id, line); | ||
| } | ||
| } | ||
| } | ||
| else if (c == '\n') | ||
| { | ||
| inComment = false; | ||
| identifier = null; | ||
| value = null; | ||
| line.incrementAndGet(); | ||
| } | ||
| else if (!inComment && identifier != null && identifier.isKey() && value == null && !Character.isWhitespace(c)) | ||
| { | ||
| value = ValueReaders.VALUE_READERS.convert(tomlString, index, new dev.plex.toml.Context(identifier, line, results.errors)); | ||
|
|
||
| if (value instanceof dev.plex.toml.Results.Errors) | ||
| { | ||
| results.errors.add((dev.plex.toml.Results.Errors) value); | ||
| } | ||
| else | ||
| { | ||
| results.addValue(identifier.getName(), value, line); | ||
| } | ||
| } | ||
| else if (value != null && !inComment && !Character.isWhitespace(c)) | ||
| { | ||
| results.errors.invalidTextAfterIdentifier(identifier, c, line.get()); | ||
| } | ||
| } | ||
|
|
||
| return results; | ||
| } | ||
|
|
||
| private TomlParser() | ||
| { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| package dev.plex.toml; | ||
|
|
||
| import java.io.File; | ||
| import java.io.FileOutputStream; | ||
| import java.io.IOException; | ||
| import java.io.OutputStream; | ||
| import java.io.OutputStreamWriter; | ||
| import java.io.StringWriter; | ||
| import java.io.Writer; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.TimeZone; | ||
| import org.jetbrains.annotations.Nullable; | ||
| import static dev.plex.toml.ValueWriters.WRITERS; | ||
|
|
||
| /** | ||
| * <p>Converts Objects to TOML</p> | ||
| * | ||
| * <p>An input Object can comprise arbitrarily nested combinations of Java primitive types, | ||
| * other {@link Object}s, {@link Map}s, {@link List}s, and Arrays. {@link Object}s and {@link Map}s | ||
| * are output to TOML tables, and {@link List}s and Array to TOML arrays.</p> | ||
| * | ||
| * <p>Example usage:</p> | ||
| * <pre><code> | ||
| * class AClass { | ||
| * int anInt = 1; | ||
| * int[] anArray = { 2, 3 }; | ||
| * } | ||
| * | ||
| * String tomlString = new TomlWriter().write(new AClass()); | ||
| * </code></pre> | ||
| */ | ||
| public class TomlWriter | ||
| { | ||
|
|
||
| public static class Builder | ||
| { | ||
| private int keyIndentation; | ||
| private int tableIndentation; | ||
| private int arrayDelimiterPadding = 0; | ||
| private TimeZone timeZone = TimeZone.getTimeZone("UTC"); | ||
| private boolean showFractionalSeconds = false; | ||
|
|
||
| public Builder indentValuesBy(int spaces) | ||
| { | ||
| this.keyIndentation = spaces; | ||
|
|
||
| return this; | ||
| } | ||
|
|
||
| public Builder indentTablesBy(int spaces) | ||
| { | ||
| this.tableIndentation = spaces; | ||
|
|
||
| return this; | ||
| } | ||
|
|
||
| public Builder timeZone(TimeZone timeZone) | ||
| { | ||
| this.timeZone = timeZone; | ||
|
|
||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * @param spaces number of spaces to put between opening square bracket and first item and between closing square bracket and last item | ||
| * @return this TomlWriter.Builder instance | ||
| */ | ||
| public Builder padArrayDelimitersBy(int spaces) | ||
| { | ||
| this.arrayDelimiterPadding = spaces; | ||
|
|
||
| return this; | ||
| } | ||
|
|
||
| public TomlWriter build() | ||
| { | ||
| return new TomlWriter(keyIndentation, tableIndentation, arrayDelimiterPadding, timeZone, showFractionalSeconds); | ||
| } | ||
|
|
||
| public Builder showFractionalSeconds() | ||
| { | ||
| this.showFractionalSeconds = true; | ||
| return this; | ||
| } | ||
| } | ||
|
|
||
| private final IndentationPolicy indentationPolicy; | ||
| private final dev.plex.toml.DatePolicy datePolicy; | ||
|
|
||
| /** | ||
| * Creates a TomlWriter instance. | ||
| */ | ||
| public TomlWriter() | ||
| { | ||
| this(0, 0, 0, TimeZone.getTimeZone("UTC"), false); | ||
| } | ||
|
|
||
| private TomlWriter(int keyIndentation, int tableIndentation, int arrayDelimiterPadding, TimeZone timeZone, boolean showFractionalSeconds) | ||
| { | ||
| this.indentationPolicy = new IndentationPolicy(keyIndentation, tableIndentation, arrayDelimiterPadding); | ||
| this.datePolicy = new dev.plex.toml.DatePolicy(timeZone, showFractionalSeconds); | ||
| } | ||
|
|
||
| /** | ||
| * Write an Object into TOML String. | ||
| * | ||
| * @param from the object to be written | ||
| * @return a string containing the TOML representation of the given Object | ||
| */ | ||
| public String write(Object from) | ||
| { | ||
| try | ||
| { | ||
| StringWriter output = new StringWriter(); | ||
| write(from, output, null); | ||
|
|
||
| return output.toString(); | ||
| } | ||
| catch (IOException e) | ||
| { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Write an Object in TOML to a {@link File}. Output is encoded as UTF-8. | ||
| * | ||
| * @param from the object to be written | ||
| * @param target the File to which the TOML will be written | ||
| * @throws IOException if any file operations fail | ||
| */ | ||
| public void write(Object from, File target) throws IOException | ||
| { | ||
| OutputStream outputStream = new FileOutputStream(target, true); | ||
| try | ||
| { | ||
| write(from, outputStream, target); | ||
| } | ||
| finally | ||
| { | ||
| outputStream.close(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Write an Object in TOML to a {@link OutputStream}. Output is encoded as UTF-8. | ||
| * | ||
| * @param from the object to be written | ||
| * @param target the OutputStream to which the TOML will be written. The stream is NOT closed after being written to. | ||
| * @throws IOException if target.write() fails | ||
| */ | ||
| public void write(Object from, OutputStream target, @Nullable File file) throws IOException | ||
| { | ||
| OutputStreamWriter writer = new OutputStreamWriter(target, StandardCharsets.UTF_8); | ||
| write(from, writer, file); | ||
| writer.flush(); | ||
| } | ||
|
|
||
| /** | ||
| * Write an Object in TOML to a {@link Writer}. You MUST ensure that the {@link Writer}s's encoding is set to UTF-8 for the TOML to be valid. | ||
| * | ||
| * @param from the object to be written. Can be a Map or a custom type. Must not be null. | ||
| * @param target the Writer to which TOML will be written. The Writer is not closed. | ||
| * @throws IOException if target.write() fails | ||
| * @throws IllegalArgumentException if from is of an invalid type | ||
| */ | ||
| public void write(Object from, Writer target, @Nullable File file) throws IOException | ||
| { | ||
| dev.plex.toml.ValueWriter valueWriter = WRITERS.findWriterFor(from); | ||
| if (valueWriter == MapValueWriter.MAP_VALUE_WRITER || valueWriter == dev.plex.toml.ObjectValueWriter.OBJECT_VALUE_WRITER) | ||
| { | ||
| WriterContext context = new WriterContext(indentationPolicy, datePolicy, target); | ||
| if (file != null && file.exists()) | ||
| { | ||
| context.file = file; | ||
| } | ||
| valueWriter.write(from, context); | ||
| } | ||
| else | ||
| { | ||
| throw new IllegalArgumentException("An object of class " + from.getClass().getSimpleName() + " cannot produce valid TOML. Please pass in a Map or a custom type."); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package dev.plex.toml; | ||
|
|
||
| import java.util.concurrent.atomic.AtomicInteger; | ||
|
|
||
| interface ValueReader | ||
| { | ||
|
|
||
| /** | ||
| * @param s must already have been trimmed | ||
| */ | ||
| boolean canRead(String s); | ||
|
|
||
| /** | ||
| * Partial validation. Stops after type terminator, rather than at EOI. | ||
| * | ||
| * @param s must already have been validated by {@link #canRead(String)} | ||
| * @param index where to start in s | ||
| * @return a value or a {@link dev.plex.toml.Results.Errors} | ||
| */ | ||
| Object read(String s, AtomicInteger index, dev.plex.toml.Context context); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package dev.plex.toml; | ||
|
|
||
| import java.util.concurrent.atomic.AtomicInteger; | ||
| import static dev.plex.toml.ArrayValueReader.ARRAY_VALUE_READER; | ||
| import static dev.plex.toml.BooleanValueReaderWriter.BOOLEAN_VALUE_READER_WRITER; | ||
| import static dev.plex.toml.DateValueReaderWriter.DATE_VALUE_READER_WRITER; | ||
| import static dev.plex.toml.LiteralStringValueReader.LITERAL_STRING_VALUE_READER; | ||
| import static dev.plex.toml.MultilineLiteralStringValueReader.MULTILINE_LITERAL_STRING_VALUE_READER; | ||
| import static dev.plex.toml.MultilineStringValueReader.MULTILINE_STRING_VALUE_READER; | ||
| import static dev.plex.toml.StringValueReaderWriter.STRING_VALUE_READER_WRITER; | ||
|
|
||
| class ValueReaders | ||
| { | ||
|
|
||
| static final ValueReaders VALUE_READERS = new ValueReaders(); | ||
|
|
||
| Object convert(String value, AtomicInteger index, dev.plex.toml.Context context) | ||
| { | ||
| String substring = value.substring(index.get()); | ||
| for (dev.plex.toml.ValueReader valueParser : READERS) | ||
| { | ||
| if (valueParser.canRead(substring)) | ||
| { | ||
| return valueParser.read(value, index, context); | ||
| } | ||
| } | ||
|
|
||
| dev.plex.toml.Results.Errors errors = new dev.plex.toml.Results.Errors(); | ||
| errors.invalidValue(context.identifier.getName(), substring, context.line.get()); | ||
| return errors; | ||
| } | ||
|
|
||
| private ValueReaders() | ||
| { | ||
| } | ||
|
|
||
| private static final dev.plex.toml.ValueReader[] READERS = { | ||
| MULTILINE_STRING_VALUE_READER, MULTILINE_LITERAL_STRING_VALUE_READER, LITERAL_STRING_VALUE_READER, STRING_VALUE_READER_WRITER, DATE_VALUE_READER_WRITER, NumberValueReaderWriter.NUMBER_VALUE_READER_WRITER, BOOLEAN_VALUE_READER_WRITER, ARRAY_VALUE_READER, InlineTableValueReader.INLINE_TABLE_VALUE_READER | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package dev.plex.toml; | ||
|
|
||
| interface ValueWriter | ||
| { | ||
| boolean canWrite(Object value); | ||
|
|
||
| void write(Object value, WriterContext context); | ||
|
|
||
| boolean isPrimitiveType(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package dev.plex.toml; | ||
|
|
||
| class ValueWriters | ||
| { | ||
|
|
||
| static final ValueWriters WRITERS = new ValueWriters(); | ||
|
|
||
| ValueWriter findWriterFor(Object value) | ||
| { | ||
| for (ValueWriter valueWriter : VALUE_WRITERS) | ||
| { | ||
| if (valueWriter.canWrite(value)) | ||
| { | ||
| return valueWriter; | ||
| } | ||
| } | ||
|
|
||
| return ObjectValueWriter.OBJECT_VALUE_WRITER; | ||
| } | ||
|
|
||
| private ValueWriters() | ||
| { | ||
| } | ||
|
|
||
| private static dev.plex.toml.DateValueReaderWriter getPlatformSpecificDateConverter() | ||
| { | ||
| String specificationVersion = Runtime.class.getPackage().getSpecificationVersion(); | ||
| return specificationVersion != null && specificationVersion.startsWith("1.6") ? dev.plex.toml.DateValueReaderWriter.DATE_PARSER_JDK_6 : dev.plex.toml.DateValueReaderWriter.DATE_VALUE_READER_WRITER; | ||
| } | ||
|
|
||
| private static final ValueWriter[] VALUE_WRITERS = { | ||
| StringValueReaderWriter.STRING_VALUE_READER_WRITER, NumberValueReaderWriter.NUMBER_VALUE_READER_WRITER, dev.plex.toml.BooleanValueReaderWriter.BOOLEAN_VALUE_READER_WRITER, getPlatformSpecificDateConverter(), | ||
| MapValueWriter.MAP_VALUE_WRITER, dev.plex.toml.PrimitiveArrayValueWriter.PRIMITIVE_ARRAY_VALUE_WRITER, TableArrayValueWriter.TABLE_ARRAY_VALUE_WRITER | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| package dev.plex.toml; | ||
|
|
||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.io.Writer; | ||
| import java.util.Arrays; | ||
|
|
||
| class WriterContext | ||
| { | ||
| private String arrayKey = null; | ||
| private boolean isArrayOfTable = false; | ||
| private boolean empty = true; | ||
| public final String key; | ||
| private final String currentTableIndent; | ||
| private final String currentFieldIndent; | ||
| private final Writer output; | ||
| private final dev.plex.toml.IndentationPolicy indentationPolicy; | ||
| private final dev.plex.toml.DatePolicy datePolicy; | ||
|
|
||
| public File file; | ||
| public String parentName; | ||
| public boolean hasRun = false; | ||
|
|
||
| WriterContext(dev.plex.toml.IndentationPolicy indentationPolicy, dev.plex.toml.DatePolicy datePolicy, Writer output) | ||
| { | ||
| this("", "", output, indentationPolicy, datePolicy); | ||
| } | ||
|
|
||
| WriterContext pushTable(String newKey) | ||
| { | ||
| String newIndent = ""; | ||
| if (!key.isEmpty()) | ||
| { | ||
| newIndent = growIndent(indentationPolicy); | ||
| } | ||
|
|
||
| String fullKey = key.isEmpty() ? newKey : key + "." + newKey; | ||
|
|
||
| WriterContext subContext = new WriterContext(fullKey, newIndent, output, indentationPolicy, datePolicy); | ||
| if (!empty) | ||
| { | ||
| subContext.empty = false; | ||
| } | ||
|
|
||
| return subContext; | ||
| } | ||
|
|
||
| WriterContext pushTableFromArray() | ||
| { | ||
| WriterContext subContext = new WriterContext(key, currentTableIndent, output, indentationPolicy, datePolicy); | ||
| if (!empty) | ||
| { | ||
| subContext.empty = false; | ||
| } | ||
| subContext.setIsArrayOfTable(true); | ||
|
|
||
| return subContext; | ||
| } | ||
|
|
||
| WriterContext write(String s) | ||
| { | ||
| try | ||
| { | ||
| output.write(s); | ||
| if (empty && !s.isEmpty()) | ||
| { | ||
| empty = false; | ||
| } | ||
|
|
||
| return this; | ||
| } | ||
| catch (IOException e) | ||
| { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| void write(char[] chars) | ||
| { | ||
| for (char c : chars) | ||
| { | ||
| write(c); | ||
| } | ||
| } | ||
|
|
||
| WriterContext write(char c) | ||
| { | ||
| try | ||
| { | ||
| output.write(c); | ||
| empty = false; | ||
|
|
||
| return this; | ||
| } | ||
| catch (IOException e) | ||
| { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| void writeKey() | ||
| { | ||
| if (key.isEmpty()) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (!empty) | ||
| { | ||
| write('\n'); | ||
| } | ||
|
|
||
| write(currentTableIndent); | ||
|
|
||
| if (isArrayOfTable) | ||
| { | ||
| write("[[").write(key).write("]]\n"); | ||
| } | ||
| else | ||
| { | ||
| write('[').write(key).write("]\n"); | ||
| } | ||
| } | ||
|
|
||
| void writeArrayDelimiterPadding() | ||
| { | ||
| for (int i = 0; i < indentationPolicy.getArrayDelimiterPadding(); i++) | ||
| { | ||
| write(' '); | ||
| } | ||
| } | ||
|
|
||
| void indent() | ||
| { | ||
| if (!key.isEmpty()) | ||
| { | ||
| write(currentFieldIndent); | ||
| } | ||
| } | ||
|
|
||
| dev.plex.toml.DatePolicy getDatePolicy() | ||
| { | ||
| return datePolicy; | ||
| } | ||
|
|
||
| WriterContext setIsArrayOfTable(boolean isArrayOfTable) | ||
| { | ||
| this.isArrayOfTable = isArrayOfTable; | ||
| return this; | ||
| } | ||
|
|
||
| WriterContext setArrayKey(String arrayKey) | ||
| { | ||
| this.arrayKey = arrayKey; | ||
| return this; | ||
| } | ||
|
|
||
| String getContextPath() | ||
| { | ||
| return key.isEmpty() ? arrayKey : key + "." + arrayKey; | ||
| } | ||
|
|
||
| private String growIndent(dev.plex.toml.IndentationPolicy indentationPolicy) | ||
| { | ||
| return currentTableIndent + fillStringWithSpaces(indentationPolicy.getTableIndent()); | ||
| } | ||
|
|
||
| private String fillStringWithSpaces(int count) | ||
| { | ||
| char[] chars = new char[count]; | ||
| Arrays.fill(chars, ' '); | ||
|
|
||
| return new String(chars); | ||
| } | ||
|
|
||
| private WriterContext(String key, String tableIndent, Writer output, dev.plex.toml.IndentationPolicy indentationPolicy, dev.plex.toml.DatePolicy datePolicy) | ||
| { | ||
| this.key = key; | ||
| this.output = output; | ||
| this.indentationPolicy = indentationPolicy; | ||
| this.currentTableIndent = tableIndent; | ||
| this.datePolicy = datePolicy; | ||
| this.currentFieldIndent = tableIndent + fillStringWithSpaces(this.indentationPolicy.getKeyValueIndent()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package dev.plex.util; | ||
|
|
||
| import dev.plex.Plex; | ||
| import dev.plex.settings.ServerSettings; | ||
| import net.kyori.adventure.text.Component; | ||
| import net.kyori.adventure.text.format.NamedTextColor; | ||
| import net.kyori.adventure.text.minimessage.MiniMessage; | ||
|
|
||
| public class PlexLog | ||
| { | ||
| public static void log(String message, Object... strings) | ||
| { | ||
| for (int i = 0; i < strings.length; i++) | ||
| { | ||
| if (message.contains("{" + i + "}")) | ||
| { | ||
| message = message.replace("{" + i + "}", strings[i].toString()); | ||
| } | ||
| } | ||
| Plex.get().getServer().getConsoleCommandSource().sendMessage(MiniMessage.miniMessage().deserialize("<yellow>[Plex] <gray>" + message)); | ||
| } | ||
|
|
||
| public static void log(Component component) | ||
| { | ||
| Plex.get().getServer().getConsoleCommandSource().sendMessage(Component.text("[Plex] ").color(NamedTextColor.YELLOW).append(component).colorIfAbsent(NamedTextColor.GRAY)); | ||
| } | ||
|
|
||
| public static void error(String message, Object... strings) | ||
| { | ||
| for (int i = 0; i < strings.length; i++) | ||
| { | ||
| if (message.contains("{" + i + "}")) | ||
| { | ||
| message = message.replace("{" + i + "}", strings[i].toString()); | ||
| } | ||
| } | ||
| Plex.get().getServer().getConsoleCommandSource().sendMessage(MiniMessage.miniMessage().deserialize("<red>[Plex Error] <gold>" + message)); | ||
| } | ||
|
|
||
| public static void warn(String message, Object... strings) | ||
| { | ||
| for (int i = 0; i < strings.length; i++) | ||
| { | ||
| if (message.contains("{" + i + "}")) | ||
| { | ||
| message = message.replace("{" + i + "}", strings[i].toString()); | ||
| } | ||
| } | ||
| Plex.get().getServer().getConsoleCommandSource().sendMessage(MiniMessage.miniMessage().deserialize("<#eb7c0e>[Plex Warning] <gold>" + message)); | ||
| } | ||
|
|
||
| public static void debug(String message, Object... strings) | ||
| { | ||
| for (int i = 0; i < strings.length; i++) | ||
| { | ||
| if (message.contains("{" + i + "}")) | ||
| { | ||
| message = message.replace("{" + i + "}", strings[i].toString()); | ||
| } | ||
| } | ||
| if (Plex.get().getConfig().as(ServerSettings.class).getServer().isDebug()) | ||
| { | ||
| Plex.get().getServer().getConsoleCommandSource().sendMessage(MiniMessage.miniMessage().deserialize("<dark_purple>[Plex Debug] <gold>" + message)); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package dev.plex.util; | ||
|
|
||
| import java.util.concurrent.ThreadLocalRandom; | ||
| import net.kyori.adventure.text.format.NamedTextColor; | ||
|
|
||
| public class RandomUtil | ||
| { | ||
| public static NamedTextColor getRandomColor() | ||
| { | ||
| NamedTextColor[] colors = NamedTextColor.NAMES.values().stream().filter(namedTextColor -> namedTextColor != NamedTextColor.BLACK && namedTextColor != NamedTextColor.DARK_BLUE).toArray(NamedTextColor[]::new); | ||
| return colors[randomNum(colors.length)]; | ||
| } | ||
|
|
||
| public static boolean randomBoolean() | ||
| { | ||
| return ThreadLocalRandom.current().nextBoolean(); | ||
| } | ||
|
|
||
| public static int randomNum() | ||
| { | ||
| return ThreadLocalRandom.current().nextInt(); | ||
| } | ||
|
|
||
| public static int randomNum(int limit) | ||
| { | ||
| return ThreadLocalRandom.current().nextInt(limit); | ||
| } | ||
|
|
||
| public static int randomNum(int start, int limit) | ||
| { | ||
| return ThreadLocalRandom.current().nextInt(start, limit); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| ############################# | ||
| # # | ||
| # Plex Velocity # | ||
| # v1.1 # | ||
| # # | ||
| ############################# | ||
|
|
||
| [server] | ||
| name = "Plexus" | ||
|
|
||
| # Placeholders | ||
| # %mcversion% - The Velocity Version (i.e. 3.1.2-SNAPSHOT) | ||
| # %servername% - The name provided above | ||
| # %randomgradient% - Creates a random gradient every ping of two random colors for the whole string | ||
| # Supports MiniMessage strings, no legacy & and § | ||
| motd = ["%randomgradient%%servername% - %mcversion%", "Another motd"] | ||
| colorizeMotd = false | ||
|
|
||
| # Enables debug messages | ||
| debug = false | ||
|
|
||
| # Due to game code only supporting legacy color codes for | ||
| # player samples and not components, you may only use § or & here | ||
| # for colors. | ||
| sample = ["example", "example"] | ||
|
|
||
| # Adds this amount to the current player count | ||
| add-player-count = 0 | ||
|
|
||
| # The max player count will always display as +1 more than the player count | ||
| plus-one-max-count = true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| plugins { | ||
| id "net.minecrell.plugin-yml.bukkit" version "0.6.1-SNAPSHOT" | ||
| } | ||
|
|
||
| dependencies { | ||
| library "org.projectlombok:lombok:1.18.24" | ||
| annotationProcessor "org.projectlombok:lombok:1.18.24" | ||
| library "org.json:json:20220320" | ||
| library "commons-io:commons-io:2.11.0" | ||
| library "dev.morphia.morphia:morphia-core:2.2.6" | ||
| library "redis.clients:jedis:4.2.2" | ||
| library "org.mariadb.jdbc:mariadb-java-client:3.0.4" | ||
| library "com.zaxxer:HikariCP:5.0.1" | ||
| library "org.apache.httpcomponents:httpclient:4.5.13" | ||
| library "org.apache.commons:commons-lang3:3.12.0" | ||
| library "org.apache.maven.resolver:maven-resolver-api:1.8.0" | ||
| library "org.apache.maven.resolver:maven-resolver-impl:1.8.0" | ||
| library "org.apache.maven.resolver:maven-resolver-connector-basic:1.8.0" | ||
| library "org.apache.maven.resolver:maven-resolver-transport-http:1.8.0" | ||
| library "org.apache.maven:maven-resolver-provider:3.8.5" | ||
| library "org.eclipse.jetty:jetty-server:11.0.9" | ||
| library "org.eclipse.jetty:jetty-servlet:11.0.9" | ||
| library "org.eclipse.jetty:jetty-proxy:11.0.9" | ||
| library "com.google.code.gson:gson:2.9.0" | ||
| compileOnly "io.papermc.paper:paper-api:1.18.2-R0.1-SNAPSHOT" | ||
| compileOnly("com.github.MilkBowl:VaultAPI:1.7") { | ||
| exclude group: "org.bukkit", module: "bukkit" | ||
| } | ||
| implementation "org.bstats:bstats-base:3.0.0" | ||
| implementation "org.bstats:bstats-bukkit:3.0.0" | ||
| implementation project(":api") | ||
| } | ||
|
|
||
| group = rootProject.group | ||
| version = rootProject.version | ||
| description = "Plex-Server" | ||
|
|
||
| shadowJar { | ||
| archiveBaseName.set("Plex") | ||
| archiveClassifier.set("") | ||
| relocate "org.bstats", "dev.plex" | ||
| } | ||
|
|
||
| bukkit { | ||
| name = "Plex" | ||
| version = project.version | ||
| description = "Plex provides a new experience for freedom servers." | ||
| main = "dev.plex.Plex" | ||
| website = "https://plex.us.org" | ||
| authors = ["Telesphoreo", "taahanis", "supernt"] | ||
| softDepend = ["Vault"] | ||
| apiVersion = "1.18" | ||
| } | ||
|
|
||
| String getGitHash() { | ||
| def stdout = new ByteArrayOutputStream() | ||
| try { | ||
| exec { | ||
| commandLine "git", "rev-parse", "--short", "HEAD" | ||
| standardOutput = stdout | ||
| ignoreExitValue = true | ||
| } | ||
| } catch (GradleException e) { | ||
| logger.error("Couldn't determine Git head because Git is not installed. " + e.getMessage()) | ||
| } | ||
| return stdout.size() > 0 ? stdout.toString().trim() : "unknown" | ||
| } | ||
|
|
||
| String getBuildNumber() { | ||
| def stdout = new ByteArrayOutputStream() | ||
| try { | ||
| exec { | ||
| commandLine "git", "rev-list", "HEAD", "--count" | ||
| standardOutput = stdout | ||
| ignoreExitValue = true | ||
| } | ||
| } catch (GradleException e) { | ||
| logger.error("Couldn't determine build number because Git is not installed. " + e.getMessage()) | ||
| } | ||
| return stdout.size() ? stdout.toString().trim() + " (local)" : "unknown" | ||
| } | ||
|
|
||
| static def getDate() { | ||
| return new Date().format("MM/dd/yyyy '<light_purple>at<gold>' hh:mm:ss a z") | ||
| } | ||
|
|
||
| task buildProperties { | ||
| ant.propertyfile(file: "$project.projectDir/src/main/resources/build.properties") { | ||
| entry(key: "buildAuthor", default: System.getenv("JENKINS_URL") != null ? "jenkins" : "unknown") | ||
| entry(key: "buildNumber", value: System.getenv("JENKINS_URL") != null ? System.getenv("BUILD_NUMBER") + " (Jenkins)" : getBuildNumber()) | ||
| entry(key: "buildDate", value: getDate()) | ||
| entry(key: "buildHead", value: getGitHash()) | ||
| } | ||
| } | ||
|
|
||
| tasks { | ||
| build { | ||
| dependsOn(shadowJar) | ||
| finalizedBy(buildProperties) | ||
| } | ||
|
|
||
| jar { | ||
| enabled = false | ||
| } | ||
|
|
||
| shadowJar { | ||
| finalizedBy(rootProject.tasks.copyJars) | ||
| } | ||
|
|
||
| javadoc { | ||
| options.memberLevel = JavadocMemberLevel.PRIVATE | ||
| } | ||
| } | ||
|
|
||
| publishing { | ||
| publications { | ||
| maven(MavenPublication) { | ||
| pom.withXml { | ||
| def dependenciesNode = asNode().appendNode("dependencies") | ||
| configurations.getByName("library").getAllDependencies().each { dependency -> | ||
| dependenciesNode.appendNode("dependency").with { | ||
| it.appendNode("groupId", dependency.group) | ||
| it.appendNode("artifactId", dependency.name) | ||
| it.appendNode("version", dependency.version) | ||
| it.appendNode("scope", "provided") | ||
| } | ||
| } | ||
| configurations.getByName("implementation").getAllDependencies().each { dependency -> | ||
| dependenciesNode.appendNode("dependency").with { | ||
| it.appendNode("groupId", dependency.group) | ||
| it.appendNode("artifactId", dependency.name) | ||
| it.appendNode("version", dependency.version) | ||
| it.appendNode("scope", "runtime") | ||
| } | ||
| } | ||
| } | ||
| artifacts = [shadowJar] | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package dev.plex; | ||
|
|
||
| import dev.plex.api.IPlayerCache; | ||
| import dev.plex.api.PlexApi; | ||
| import dev.plex.player.PlexPlayer; | ||
|
|
||
| public class PlexProvider implements PlexApi | ||
| { | ||
| @Override | ||
| public IPlayerCache<PlexPlayer> getPlayerCache() | ||
| { | ||
| return Plex.get().getPlayerCache(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| package dev.plex.command.impl; | ||
|
|
||
| import dev.plex.api.event.GameModeUpdateEvent; | ||
| import dev.plex.command.PlexCommand; | ||
| import dev.plex.command.annotation.CommandParameters; | ||
| import dev.plex.command.annotation.CommandPermissions; | ||
| import dev.plex.command.exception.CommandFailException; | ||
| import dev.plex.command.source.RequiredCommandSource; | ||
| import dev.plex.rank.enums.Rank; | ||
| import dev.plex.util.PlexUtils; | ||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import net.kyori.adventure.text.Component; | ||
| import org.bukkit.Bukkit; | ||
| import org.bukkit.GameMode; | ||
| import org.bukkit.command.CommandSender; | ||
| import org.bukkit.entity.Player; | ||
| import org.jetbrains.annotations.NotNull; | ||
| import org.jetbrains.annotations.Nullable; | ||
|
|
||
| @CommandParameters(name = "gamemode", usage = "/<command> <creative | survival | adventure | default | spectator> [player]", description = "Change your gamemode", aliases = "gm,egamemode,gmt,egmt") | ||
| @CommandPermissions(level = Rank.OP, permission = "plex.gamemode", source = RequiredCommandSource.ANY) | ||
| public class GamemodeCMD extends PlexCommand | ||
| { | ||
| private GameMode gamemode; | ||
|
|
||
| @Override | ||
| protected Component execute(@NotNull CommandSender sender, @Nullable Player playerSender, String[] args) | ||
| { | ||
| if (args.length == 0) | ||
| { | ||
| return usage(); | ||
| } | ||
| switch (args[0].toLowerCase()) | ||
| { | ||
| case "survival", "s", "0" -> | ||
| { | ||
| gamemode = GameMode.SURVIVAL; | ||
| update(sender, playerSender, GameMode.SURVIVAL); | ||
| return null; | ||
| } | ||
| case "creative", "c", "1" -> | ||
| { | ||
| gamemode = GameMode.CREATIVE; | ||
| update(sender, playerSender, GameMode.CREATIVE); | ||
| return null; | ||
| } | ||
| case "adventure", "a", "2" -> | ||
| { | ||
| gamemode = GameMode.ADVENTURE; | ||
| update(sender, playerSender, GameMode.ADVENTURE); | ||
| return null; | ||
| } | ||
| case "default", "d", "5" -> | ||
| { | ||
| gamemode = plugin.getServer().getDefaultGameMode(); | ||
| update(sender, playerSender, plugin.getServer().getDefaultGameMode()); | ||
| return null; | ||
| } | ||
| case "spectator", "sp", "3", "6" -> | ||
| { | ||
| gamemode = GameMode.SPECTATOR; | ||
| checkRank(sender, Rank.ADMIN, "plex.gamemode.spectator"); | ||
| update(sender, playerSender, GameMode.SPECTATOR); | ||
| return null; | ||
| } | ||
| } | ||
| if (args.length > 1) | ||
| { | ||
| checkRank(sender, Rank.ADMIN, "plex.gamemode.others"); | ||
| Player player = getNonNullPlayer(args[1]); | ||
| Bukkit.getServer().getPluginManager().callEvent(new GameModeUpdateEvent(sender, player, gamemode)); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private void update(CommandSender sender, Player playerSender, GameMode gameMode) | ||
| { | ||
| if (isConsole(sender)) | ||
| { | ||
| throw new CommandFailException(PlexUtils.messageString("consoleMustDefinePlayer")); | ||
| } | ||
| if (!(playerSender == null)) | ||
| { | ||
| Bukkit.getServer().getPluginManager().callEvent(new GameModeUpdateEvent(sender, playerSender.getPlayer(), gameMode)); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException | ||
| { | ||
| if (args.length == 1) | ||
| { | ||
| return Arrays.asList("creative", "survival", "adventure", "spectator", "default"); | ||
| } | ||
| if (args.length == 2) | ||
| { | ||
| return PlexUtils.getPlayerNameList(); | ||
| } | ||
| return Collections.emptyList(); | ||
| } | ||
| } |