diff --git a/src/main/java/info/itsthesky/disky/api/ReflectionUtils.java b/src/main/java/info/itsthesky/disky/api/ReflectionUtils.java index 8ea2b69..6fc1bb2 100644 --- a/src/main/java/info/itsthesky/disky/api/ReflectionUtils.java +++ b/src/main/java/info/itsthesky/disky/api/ReflectionUtils.java @@ -285,4 +285,8 @@ public static void removeElement(String clazz, String... fields) throws Exceptio return null; } } + + public static Class getGenericType(Field field) { + return (Class) ((java.lang.reflect.ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; + } } \ No newline at end of file diff --git a/src/main/java/info/itsthesky/disky/api/datastruct/BaseDataStructElement.java b/src/main/java/info/itsthesky/disky/api/datastruct/BaseDataStructElement.java index bc3010a..d413d85 100644 --- a/src/main/java/info/itsthesky/disky/api/datastruct/BaseDataStructElement.java +++ b/src/main/java/info/itsthesky/disky/api/datastruct/BaseDataStructElement.java @@ -1,6 +1,7 @@ package info.itsthesky.disky.api.datastruct; import ch.njol.skript.Skript; +import ch.njol.skript.config.Node; import ch.njol.skript.config.SectionNode; import ch.njol.skript.expressions.base.SectionExpression; import ch.njol.skript.lang.Expression; @@ -16,48 +17,31 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public abstract class BaseDataStructElement> extends SectionExpression { - protected EntryContainer container; + protected DataStructureFactory.DataStructureParseResult parseResult; @Override - public boolean init(Expression[] expressions, int pattern, Kleenean delayed, + public boolean init(Expression[] givenExprs, int pattern, Kleenean delayed, SkriptParser.ParseResult result, @Nullable SectionNode node, @Nullable List triggerItems) { - final var validator = DataStructureFactory.createValidator(getDataStructClass()); - container = validator.validate(node); - - final var presentNodes = new ArrayList(); - if (container != null) { - for (final var entryData : validator.getEntryData()) { - if (container.hasEntry(entryData.getKey())) - presentNodes.add(entryData.getKey()); - } - } - - final var errorMessage = DataStructureFactory.preValidate(getDataStructClass(), presentNodes, container); - if (errorMessage != null) { - // DiSky.debug("--- Error while validating data structure: " + errorMessage); - // Skript.error(errorMessage); - // why skript? why don't you want my error message? ;-; - DiSkyRuntimeHandler.error(new IllegalStateException(errorMessage), node); - return false; - } - - return container != null; + this.parseResult = DataStructureFactory.initDataStructure(getDataStructClass(), node); + return this.parseResult != null; } @Override protected T @Nullable [] get(Event event) { - if (container == null) + if (parseResult == null) return null; try { - final var result = DataStructureFactory.createDataStructure(getDataStructClass(), container, event, null); + final var result = DataStructureFactory.createDataStructure(getDataStructClass(), parseResult, event, null); return (T[]) new Object[] {result}; - } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { + } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } } diff --git a/src/main/java/info/itsthesky/disky/api/datastruct/ChainDataStructElement.java b/src/main/java/info/itsthesky/disky/api/datastruct/ChainDataStructElement.java index e5c9af8..2507269 100644 --- a/src/main/java/info/itsthesky/disky/api/datastruct/ChainDataStructElement.java +++ b/src/main/java/info/itsthesky/disky/api/datastruct/ChainDataStructElement.java @@ -15,27 +15,24 @@ import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.entry.EntryContainer; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public abstract class ChainDataStructElement> extends SectionExpression implements IAsyncGettableExpression { - protected EntryContainer container; + protected DataStructureFactory.DataStructureParseResult parseResult; protected Node node; @Override - public boolean init(Expression[] expressions, - int pattern, - Kleenean delayed, - SkriptParser.ParseResult result, - @Nullable SectionNode node, + public boolean init(Expression[] givenExprs, int pattern, Kleenean delayed, + SkriptParser.ParseResult result, @Nullable SectionNode node, @Nullable List triggerItems) { this.node = getParser().getNode(); - - final var validator = DataStructureFactory.createValidator(getDataStructClass()); - container = validator.validate(node); - - return container != null; + this.parseResult = DataStructureFactory.initDataStructure(getDataStructClass(), node); + return this.parseResult != null; } @Override @@ -46,17 +43,15 @@ public boolean init(Expression[] expressions, @Override public F[] getAsync(Event event) { - if (container == null) - return null; final var original = getOriginalInstance(event); if (original == null) return null; try { - final var result = DataStructureFactory.createDataStructure(getDataStructClass(), container, event, original); - return (F[]) new Object[] {applyChanges(event, result)}; - } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | java.lang.reflect.InvocationTargetException e) { + final var result = DataStructureFactory.createDataStructure(getDataStructClass(), parseResult, event, null); + return (F[]) new Object[] {applyChanges(event, (T) result)}; + } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } } diff --git a/src/main/java/info/itsthesky/disky/api/datastruct/DataStructureEntry.java b/src/main/java/info/itsthesky/disky/api/datastruct/DataStructureEntry.java index e19f2a8..11e4984 100644 --- a/src/main/java/info/itsthesky/disky/api/datastruct/DataStructureEntry.java +++ b/src/main/java/info/itsthesky/disky/api/datastruct/DataStructureEntry.java @@ -1,5 +1,8 @@ package info.itsthesky.disky.api.datastruct; +import info.itsthesky.disky.api.datastruct.base.DataStruct; + +import javax.annotation.Nullable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -18,6 +21,14 @@ */ boolean optional() default true; + /** + * Get the sub structure type, to use when parsing an array + * of structure for this field. Naturally, those structures, once + * built, must return the same type as the field type. + * @return The sub structure type + */ + Class subStructureType() default DataStruct.class; + //region Documentation /** diff --git a/src/main/java/info/itsthesky/disky/api/datastruct/DataStructureFactory.java b/src/main/java/info/itsthesky/disky/api/datastruct/DataStructureFactory.java index b6e3432..09226fd 100644 --- a/src/main/java/info/itsthesky/disky/api/datastruct/DataStructureFactory.java +++ b/src/main/java/info/itsthesky/disky/api/datastruct/DataStructureFactory.java @@ -1,127 +1,458 @@ package info.itsthesky.disky.api.datastruct; +import ch.njol.skript.config.Node; +import ch.njol.skript.config.SectionNode; import ch.njol.skript.lang.Expression; import ch.njol.skript.registrations.Classes; import info.itsthesky.disky.DiSky; +import info.itsthesky.disky.api.ReflectionUtils; import info.itsthesky.disky.api.datastruct.base.BasicDS; import info.itsthesky.disky.api.datastruct.base.ChainDS; import info.itsthesky.disky.api.datastruct.base.DataStruct; -import info.itsthesky.disky.api.datastruct.base.EditableDataStructure; import info.itsthesky.disky.api.skript.BetterExpressionEntryData; import info.itsthesky.disky.core.SkriptUtils; +import info.itsthesky.disky.elements.sections.handler.DiSkyRuntimeHandler; import org.bukkit.event.Event; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.skriptlang.skript.lang.entry.EntryContainer; import org.skriptlang.skript.lang.entry.EntryValidator; -import org.skriptlang.skript.lang.entry.util.ExpressionEntryData; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import java.util.*; +/** + * Factory class responsible for creating and managing data structures in the DiSky system. + * This factory handles the parsing, validation, and instantiation of data structures + * defined through annotations and configuration nodes. + * + * The workflow consists of three main phases: + * 1. Initialization - Parse and validate the structure definition ({@link #initDataStructure}) + * 2. Validation - Create and configure the entry validator ({@link #createValidator}) + * 3. Creation - Build the actual data structure instance ({@link #createDataStructure}) + * + * @see DataStructure + * @see DataStructureEntry + * @see BasicDS + * @see ChainDS + */ public final class DataStructureFactory { - public static EntryValidator createValidator(@NotNull Class structClass) { + /** + * Represents the parsed result of a data structure configuration, + * containing all necessary information to create instances. + */ + public static record DataStructureParseResult( + @NotNull EntryValidator validator, + @NotNull EntryContainer container, + @NotNull Map>> expressions, + @NotNull Map> subStructures + ) {} + + /** + * Initializes a data structure by parsing and validating its configuration. + * + * @param dataStructClass The class of the data structure to initialize + * @param node The section node containing the structure's configuration + * @return A parse result containing all necessary information, or null if validation fails + */ + public static DataStructureParseResult initDataStructure( + @NotNull Class dataStructClass, + @NotNull SectionNode node + ) { + final var validator = createValidator(dataStructClass); + final var container = validator.validate(node); + if (container == null) return null; + + final var keysToFields = mapKeysToFields(dataStructClass); + DiSky.debug("=========== Starting parsing of data structure " + dataStructClass.getName() + " ==========="); + logContainerInfo(container, validator); + + final var presentNodes = collectPresentNodes(validator, container); + final var expressions = parseExpressions(container, presentNodes); + final var subStructures = parseSubStructures(dataStructClass, container, keysToFields); + + final var errorMessage = preValidate(dataStructClass, presentNodes); + if (errorMessage != null) { + DiSkyRuntimeHandler.error(new IllegalStateException(errorMessage), node); + return null; + } + return new DataStructureParseResult(validator, container, expressions, subStructures); + } + + /** + * Creates a validator for the given data structure class. + * + * @param structClass The class to create a validator for + * @return A configured entry validator + */ + public static EntryValidator createValidator(@NotNull Class structClass) { final var validator = EntryValidator.builder(); - final var allNodes = new HashMap(); + DiSky.debug("=========== Starting validation of data structure " + structClass.getName() + " ==========="); for (final var field : structClass.getDeclaredFields()) { final var entry = field.getAnnotation(DataStructureEntry.class); - if (entry == null) - continue; + if (entry == null) continue; - final var key = entry.value(); - final Class type = field.getType(); + processFieldValidator(validator, field, entry, structClass); + } - Object defaultValue = null; - try { - var instance = structClass.getDeclaredConstructor().newInstance(); - field.setAccessible(true); - defaultValue = field.get(instance); - } catch (IllegalAccessException | NoSuchMethodException | InstantiationException | InvocationTargetException e) { - DiSky.debug("Cannot get default value of field " + field.getName() + " in class " + structClass.getName() + "! Are you sure a public and empty constructor is present?"); - } + validator.unexpectedNodeTester(node -> { + DiSky.debug("UNEXPECTED NODE: " + node.getKey()); + return false; + }); - // get the natural name for the type - final var typeName = Classes.getExactClassName(type); - allNodes.put(key.split(":")[0], typeName); + DiSky.debug("=========== End of validation of data structure " + structClass.getName() + " ==========="); + return validator.build(); + } + + /** + * Creates an instance of the data structure with the parsed configuration. + * + * @param structClass The class of the data structure to create + * @param parseResult The parsed configuration data + * @param event The event context + * @param chainInstance The chain instance for chain data structures + * @return The created data structure instance + * @throws ReflectiveOperationException if instance creation fails + */ + public static Object createDataStructure( + @NotNull Class structClass, + @NotNull DataStructureParseResult parseResult, + @NotNull Event event, + @Nullable Object chainInstance + ) throws ReflectiveOperationException { + DiSky.debug("################## Starting creation of data structure " + structClass.getName() + " ##################"); + final var instance = structClass.getConstructor().newInstance(); + final var container = parseResult.container(); + + final var unhandledNodes = groupUnhandledNodes(container); + processFields(structClass, instance, parseResult, event, unhandledNodes); + DiSky.debug("################## End of creation of data structure " + structClass.getName() + " ##################"); + return finalizeInstance(instance, chainInstance); + } + + /** + * Maps field keys to their corresponding field names. + */ + private static Map mapKeysToFields(@NotNull Class dataStructClass) { + final var keysToFields = new HashMap(); + for (final var field : dataStructClass.getDeclaredFields()) { + final var entry = field.getAnnotation(DataStructureEntry.class); + if (entry == null) continue; + keysToFields.put(entry.value(), field.getName()); + } + return keysToFields; + } + + /** + * Processes a field for validation configuration. + */ + private static void processFieldValidator( + EntryValidator.EntryValidatorBuilder validator, + Field field, + DataStructureEntry entry, + Class structClass + ) { + final var key = entry.value(); + final Class type = field.getType(); + + Object defaultValue = getDefaultValue(field, structClass); + + if (!isList(type) || entry.subStructureType() == DataStruct.class) { + final var typeName = Classes.getExactClassName(type); validator.addEntryData(new BetterExpressionEntryData( key, SkriptUtils.convertToExpressions(defaultValue), entry.optional(), type )); + DiSky.debug("- Added entry data for key " + key + " with type " + typeName); + } else { + DiSky.debug("- Added sub-structure for key " + key); + } + } + + /** + * Gets the default value for a field. + */ + private static Object getDefaultValue(Field field, Class structClass) { + try { + var instance = structClass.getDeclaredConstructor().newInstance(); + field.setAccessible(true); + return field.get(instance); + } catch (ReflectiveOperationException e) { + DiSky.debug("Cannot get default value of field " + field.getName() + + " in class " + structClass.getName() + + "! Are you sure a public and empty constructor is present?"); + return null; } + } - // final changes - return validator.unexpectedNodeTester(node -> false).build(); + /** + * Logs debug information about the container and validator. + */ + private static void logContainerInfo(EntryContainer container, EntryValidator validator) { + DiSky.debug("- Unhandled nodes (" + container.getUnhandledNodes().size() + "): " + + container.getUnhandledNodes().stream().map(Node::getKey).toList()); + DiSky.debug("- Found entry data (" + validator.getEntryData().size() + "): " + + validator.getEntryData().stream().map(data -> data.getKey()).toList()); } - public static > F createDataStructure(@NotNull Class structClass, - @NotNull EntryContainer container, - @NotNull Event event, - @Nullable F chainInstance) - throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { - final var instance = structClass.getConstructor().newInstance(); + /** + * Collects all present nodes from the validator and container. + */ + private static List collectPresentNodes(EntryValidator validator, EntryContainer container) { + final var presentNodes = new ArrayList(); + for (final var entryData : validator.getEntryData()) { + if (container.hasEntry(entryData.getKey())) + presentNodes.add(entryData.getKey().split(":")[0]); + } + for (final var node : container.getUnhandledNodes()) + presentNodes.add(node.getKey().split(":")[0]); + + DiSky.debug("Present nodes: " + presentNodes); + return presentNodes; + } + + /** + * Parses expressions from the container for given nodes. + */ + private static Map>> parseExpressions( + EntryContainer container, + List presentNodes + ) { + final var expressions = new HashMap>>(); + for (final var nodeKey : presentNodes) { + var exprs = container.getOptional(nodeKey, List.class, true); + if (exprs == null) exprs = new ArrayList<>(); + DiSky.debug("-> Adding expression for node " + nodeKey + " with " + exprs.size() + " expressions"); + expressions.put(nodeKey, exprs); + } + return expressions; + } + + /** + * Parses sub-structures from unhandled nodes. + */ + private static Map> parseSubStructures( + Class dataStructClass, + EntryContainer container, + Map keysToFields + ) { + final var subStructures = new HashMap>(); + DiSky.debug("-> Starting sub-structures handling ..."); + for (final var subNode : container.getUnhandledNodes()) { + if (!(subNode instanceof SectionNode subSectionNode)) { + DiSky.debug("-> Unhandled node is not a section node (" + subNode.getKey() + "), skipping ..."); + continue; + } + + processSubStructure(dataStructClass, subNode, keysToFields, subStructures, subSectionNode); + } + + return subStructures; + } + + /** + * Processes a single sub-structure node. + */ + private static void processSubStructure( + Class dataStructClass, + Node subNode, + Map keysToFields, + Map> subStructures, + SectionNode subSectionNode + ) { + final var nodeKey = subNode.getKey().split(":")[0]; + DiSky.debug("Now processing sub-structure for key " + nodeKey + " ..."); + + final var entryField = ReflectionUtils.getField(dataStructClass, keysToFields.get(nodeKey)); + if (entryField == null) { + DiSky.debug("-> Cannot find field for key " + nodeKey + " in class " + dataStructClass.getName()); + return; + } + + final var entryType = entryField.getDeclaredAnnotation(DataStructureEntry.class).subStructureType(); + final var subResult = initDataStructure(entryType, subSectionNode); + + if (subResult != null) + subStructures.computeIfAbsent(nodeKey, k -> new ArrayList<>()).add(subResult); + else + DiSky.debug("Cannot init sub-structure for key " + nodeKey + " in class " + dataStructClass.getName()); + + DiSky.debug("--> Adding sub-structure for key " + nodeKey + " with " + subResult); + } + + /** + * Groups unhandled nodes by their base key. + */ + private static Map> groupUnhandledNodes(EntryContainer container) { + final var unhandledNodes = new HashMap>(); + for (final var node : container.getUnhandledNodes()) + unhandledNodes.computeIfAbsent(node.getKey().split(":")[0], k -> new ArrayList<>()).add(node); + return unhandledNodes; + } + + /** + * Processes all fields of the data structure instance. + */ + private static void processFields( + Class structClass, + Object instance, + DataStructureParseResult parseResult, + Event event, + Map> unhandledNodes + ) throws IllegalAccessException { for (final var field : structClass.getDeclaredFields()) { final var entry = field.getAnnotation(DataStructureEntry.class); - if (entry == null) + if (entry == null) continue; + + processField(field, entry, instance, parseResult, event, unhandledNodes); + } + } + + /** + * Processes a single field during instance creation. + */ + private static void processField( + Field field, + DataStructureEntry entry, + Object instance, + DataStructureParseResult parseResult, + Event event, + Map> unhandledNodes + ) throws IllegalAccessException { + final var key = entry.value(); + final var type = field.getType(); + final var isList = isList(type); + final var isDataStructs = entry.subStructureType() != DataStruct.class; + + field.setAccessible(true); + + if (isDataStructs) { + processDataStructField(field, entry, instance, key, unhandledNodes, parseResult, event); + } else { + processSimpleField(field, instance, key, isList, parseResult, event); + } + } + + /** + * Processes a field that contains a data structure. + */ + private static void processDataStructField( + Field field, + DataStructureEntry entry, + Object instance, + String key, + Map> unhandledNodes, + DataStructureParseResult parseResult, + Event event + ) throws IllegalAccessException { + final var subNodes = unhandledNodes.get(key); + if (subNodes == null) return; + + final var list = new ArrayList<>(); + for (int i = 0; i < subNodes.size(); i++) { + final var subNode = subNodes.get(i); + final var targetDataStructureType = entry.subStructureType(); + final var nodeKey = subNode.getKey().split(":")[0]; + final var subResultList = parseResult.subStructures().get(nodeKey); + + if (subResultList == null || subResultList.isEmpty()) { + DiSky.debug("Cannot find sub-structure for key " + nodeKey + " in class " + instance.getClass().getName()); continue; + } - final var key = entry.value(); - final var type = field.getType(); - final var isList = List.class.isAssignableFrom(type) || type.isArray(); + DiSky.debug("Creating sub-structure for key " + key + " with " + subResultList.size() + " sub-structures ..."); + final var subResultItem = subResultList.get(i); + try { + final var subInstance = createDataStructure(targetDataStructureType, subResultItem, event, null); + if (subInstance != null) + list.add(subInstance); + } catch (ReflectiveOperationException e) { + DiSky.debug("Failed to create sub-structure: " + e.getMessage()); + } + } + field.set(instance, list); + } - field.setAccessible(true); + /** + * Processes a field that contains simple values (non-data structures). + */ + private static void processSimpleField( + Field field, + Object instance, + String key, + boolean isList, + DataStructureParseResult parseResult, + Event event + ) throws IllegalAccessException { + final var expressions = parseResult.expressions().getOrDefault(key, new ArrayList<>()); - var list = container.getOptional(key, List.class, true); - if (list == null) - list = new ArrayList<>(); - - // be sure it's a list, if it's not, check if the list contain only one expr - if (!isList && list.size() == 1) { - final var expr = (Expression) list.get(0); - if (expr != null) - field.set(instance, expr.getSingle(event)); - } else if (isList) { - final var parsedList = new ArrayList<>(); - for (final var expr : list) - if (expr != null) - parsedList.add(((Expression) expr).getSingle(event)); - - field.set(instance, parsedList); + if (!isList && expressions.size() == 1) { + final var expr = (Expression) expressions.get(0); + if (expr != null) { + field.set(instance, expr.getSingle(event)); + } + } else if (isList) { + final var parsedList = new ArrayList<>(); + for (final var expr : expressions) { + if (expr != null) { + final var value = expr.getSingle(event); + if (value != null) { + parsedList.add(value); + } + } } + field.set(instance, parsedList); } + } + + /** + * Determines if a type represents a list or array. + */ + private static boolean isList(Class type) { + return List.class.isAssignableFrom(type) || type.isArray(); + } + /** + * Finalizes the instance creation by building or editing based on its type. + */ + private static Object finalizeInstance(Object instance, @Nullable Object chainInstance) { if (instance instanceof BasicDS) { - return ((BasicDS) instance).build(); + return ((BasicDS) instance).build(); } else if (instance instanceof ChainDS) { - if (chainInstance == null) + if (chainInstance == null) { throw new IllegalArgumentException("Cannot edit a chain data structure without a chain instance!"); - - return ((ChainDS) instance).edit(chainInstance); + } + return ((ChainDS) instance).edit(chainInstance); } - - throw new IllegalArgumentException("The data structure class " + structClass.getName() + " must implement either BasicDS or ChainDS interface!"); + throw new IllegalArgumentException( + "The data structure class " + instance.getClass().getName() + + " must implement either BasicDS or ChainDS interface!"); } - public static > @Nullable String preValidate(@NotNull Class structClass, - @NotNull List presentNodes, - @NotNull EntryContainer container) { - for (final var nodeKey : presentNodes) - container.getOptional(nodeKey, List.class, true); - - final T instance; + /** + * Pre-validates a data structure class with the given nodes. + * + * @param structClass The class to validate + * @param presentNodes The list of present node keys + * @return An error message if validation fails, null otherwise + */ + public static > @Nullable String preValidate( + @NotNull Class structClass, + @NotNull List presentNodes + ) { try { - instance = structClass.getConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { - throw new RuntimeException(e); + final T instance = structClass.getConstructor().newInstance(); + return instance.preValidate(presentNodes); + } catch (ReflectiveOperationException e) { + return "Failed to create instance for validation: " + e.getMessage(); } - return instance.preValidate(presentNodes); } -} +} \ No newline at end of file diff --git a/src/main/java/info/itsthesky/disky/api/datastruct/EditDataStructElement.java b/src/main/java/info/itsthesky/disky/api/datastruct/EditDataStructElement.java index ad0fc72..e7dcde5 100644 --- a/src/main/java/info/itsthesky/disky/api/datastruct/EditDataStructElement.java +++ b/src/main/java/info/itsthesky/disky/api/datastruct/EditDataStructElement.java @@ -13,11 +13,14 @@ import org.skriptlang.skript.lang.entry.EntryContainer; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public abstract class EditDataStructElement> extends Section { protected EntryContainer container; + protected Map>> expressions; @Override public boolean init(Expression[] expressions, @@ -35,7 +38,11 @@ public boolean init(Expression[] expressions, } } - final var errorMessage = DataStructureFactory.preValidate(getDataStructClass(), presentNodes, container); + this.expressions = new HashMap<>(); + for (final var nodeKey : presentNodes) + container.getOptional(nodeKey, List.class, true); + + final var errorMessage = DataStructureFactory.preValidate(getDataStructClass(), presentNodes); if (errorMessage != null) { // DiSky.debug("--- Error while validating data structure: " + errorMessage); // Skript.error(errorMessage); diff --git a/src/main/java/info/itsthesky/disky/api/skript/BetterExpressionEntryData.java b/src/main/java/info/itsthesky/disky/api/skript/BetterExpressionEntryData.java index 3370731..23168a6 100644 --- a/src/main/java/info/itsthesky/disky/api/skript/BetterExpressionEntryData.java +++ b/src/main/java/info/itsthesky/disky/api/skript/BetterExpressionEntryData.java @@ -75,6 +75,22 @@ public List> getValue(@NotNull Node node) { } } + public void preValidate(Node node) { + if (node instanceof final SectionNode sectionNode) { + for (Node subNode : sectionNode) { + final String value = subNode.getKey(); + parseExpression(value); + } + } else if (node instanceof final SimpleNode simpleNode) { + final String key = simpleNode.getKey(); + if (key == null) + return; + + final String value = ScriptLoader.replaceOptions(key).substring(getKey().length() + EntryValidator.EntryValidatorBuilder.DEFAULT_ENTRY_SEPARATOR.length()); + parseExpression(value); + } + } + private Expression parseExpression(String value) { Expression expression; try (ParseLogHandler log = new ParseLogHandler().start()) { @@ -93,7 +109,11 @@ private Expression parseExpression(String value) { @Override public boolean canCreateWith(@NotNull Node node) { if (node instanceof SectionNode) { - return true; + String key = node.getKey(); + if (key == null) + return false; + key = ScriptLoader.replaceOptions(key); + return getKey().equalsIgnoreCase(key); } else if (node instanceof SimpleNode) { String key = node.getKey(); if (key == null) diff --git a/src/main/java/info/itsthesky/disky/elements/datastructs/structures/EmbedFieldStructure.java b/src/main/java/info/itsthesky/disky/elements/datastructs/structures/EmbedFieldStructure.java index 414a1c4..9e60842 100644 --- a/src/main/java/info/itsthesky/disky/elements/datastructs/structures/EmbedFieldStructure.java +++ b/src/main/java/info/itsthesky/disky/elements/datastructs/structures/EmbedFieldStructure.java @@ -6,14 +6,14 @@ public class EmbedFieldStructure implements BasicDS { - @DataStructureEntry(value = "name") + @DataStructureEntry(value = "name", optional = false) public String name; - @DataStructureEntry(value = "value") + @DataStructureEntry(value = "value", optional = false) public String value; - @DataStructureEntry(value = "inline", optional = true) - public boolean inline = false; + @DataStructureEntry(value = "inline") + public Boolean inline = false; @Override public MessageEmbed.Field build() { diff --git a/src/main/java/info/itsthesky/disky/elements/datastructs/structures/EmbedStructure.java b/src/main/java/info/itsthesky/disky/elements/datastructs/structures/EmbedStructure.java index f2dddd6..40945b8 100644 --- a/src/main/java/info/itsthesky/disky/elements/datastructs/structures/EmbedStructure.java +++ b/src/main/java/info/itsthesky/disky/elements/datastructs/structures/EmbedStructure.java @@ -8,6 +8,7 @@ import info.itsthesky.disky.api.datastruct.base.BasicDS; import info.itsthesky.disky.core.SkriptUtils; import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; import java.time.Instant; import java.util.List; @@ -15,39 +16,39 @@ @DataStructure(clazz = EmbedBuilder.class) public class EmbedStructure implements BasicDS { - @DataStructureEntry(value = "title", optional = true) + @DataStructureEntry(value = "title") public String title; - @DataStructureEntry(value = "url", optional = true) + @DataStructureEntry(value = "url") public String url; - @DataStructureEntry(value = "description", optional = true) + @DataStructureEntry(value = "description") public String description; - @DataStructureEntry(value = "footer", optional = true) + @DataStructureEntry(value = "footer") public String footer; - @DataStructureEntry(value = "footer icon", optional = true) + @DataStructureEntry(value = "footer icon") public String footerIcon; - @DataStructureEntry(value = "thumbnail", optional = true) + @DataStructureEntry(value = "thumbnail") public String thumbnail; - @DataStructureEntry(value = "image", optional = true) + @DataStructureEntry(value = "image") public String image; - @DataStructureEntry(value = "author", optional = true) + @DataStructureEntry(value = "author") public String author; - @DataStructureEntry(value = "author icon", optional = true) + @DataStructureEntry(value = "author icon") public String authorIcon; - @DataStructureEntry(value = "author url", optional = true) + @DataStructureEntry(value = "author url") public String authorUrl; - @DataStructureEntry(value = "timestamp", optional = true) + @DataStructureEntry(value = "timestamp") public Date timestamp; - @DataStructureEntry(value = "color", optional = true) + @DataStructureEntry(value = "color") public Color color = SkriptColor.YELLOW; - @DataStructureEntry(value = "field", optional = true) - public List fields; + @DataStructureEntry(value = "field", subStructureType = EmbedFieldStructure.class) + public List fields; @Override public EmbedBuilder build() { @@ -86,8 +87,8 @@ public EmbedBuilder build() { if (timestamp != null) builder.setTimestamp(Instant.ofEpochMilli(timestamp.getTime())); if (fields != null) { - for (EmbedFieldStructure field : fields) { - builder.addField(field.build()); + for (MessageEmbed.Field field : fields) { + builder.addField(field); } } diff --git a/src/main/java/info/itsthesky/disky/elements/events/ExprEventValues.java b/src/main/java/info/itsthesky/disky/elements/events/ExprEventValues.java index bd665a6..f600cbf 100644 --- a/src/main/java/info/itsthesky/disky/elements/events/ExprEventValues.java +++ b/src/main/java/info/itsthesky/disky/elements/events/ExprEventValues.java @@ -37,6 +37,8 @@ public static void registerEventValue(Class event, EventValue @NotNull [] exprs, final int matchedPattern, final @NotNull Kleenean isDelayed, final @NotNull ParseResult parser) { + if (ParserInstance.get().getCurrentEvents() == null) + return false; final String name = parser.expr.split("event-")[1]; final List> values = eventValues.getOrDefault(ParserInstance.get().getCurrentEvents()[0], new ArrayList<>()); if (values.isEmpty() || values.stream().noneMatch(v -> v.getName().equalsIgnoreCase(name))) {