Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package me.deecaad.core.file;
import me.deecaad.core.utils.Keyable;
import me.deecaad.core.utils.StringUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public interface InlineSerializer extends Serializer, Keyable {
Pattern NAME_FINDER = Pattern.compile(".+?(?=\\{)");
String UNIQUE_IDENTIFIER = "uniqueIdentifier";
@Override
default boolean shouldSerialize(@NotNull SerializeData data) {
// We don't want FileReader activating on these by default
return false;
}
@Override
default @NotNull String getKey() {
return getInlineKeyword();
}
/**
* Reformats the Upper_Snake_Case YAML config key to UpperCamelCase inline key. This
* helps make inline serialization less sensitive to errors.
*
* @return The non-null lowerCamelCase keyword.
*/
default String getInlineKeyword() {
String keyword = getKeyword();
if (keyword == null) {
String name = getClass().getSimpleName();
throw new NullPointerException("Keyword for " + name + " is null");
}
String[] split = keyword.split("_");
// split[0] = split[0].toLowerCase(Locale.ROOT); // lower camel case
return String.join("", split);
}
static Map inlineFormat(String line) throws FormatException {
// Count trailing whitespace, so we can trim() the edges of the string
// while keeping track of the index of errors in the string.
int trailingWhitespace;
for (trailingWhitespace = 0; trailingWhitespace < line.length(); trailingWhitespace++) {
if (line.charAt(trailingWhitespace) != ' ')
break;
}
line = line.trim();
// This patterns groups the start of the string until the first '{' or
// the end of the string. This is used to determine WHICH mechanic/
// targeter/condition the user is trying to use.
Matcher matcher = NAME_FINDER.matcher(line);
if (!matcher.find())
throw new FormatException(trailingWhitespace, "Could not determine name... Before the first '(' you should have a name like 'Sound'");
String uniqueIdentifier = matcher.group();
// Quick count of open and close curlyBrackets, to make sure the user has the
// correct number and nothing is "malformed"
int squareBrackets = 0;
int curlyBrackets = 0;
for (int i = 0; i < line.length(); i++) {
if (StringUtil.isEscaped(line, i))
continue;
switch (line.charAt(i)) {
case '{' -> curlyBrackets++;
case '}' -> curlyBrackets--;
case '[' -> squareBrackets++;
case ']' -> squareBrackets--;
}
}
// Every opening '{' needs a closing '}'
if (curlyBrackets != 0) {
int index = trailingWhitespace + (curlyBrackets > 0
? index(line, '{', 0, line.lastIndexOf('}'), 1)
: index(line, '}', line.length() - 1, line.indexOf('{'), -1));
throw new FormatException(index, "Missing" + (curlyBrackets > 0 ? " closing '}'" : " opening '{'"));
}
// Every opening '[' needs a closing ']'
if (squareBrackets != 0) {
int index = trailingWhitespace + (squareBrackets > 0
? index(line, '[', 0, line.lastIndexOf(']'), 1)
: index(line, ']', line.length() - 1, line.indexOf('['), -1));
throw new FormatException(index, "Missing" + (squareBrackets > 0 ? " closing ']'" : " opening '['"));
}
// Take away the unique identifier and the outside parens
line = line.substring(uniqueIdentifier.length());
trailingWhitespace += uniqueIdentifier.length();
if (line.startsWith("{") && line.endsWith("}")) {
line = line.substring(1, line.length() - 1);
trailingWhitespace++;
}
// This will return a map of strings, lists, and maps.
Map map = mapify(line, trailingWhitespace);
map.put(UNIQUE_IDENTIFIER, new MapConfigLike.Holder(uniqueIdentifier.trim(), 0));
return map;
}
static Map mapify(String line, int offset) throws FormatException {
Map map = new HashMap<>();
String key = null;
StringBuilder value = new StringBuilder();
for (int i = 0; i < line.length(); i++) {
// When a character is escaped, we should skip the special parsing.
char c = line.charAt(i);
if (StringUtil.isEscaped(line, i)) {
if (StringUtil.isEscaped(line, i, true))
value.append(c);
// If the escaped character was the last character, we need an
// extra check to make sure we add that value to the list.
if (i + 1 >= line.length()) {
if (key == null)
throw new FormatException(offset + i, "Expected key=value, but was missing key... value=" + value);
map.put(key, new MapConfigLike.Holder(value.substring(value.indexOf(" ") == 0 ? 1 : 0), i - value.length()));
}
continue;
}
// Handle nested objects and lists
if (c == '[' || c == '{') {
int start = i + 1;
int stop = start + findMatch(c, c == '[' ? ']' : '}', line.substring(start));
if (c == '{') {
Map tempMap = mapify(line.substring(start, stop), start + offset);
map.put(key, new MapConfigLike.Holder(tempMap, i));
if (!value.toString().isBlank())
tempMap.put(UNIQUE_IDENTIFIER, new MapConfigLike.Holder(value.toString().trim(), offset + i - value.length()));
} else {
List tempList = listify(line.substring(start, stop), start + offset);
map.put(key, new MapConfigLike.Holder(tempList, offset + i));
if (!value.toString().isBlank())
throw new FormatException(offset + i, "Found '" + value + "' before a list... It should not be there!");
}
// Skip ahead
i = stop;
key = null;
value.setLength(0);
// If there is a comma, we should skip it
if (i + 1 < line.length() && line.charAt(i + 1) == ',')
i++;
}
// We found the key! Now what is the value...?
else if (c == '=') {
if (key != null)
throw new FormatException(offset + i, "Found a duplicate '=' after '" + key + "'... Use '\\\\=' for escaped characters.");
if (value.toString().isBlank())
throw new FormatException(offset + i, "Found an empty key");
key = value.toString().trim();
value.setLength(0);
}
// When we reach the end of the line or find a comma, then we *should*
// have a key-value pair. So we have to save it.
else if (c == ',' || i + 1 == line.length()) {
// If this is the last character, make sure it is added to the
// value. Don't add if the character is a comma since users can
// end their list with an extra comma, and we will ignore it.
if (i + 1 == line.length() && c != ',')
value.append(c);
if (key == null)
throw new FormatException(offset + i - value.length(), "Expected key=value, but was missing key... fond '" + value + "'");
if (value.isEmpty())
throw new FormatException(offset + i, "Found an empty value");
map.put(key, new MapConfigLike.Holder(value.toString(), offset + i - value.length() + 1));
key = null;
value.setLength(0);
}
// When there is no special character to handle, the character
// is probably just from a key or value.
else {
value.append(c);
}
}
return map;
}
static List listify(String line, int offset) throws FormatException {
List list = new ArrayList<>();
StringBuilder value = new StringBuilder();
for (int i = 0; i < line.length(); i++) {
// When a character is escaped, we should skip the special parsing.
char c = line.charAt(i);
if (StringUtil.isEscaped(line, i)) {
if (StringUtil.isEscaped(line, i, true))
value.append(c);
// If the escaped character was the last character, we need an
// extra check to make sure we add that value to the list.
if (i + 1 >= line.length())
list.add(new MapConfigLike.Holder(value.substring(value.indexOf(" ") == 0 ? 1 : 0), offset + i - value.length() + 1));
continue;
}
// Illegal characters
if (c == '[' || c == '=')
throw new FormatException(i + offset, "Illegal character '" + c + "'");
// Handle nested objects
if (c == '{') {
int start = i + 1;
int stop = start + findMatch(c, '}', line.substring(start));
Map map = mapify(line.substring(start, stop), offset + stop);
if (!value.toString().isBlank())
map.put(UNIQUE_IDENTIFIER, new MapConfigLike.Holder(value.toString().trim(), offset + i - value.length() + 1));
list.add(new MapConfigLike.Holder(map, offset + i + 1));
i = stop;
value.setLength(0);
// If there is a comma, we should skip it
if (i + 1 < line.length() && line.charAt(i + 1) == ',')
i++;
}
// When we reach the end of the line or find a comma, then we
// should add the value to the list.
else if (c == ',' || i + 1 == line.length()) {
// If this is the last character, make sure it is added to the
// value. Don't add if the character is a comma since users can
// end their list with an extra comma, and we will ignore it.
if (i + 1 == line.length() && c != ',')
value.append(c);
if (value.isEmpty())
throw new FormatException(i + offset, "Found duplicate commas... Use '\\\\,' for an escaped comma");
list.add(new MapConfigLike.Holder(value.substring(value.indexOf(" ") == 0 ? 1 : 0), offset + i - value.length() + 1));
value.setLength(0);
}
else {
value.append(c);
}
}
return list;
}
private static int findMatch(char open, char close, String line) {
int nested = 0;
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (StringUtil.isEscaped(line, i))
continue;
if (c == open)
nested++;
else if (c == close && nested > 0)
nested--;
else if (c == close && nested == 0)
return i;
}
throw new IllegalArgumentException("Could not find match for '" + open + "' in '" + line + "'");
}
private static int index(String str, char c, int start, int stop, int step) {
for (int i = start; i != stop; i += step) {
if (StringUtil.isEscaped(str, i))
continue;
if (str.charAt(i) == c)
return i;
}
return stop;
}
/**
* This exception is used whenever the user inputs an improperly formatted string. This is basically
* a Syntax Error.
*/
class FormatException extends Exception {
private final int index;
public FormatException(int index) {
this.index = index;
}
public FormatException(int index, String message) {
super(message);
this.index = index;
}
public int getIndex() {
return index;
}
}
}