From 05969a2fcc4876b516d65db6c8aa5eb095a3e8e4 Mon Sep 17 00:00:00 2001 From: Flowsqy <47575244+Flowsqy@users.noreply.github.com> Date: Thu, 24 Feb 2022 00:06:14 +0100 Subject: [PATCH] Implement the custom parser in HologramFormat (remove nashorn engine) --- .../config/hologram/HologramFormat.java | 392 +++++++++--------- .../config/hologram/HologramLine.java | 25 ++ .../config/hologram/HologramOption.java | 36 ++ .../shopchest/config/hologram/Operator.java | 75 ---- .../config/hologram/line/FormatReplacer.java | 132 ++++++ .../config/hologram/line/FormattedLine.java | 85 ++++ .../config/hologram/parser/Token.java | 1 + 7 files changed, 468 insertions(+), 278 deletions(-) create mode 100644 plugin/src/main/java/de/epiceric/shopchest/config/hologram/HologramLine.java create mode 100644 plugin/src/main/java/de/epiceric/shopchest/config/hologram/HologramOption.java delete mode 100644 plugin/src/main/java/de/epiceric/shopchest/config/hologram/Operator.java create mode 100644 plugin/src/main/java/de/epiceric/shopchest/config/hologram/line/FormatReplacer.java create mode 100644 plugin/src/main/java/de/epiceric/shopchest/config/hologram/line/FormattedLine.java diff --git a/plugin/src/main/java/de/epiceric/shopchest/config/hologram/HologramFormat.java b/plugin/src/main/java/de/epiceric/shopchest/config/hologram/HologramFormat.java index 6079ef4..8624941 100644 --- a/plugin/src/main/java/de/epiceric/shopchest/config/hologram/HologramFormat.java +++ b/plugin/src/main/java/de/epiceric/shopchest/config/hologram/HologramFormat.java @@ -1,103 +1,200 @@ package de.epiceric.shopchest.config.hologram; -import java.io.File; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; - +import de.epiceric.shopchest.ShopChest; import de.epiceric.shopchest.config.Placeholder; +import de.epiceric.shopchest.config.hologram.condition.Condition; +import de.epiceric.shopchest.config.hologram.line.FormatReplacer; +import de.epiceric.shopchest.config.hologram.line.FormattedLine; +import de.epiceric.shopchest.config.hologram.parser.FormatParser; +import de.epiceric.shopchest.config.hologram.parser.ParserResult; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; -import de.epiceric.shopchest.ShopChest; +import java.io.File; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; public class HologramFormat { - /* - TODO Change implementation of this class - -> Deserialize from the configuration and load it some way to avoid String manipulation at each method invocation - -> Rework the current String manipulation process to add complex expression evaluation WITHOUT nashorn engine - */ - - public enum Requirement { - VENDOR, AMOUNT, ITEM_TYPE, ITEM_NAME, HAS_ENCHANTMENT, BUY_PRICE, - SELL_PRICE, HAS_POTION_EFFECT, IS_MUSIC_DISC, IS_POTION_EXTENDED, IS_BANNER_PATTERN, - IS_WRITTEN_BOOK, ADMIN_SHOP, NORMAL_SHOP, IN_STOCK, MAX_STACK, CHEST_SPACE, DURABILITY - } - - // no "-" sign since no variable can be negative - // e.g.: 100.0 >= 50.0 - private static final Pattern SIMPLE_NUMERIC_CONDITION = Pattern.compile("^(\\d+(?:\\.\\d+)?) ([<>][=]?|[=!]=) (\\d+(?:\\.\\d+)?)$"); - - // e.g.: "STONE" == "DIAMOND_SWORD" - private static final Pattern SIMPLE_STRING_CONDITION = Pattern.compile("^\"([^\"]*)\" ([=!]=) \"([^\"]*)\"$"); - - private ScriptEngineManager manager = new ScriptEngineManager(); - private ScriptEngine engine = manager.getEngineByName("JavaScript"); - - private ShopChest plugin; - private File configFile; - private YamlConfiguration config; + private final ShopChest plugin; + private HologramLine[] lines; public HologramFormat(ShopChest plugin) { - this.configFile = new File(plugin.getDataFolder(), "hologram-format.yml"); - this.config = YamlConfiguration.loadConfiguration(configFile); this.plugin = plugin; } + public void load() { + // Load file + final File configFile = new File(plugin.getDataFolder(), "hologram-format.yml"); + final YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + // Get lines + final ConfigurationSection linesSection = config.getConfigurationSection("lines"); + if (linesSection == null) { + // TODO Inform that there is no lines section + return; + } + // Get options + final Map optionSections = new HashMap<>(); + for (String linesId : linesSection.getKeys(false)) { + final ConfigurationSection lineSection = linesSection.getConfigurationSection(linesId); + if (lineSection == null) { + // TODO Inform that a line must be a section + continue; + } + final ConfigurationSection optionSection = lineSection.getConfigurationSection("options"); + if (optionSection == null) { + // TODO Inform that a line must contain an option section + continue; + } + optionSections.put(linesId, optionSection); + } + + // Sort lines by id + final List orderedOptionSections = optionSections.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(Map.Entry::getValue) + .collect(Collectors.toList()); + + // Prepare formatter + final FormatData data = new FormatData(); + final FormatParser parser = new FormatParser(); + + // Deserialize each option + final List lines = new ArrayList<>(); + // For every line + for (ConfigurationSection optionsSection : orderedOptionSections) { + final List options = new LinkedList<>(); + // For every option of the line + for (final String optionKey : optionsSection.getKeys(false)) { + final ConfigurationSection optionSection = optionsSection.getConfigurationSection(optionKey); + if (optionSection == null) { + // TODO Inform that an 'options' key must refer to a section + continue; + } + // Get the requirements + final List>> requirementConditions = new LinkedList<>(); + + for (String requirement : optionSection.getStringList("requirements")) { + if (requirement == null) { + continue; + } + final ParserResult result; + try { + result = parser.parse( + requirement, + data.getRequirements(), + data.getRequirementsTypes() + ); + } catch (Exception e) { + // TODO Inform that it can not be deserialized + continue; + } + + if (result.isCondition()) { + requirementConditions.add(result.getCondition()); + continue; + } + // TODO Inform that there is a requirement that is not a condition + } + + // Get the format + final String format = optionSection.getString("format"); + if (format == null) { + // TODO Inform that format does not exist for this option + continue; + } + + final FormattedLine formattedString = evaluateFormat(format, parser, data); + + // Add the option + options.add(new HologramOption( + formattedString, + requirementConditions.isEmpty() ? Collections.emptyList() : requirementConditions + )); + + // There is no requirement for this option, so it's the last + // (it will always be picked so the next options are skipped) + if (requirementConditions.isEmpty()) { + break; + } + } + if (options.isEmpty()) { + // TODO Inform that this line does not contain any valid option + continue; + } + + // Add the line + lines.add(new HologramLine(new ArrayList<>(options))); + } + + this.lines = lines.toArray(new HologramLine[0]); + } + + private FormattedLine evaluateFormat(String format, FormatParser parser, FormatData data) { + final FormatReplacer formatReplacer = new FormatReplacer<>(format); + + // Detect and evaluate accolade inner parts + final Map> parsedScripts = new HashMap<>(); + final Matcher matcher = Pattern.compile("\\{([^}]+)}").matcher(format); + + while (matcher.find()) { + final String withBrackets = matcher.group(); + final String script = withBrackets.substring(1, withBrackets.length() - 1); + + final ParserResult result; + try { + result = parser.parse(script, data.getPlaceholders(), data.getPlaceholderTypes()); + } catch (Exception e) { + // TODO Inform that the script can not be deserialized + parsedScripts.put(withBrackets, new ParserResult<>(null, null, null, null)); + continue; + } + parsedScripts.put(withBrackets, result); + } + + // Replace accolade inner parts + for (Map.Entry> entry : parsedScripts.entrySet()) { + final String regex = entry.getKey(); + final ParserResult result = entry.getValue(); + if (result.isConstant()) { + formatReplacer.replace(regex, String.valueOf(result.getConstant())); + } else if (result.isValue()) { + formatReplacer.replace(regex, new FormattedLine.ProviderToString<>(result.getValue())); + } else if (result.isCondition()) { + formatReplacer.replace(regex, new FormattedLine.ConditionToString<>(result.getCondition())); + } else if (result.isCalculation()) { + formatReplacer.replace(regex, new FormattedLine.CalculationToString<>(result.getCalculation())); + } else { + formatReplacer.replace(regex, ""); + } + } + + // Replace classics placeholders + for (Map.Entry entry : data.getPlaceholders().entrySet()) { + formatReplacer.replace(entry.getKey(), new FormattedLine.MapToString<>(entry.getValue())); + } + + return formatReplacer.create(); + } + /** * Get the format for the given line of the hologram - * @param line Line of the hologram + * + * @param line Line of the hologram * @param reqMap Values of the requirements that might be needed by the format (contains {@code null} if not comparable) * @param plaMap Values of the placeholders that might be needed by the format - * @return The format of the first working option, or an empty String if no option is working - * because of not fulfilled requirements + * @return The format of the first working option, or an empty String if no option is working + * because of not fulfilled requirements */ public String getFormat(int line, Map reqMap, Map plaMap) { - ConfigurationSection options = config.getConfigurationSection("lines." + line + ".options"); - - // For every option - optionLoop: - for (String key : options.getKeys(false)) { - ConfigurationSection option = options.getConfigurationSection(key); - List requirements = option.getStringList("requirements"); - - // Check every requirement - for (String sReq : requirements) { - //TODO Maybe remove some loops as every requirements are re evaluated in the #evalRequirement - for (Requirement req : reqMap.keySet()) { - // If the configuration requirement contain a requirement specified by the shop - if (sReq.contains(req.toString())) { - // Then evaluate the requirement. - // If the requirement is not fulfilled, skip this option and go to the next one - if (!evalRequirement(sReq, reqMap)) { - continue optionLoop; - } - // TODO Maybe skip to the next config requirement as the requirement has been found and is valid - } - } - } - - // Here, every requirement is fulfilled and this is the first valid option - - String format = option.getString("format"); - - // Evaluate placeholders and return the formatted line - return evalPlaceholder(format, plaMap); - } - - // No option matching to que shop requirements - // Returning an empty string - return ""; + return lines[line].get(reqMap, plaMap); } public void reload() { - config = YamlConfiguration.loadConfiguration(configFile); + lines = null; + load(); } /** @@ -107,6 +204,8 @@ public class HologramFormat { // Return whether an option contains STOCK or CHEST_SPACE : // - In the format // - In one of its requirement + // TODO Implement this + /* int count = getLineCount(); for (int i = 0; i < count; i++) { ConfigurationSection options = config.getConfigurationSection("lines." + i + ".options"); @@ -126,7 +225,7 @@ public class HologramFormat { } } } - + */ return false; } @@ -134,138 +233,25 @@ public class HologramFormat { * @return Amount of lines in a hologram */ public int getLineCount() { - return config.getConfigurationSection("lines").getKeys(false).size(); + if (lines == null) { + throw new IllegalStateException("The hologram format is loaded"); + } + return lines.length; } /** * @return Configuration of the "hologram-format.yml" file + * @deprecated The configuration is not used during runtime. + * If you invoke this method, you will load the configuration from the disk. */ + @Deprecated public YamlConfiguration getConfig() { - return config; + return YamlConfiguration.loadConfiguration(new File(plugin.getDataFolder(), "hologram-format.yml")); } - /** - * Parse and evaluate a condition - * @param condition Condition to evaluate - * @param values Values of the requirements - * @return Result of the condition - */ - public boolean evalRequirement(String condition, Map values) { - String cond = condition; - - // Double-check the presence of a requirement (WTF ?) - for (HologramFormat.Requirement req : HologramFormat.Requirement.values()) { - if (cond.contains(req.toString()) && values.containsKey(req)) { - Object val = values.get(req); - String sVal = String.valueOf(val); - - if (val instanceof String && !(sVal.startsWith("\"") && sVal.endsWith("\""))) { - sVal = String.format("\"%s\"", sVal); - } - - cond = cond.replace(req.toString(), sVal); - } - } - - // Evaluate three basic condition : Direct Boolean, Math comparison and String equality - - if (cond.equals("true")) { - // e.g.: ADMIN_SHOP - return true; - } else if (cond.equals("false")) { - return false; - } else { - char firstChar = cond.charAt(0); - - // numeric cond: first char must be a digit (no variable can be negative) - if (firstChar >= '0' && firstChar <= '9') { - Matcher matcher = SIMPLE_NUMERIC_CONDITION.matcher(cond); - - if (matcher.find()) { - double a, b; - Operator operator; - try { - a = Double.parseDouble(matcher.group(1)); - operator = Operator.from(matcher.group(2)); - b = Double.parseDouble(matcher.group(3)); - - return operator.compare(a, b); - } catch (IllegalArgumentException ignored) { - // should not happen, since regex checked that there is valid number and valid operator - } - } - } - - // string cond: first char must be a: " - if (firstChar == '"') { - Matcher matcher = SIMPLE_STRING_CONDITION.matcher(cond); - - if (matcher.find()) { - String a, b; - Operator operator; - try { - a = matcher.group(1); - operator = Operator.from(matcher.group(2)); - b = matcher.group(3); - - return operator.compare(a, b); - } catch (IllegalArgumentException | UnsupportedOperationException ignored) { - // should not happen, since regex checked that there is valid operator - } - } - } - - // complex comparison - // Like && and || or other arithmetic operations - try { - return (boolean) engine.eval(cond); - } catch (ScriptException e) { - plugin.debug("Failed to eval condition: " + condition); - plugin.debug(e); - return false; - } - } - } - - /** - * Parse and evaluate a condition - * @param string Message or hologram format whose containing scripts to execute - * @param values Values of the placeholders - * @return Result of the condition - */ - public String evalPlaceholder(String string, Map values) { - // Detect and evaluate accolade inner parts - try { - Matcher matcher = Pattern.compile("\\{([^}]+)}").matcher(string); - String newString = string; - - while (matcher.find()) { - String withBrackets = matcher.group(); - String script = withBrackets.substring(1, withBrackets.length() - 1); - - for (Placeholder placeholder : values.keySet()) { - if (script.contains(placeholder.toString())) { - Object val = values.get(placeholder); - String sVal = String.valueOf(val); - - if (val instanceof String && !(sVal.startsWith("\"") && sVal.endsWith("\""))) { - sVal = String.format("\"%s\"", sVal); - } - - script = script.replace(placeholder.toString(), sVal); - } - } - - String result = String.valueOf(engine.eval(script)); - newString = newString.replace(withBrackets, result); - } - - return newString; - } catch (ScriptException e) { - plugin.debug("Failed to eval placeholder script in string: " + string); - plugin.debug(e); - } - - return string; + public enum Requirement { + VENDOR, AMOUNT, ITEM_TYPE, ITEM_NAME, HAS_ENCHANTMENT, BUY_PRICE, + SELL_PRICE, HAS_POTION_EFFECT, IS_MUSIC_DISC, IS_POTION_EXTENDED, IS_BANNER_PATTERN, + IS_WRITTEN_BOOK, ADMIN_SHOP, NORMAL_SHOP, IN_STOCK, MAX_STACK, CHEST_SPACE, DURABILITY } } diff --git a/plugin/src/main/java/de/epiceric/shopchest/config/hologram/HologramLine.java b/plugin/src/main/java/de/epiceric/shopchest/config/hologram/HologramLine.java new file mode 100644 index 0000000..6bc08a7 --- /dev/null +++ b/plugin/src/main/java/de/epiceric/shopchest/config/hologram/HologramLine.java @@ -0,0 +1,25 @@ +package de.epiceric.shopchest.config.hologram; + +import de.epiceric.shopchest.config.Placeholder; + +import java.util.List; +import java.util.Map; + +public class HologramLine { + + private final List options; + + public HologramLine(List options) { + this.options = options; + } + + public final String get(Map reqMap, Map plaMap) { + for (HologramOption option : options) { + if (option.isValid(reqMap)) { + return option.getFormat(plaMap); + } + } + return ""; + } + +} diff --git a/plugin/src/main/java/de/epiceric/shopchest/config/hologram/HologramOption.java b/plugin/src/main/java/de/epiceric/shopchest/config/hologram/HologramOption.java new file mode 100644 index 0000000..f88179d --- /dev/null +++ b/plugin/src/main/java/de/epiceric/shopchest/config/hologram/HologramOption.java @@ -0,0 +1,36 @@ +package de.epiceric.shopchest.config.hologram; + +import de.epiceric.shopchest.config.Placeholder; +import de.epiceric.shopchest.config.hologram.condition.Condition; +import de.epiceric.shopchest.config.hologram.line.FormattedLine; + +import java.util.List; +import java.util.Map; + +public class HologramOption { + + private final FormattedLine formattedString; + private final List>> requirements; + + public HologramOption(FormattedLine formattedString, List>> requirements) { + this.formattedString = formattedString; + this.requirements = requirements; + } + + public boolean isValid(Map requirementsValues) { + if (requirementsValues == null) { + return true; + } + for (Condition> condition : requirements) { + if (!condition.test(requirementsValues)) { + return false; + } + } + return true; + } + + public String getFormat(Map placeholderValues) { + return formattedString.get(placeholderValues); + } + +} diff --git a/plugin/src/main/java/de/epiceric/shopchest/config/hologram/Operator.java b/plugin/src/main/java/de/epiceric/shopchest/config/hologram/Operator.java deleted file mode 100644 index 30f9deb..0000000 --- a/plugin/src/main/java/de/epiceric/shopchest/config/hologram/Operator.java +++ /dev/null @@ -1,75 +0,0 @@ -package de.epiceric.shopchest.config.hologram; - -public enum Operator { - - EQUAL("==") { - @Override - public boolean compare(double a, double b) { - return Double.compare(a, b) == 0; - } - @Override - public boolean compare(String a, String b) { - return a.equals(b); - } - }, - - NOT_EQUAL("!=") { - @Override - public boolean compare(double a, double b) { - return Double.compare(a, b) != 0; - } - @Override - public boolean compare(String a, String b) { - return !a.equals(b); - } - }, - - GREATER_THAN(">") { - @Override - public boolean compare(double a, double b) { - return a > b; - } - }, - - GREATER_THAN_OR_EQUAL(">=") { - @Override - public boolean compare(double a, double b) { - return a >= b; - } - }, - - LESS_THAN("<") { - @Override - public boolean compare(double a, double b) { - return a < b; - } - }, - - LESS_THAN_OR_EQUAL("<=") { - @Override - public boolean compare(double a, double b) { - return a <= b; - } - }; - - private final String symbol; - - Operator(String symbol) { - this.symbol = symbol; - } - - public static Operator from(String symbol) { - for (Operator operator : values()) { - if (operator.symbol.equals(symbol)) { - return operator; - } - } - throw new IllegalArgumentException(); - } - - public abstract boolean compare(double a, double b); - - public boolean compare(String a, String b) { - throw new UnsupportedOperationException(); - } -} diff --git a/plugin/src/main/java/de/epiceric/shopchest/config/hologram/line/FormatReplacer.java b/plugin/src/main/java/de/epiceric/shopchest/config/hologram/line/FormatReplacer.java new file mode 100644 index 0000000..dd5a92c --- /dev/null +++ b/plugin/src/main/java/de/epiceric/shopchest/config/hologram/line/FormatReplacer.java @@ -0,0 +1,132 @@ +package de.epiceric.shopchest.config.hologram.line; + +import de.epiceric.shopchest.config.hologram.provider.ConstantProvider; + +import java.util.*; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FormatReplacer

{ + + private List> tokens; + + public FormatReplacer(String original) { + Objects.requireNonNull(original); + this.tokens = new ArrayList<>(Collections.singletonList(new Token.StringToken<>(original))); + } + + public FormatReplacer

replace(String regex, String replacement) { + final List> outputTokens = new ArrayList<>(); + for (final Token

token : tokens) { + if (token.isString()) { + outputTokens.add(new Token.StringToken<>(token.getString().replace(regex, replacement))); + } else { + outputTokens.add(token); + } + } + this.tokens = outputTokens; + return this; + } + + public FormatReplacer

replace(String regex, Function, String> dynamic) { + final List> outputTokens = new ArrayList<>(); + for (final Token

token : tokens) { + if (token.isString()) { + outputTokens.addAll(getTokenReplacement(token.getString(), regex, dynamic)); + } else { + outputTokens.add(token); + } + } + this.tokens = outputTokens; + return this; + } + + public FormattedLine

create() { + final List, String>> components = new ArrayList<>(); + tokens.stream().map(Token::getDynamics).map(Arrays::asList).forEach(components::addAll); + return new FormattedLine<>(components); + } + + private List> getTokenReplacement(String input, String regex, Function, String> dynamic) { + final List> tokens = new ArrayList<>(); + final Matcher matcher = Pattern.compile(regex, Pattern.LITERAL).matcher(input); + if (matcher.find()) { + int cursor = 0; + do { + final String pre = input.substring(cursor, matcher.start()); + if (!pre.isEmpty()) { + tokens.add(new Token.StringToken<>(pre)); + } + tokens.add(new Token.DynamicsToken<>(dynamic)); + cursor = matcher.end(); + } while (matcher.find()); + final String end = input.substring(cursor); + if (!end.isEmpty()) { + tokens.add(new Token.StringToken<>(end)); + } + return tokens; + } + tokens.add(new Token.StringToken<>(input)); + return tokens; + } + + public interface Token

{ + + boolean isString(); + + String getString(); + + Function, String> getDynamics(); + + final class DynamicsToken

implements Token

{ + + private final Function, String> dynamics; + + public DynamicsToken(Function, String> dynamics) { + this.dynamics = dynamics; + } + + @Override + public boolean isString() { + return false; + } + + @Override + public String getString() { + throw new UnsupportedOperationException(); + } + + @Override + public Function, String> getDynamics() { + return dynamics; + } + } + + final class StringToken

implements Token

{ + + private final String string; + + public StringToken(String string) { + this.string = string; + } + + @Override + public boolean isString() { + return true; + } + + @Override + public String getString() { + return string; + } + + @Override + public Function, String> getDynamics() { + return new ConstantProvider<>(string); + } + + } + + } +} diff --git a/plugin/src/main/java/de/epiceric/shopchest/config/hologram/line/FormattedLine.java b/plugin/src/main/java/de/epiceric/shopchest/config/hologram/line/FormattedLine.java new file mode 100644 index 0000000..198cefe --- /dev/null +++ b/plugin/src/main/java/de/epiceric/shopchest/config/hologram/line/FormattedLine.java @@ -0,0 +1,85 @@ +package de.epiceric.shopchest.config.hologram.line; + +import de.epiceric.shopchest.config.hologram.calculation.Calculation; +import de.epiceric.shopchest.config.hologram.condition.Condition; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public class FormattedLine

{ + + private final List, String>> tokens; + + public FormattedLine(List, String>> tokens) { + this.tokens = tokens; + } + + public String get(Map values) { + final StringBuilder output = new StringBuilder(); + for (Function, String> token : tokens) { + output.append(token.apply(values)); + } + return output.toString(); + } + + public final static class MapToString

implements Function, String> { + + private final P key; + + public MapToString(P key) { + this.key = key; + } + + @Override + public String apply(Map values) { + final Object o = values.get(key); + return o == null ? "" : o.toString(); + } + } + + public final static class ProviderToString

implements Function, String> { + + private final Function, ?> provider; + + public ProviderToString(Function, ?> provider) { + this.provider = provider; + } + + @Override + public String apply(Map pObjectMap) { + return String.valueOf(provider); + } + } + + public final static class ConditionToString

implements Function, String> { + + private final Condition> condition; + + public ConditionToString(Condition> condition) { + this.condition = condition; + } + + @Override + public String apply(Map values) { + return String.valueOf(condition.test(values)); + } + + } + + public final static class CalculationToString

implements Function, String> { + + private final Calculation> calculation; + + public CalculationToString(Calculation> calculation) { + this.calculation = calculation; + } + + @Override + public String apply(Map values) { + return String.valueOf(calculation.apply(values)); + } + + } + +} diff --git a/plugin/src/main/java/de/epiceric/shopchest/config/hologram/parser/Token.java b/plugin/src/main/java/de/epiceric/shopchest/config/hologram/parser/Token.java index 7121edd..5fd44b1 100644 --- a/plugin/src/main/java/de/epiceric/shopchest/config/hologram/parser/Token.java +++ b/plugin/src/main/java/de/epiceric/shopchest/config/hologram/parser/Token.java @@ -19,6 +19,7 @@ public class Token { public final static TokenType>> NODE = new TokenType<>("Node"); public final static TokenType> CALCULATION = new TokenType<>("Calculation"); public final static TokenType> CONDITION = new TokenType<>("Condition"); + private final TokenType type; private final T value;