From 1a5c7a684a9d7193cdab117e7891b033f8b0814b Mon Sep 17 00:00:00 2001 From: TPGamesNL Date: Thu, 21 Jan 2021 18:56:10 +0100 Subject: [PATCH] Fix https://github.com/TPGamesNL/skript-reflect/issues/31 Added some javadocs Some code cleanup Some internal changes --- .../com/btk5h/skriptmirror/Descriptor.java | 87 +++++-- .../skriptmirror/ImportNotFoundException.java | 20 ++ .../btk5h/skriptmirror/ScriptLoaderState.java | 16 +- .../skript/CondExpressionStatement.java | 6 +- .../skriptmirror/skript/CondParseLater.java | 6 +- .../skript/EffExpressionStatement.java | 1 - .../com/btk5h/skriptmirror/skript/Types.java | 3 +- .../skript/custom/ExprParseMark.java | 2 +- .../skript/custom/ExprParseRegex.java | 14 +- .../skript/custom/PreloadListener.java | 7 - .../skript/reflect/ExprJavaCall.java | 232 ++++++++++-------- .../skript/reflect/sections/CondSection.java | 8 +- .../com/btk5h/skriptmirror/util/JavaUtil.java | 17 +- .../skriptmirror/util/SkriptMirrorUtil.java | 6 + .../skriptmirror/util/StringSimilarity.java | 9 +- 15 files changed, 263 insertions(+), 171 deletions(-) create mode 100644 src/main/java/com/btk5h/skriptmirror/ImportNotFoundException.java diff --git a/src/main/java/com/btk5h/skriptmirror/Descriptor.java b/src/main/java/com/btk5h/skriptmirror/Descriptor.java index abced17..e26b5d4 100644 --- a/src/main/java/com/btk5h/skriptmirror/Descriptor.java +++ b/src/main/java/com/btk5h/skriptmirror/Descriptor.java @@ -9,9 +9,30 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * A Descriptor is all the info that can be provided in a script about a method. + * This consists of: + * + */ public final class Descriptor { + + /** + * A regex string for a list of array symbols, e.g. {@code [][][]}. + */ private static final String PACKAGE_ARRAY = SkriptMirrorUtil.PACKAGE + "(?:\\[])*"; + + /** + * A regex {@link Pattern} for a single array symbol, matches only {@code []}. + */ private static final Pattern PACKAGE_ARRAY_SINGLE = Pattern.compile("\\[]"); + + /** + * A regex {@link Pattern} for a {@link Descriptor}. + */ private static final Pattern DESCRIPTOR = Pattern.compile("" + "(?:\\[(" + SkriptMirrorUtil.PACKAGE + ")])?" + @@ -61,37 +82,37 @@ public String toString() { } public String toString(boolean isStatic) { - return String.format("%s" + (isStatic ? "." : "#") + "%s", - javaClass == null ? "(unspecified)" : SkriptMirrorUtil.getDebugName(javaClass), - name); + return javaClass == null ? "(unspecified)" : SkriptMirrorUtil.getDebugName(javaClass) + + (isStatic ? "." : "#") + + name; } + /** + * Returns a new descriptor with a new {@link #javaClass}. + * If this Descriptors {@link #javaClass} is not null, it will instead return itself. + */ public Descriptor orDefaultClass(Class cls) { - if (getJavaClass() != null) { + if (javaClass != null) { return this; } - return new Descriptor(cls, getName(), getParameterTypes()); + return new Descriptor(cls, name, parameterTypes); } - public static Descriptor parse(String desc, File script) throws ClassNotFoundException { + /** + * Parses the given {@link String} as a {@link Descriptor}. The script parameter is to get the imports. + */ + public static Descriptor parse(String desc, File script) throws ImportNotFoundException { Matcher m = DESCRIPTOR.matcher(desc); if (m.matches()) { String cls = m.group(1); - String name = m.group(2); - String args = m.group(3); - - Class javaClass = null; - Class[] parameterTypes = null; + Class javaClass = cls == null ? null : lookupClass(script, cls); - if (cls != null) { - javaClass = lookupClass(script, cls); - } + String name = m.group(2); - if (args != null) { - parameterTypes = parseParams(args, script); - } + String args = m.group(3); + Class[] parameterTypes = args == null ? null : parseParams(args, script); return new Descriptor(javaClass, name, parameterTypes); } @@ -99,25 +120,35 @@ public static Descriptor parse(String desc, File script) throws ClassNotFoundExc return null; } - private static Class[] parseParams(String args, File script) throws ClassNotFoundException { + /** + * Parses a list of imported names, returning a class array containing the classes in the given string. + */ + private static Class[] parseParams(String args, File script) throws ImportNotFoundException { String[] rawClasses = args.split(","); + Class[] parsedClasses = new Class[rawClasses.length]; + for (int i = 0; i < rawClasses.length; i++) { String userType = rawClasses[i].trim(); + // Calculate array depth with regex Matcher arrayDepthMatcher = PACKAGE_ARRAY_SINGLE.matcher(userType); int arrayDepth = 0; while (arrayDepthMatcher.find()) { arrayDepth++; } - userType = userType.substring(0, userType.length() - (2 * arrayDepth)); - Class cls = JavaUtil.PRIMITIVE_CLASS_NAMES.get(userType); + // Remove array's square brackets + userType = userType.substring(0, userType.length() - (2 * arrayDepth)); - if (cls == null) { + Class cls; + if (JavaUtil.PRIMITIVE_CLASS_NAMES.containsKey(userType)) { + cls = JavaUtil.PRIMITIVE_CLASS_NAMES.get(userType); + } else { cls = lookupClass(script, userType); } + // Convert class to array class with calculated depth cls = JavaUtil.getArrayClass(cls, arrayDepth); parsedClasses[i] = cls; @@ -126,13 +157,15 @@ private static Class[] parseParams(String args, File script) throws ClassNotF return parsedClasses; } - private static Class lookupClass(File script, String userType) throws ClassNotFoundException { + /** + * Looks up a class from its imported name in the given file. + */ + private static Class lookupClass(File script, String userType) throws ImportNotFoundException { JavaType customImport = CustomImport.lookup(script, userType); + if (customImport == null) + throw new ImportNotFoundException(userType); - if (customImport != null) { - return customImport.getJavaClass(); - } else { - return LibraryLoader.getClassLoader().loadClass(userType); - } + return customImport.getJavaClass(); } + } diff --git a/src/main/java/com/btk5h/skriptmirror/ImportNotFoundException.java b/src/main/java/com/btk5h/skriptmirror/ImportNotFoundException.java new file mode 100644 index 0000000..0abfbae --- /dev/null +++ b/src/main/java/com/btk5h/skriptmirror/ImportNotFoundException.java @@ -0,0 +1,20 @@ +package com.btk5h.skriptmirror; + +/** + * Exception thrown when the user tries to use a certain class, but that class is not imported. + * Currently only used in the {@link Descriptor} class. + */ +public class ImportNotFoundException extends Exception { + + private final String userType; + + public ImportNotFoundException(String userType) { + super("Import not found: " + userType); + this.userType = userType; + } + + public String getUserType() { + return userType; + } + +} diff --git a/src/main/java/com/btk5h/skriptmirror/ScriptLoaderState.java b/src/main/java/com/btk5h/skriptmirror/ScriptLoaderState.java index ce6e69a..bad79b0 100644 --- a/src/main/java/com/btk5h/skriptmirror/ScriptLoaderState.java +++ b/src/main/java/com/btk5h/skriptmirror/ScriptLoaderState.java @@ -6,10 +6,10 @@ import org.bukkit.event.Event; public class ScriptLoaderState { - private Config currentScript; - private String currentEventName; - private Class[] currentEvents; - private Kleenean hasDelayBefore; + private final Config currentScript; + private final String currentEventName; + private final Class[] currentEvents; + private final Kleenean hasDelayBefore; private ScriptLoaderState(Config currentScript, String currentEventName, Class[] currentEvents, Kleenean hasDelayBefore) { @@ -27,10 +27,10 @@ public void applyToCurrentState() { public static ScriptLoaderState copyOfCurrentState() { return new ScriptLoaderState( - ScriptLoader.currentScript, - ScriptLoader.getCurrentEventName(), - ScriptLoader.getCurrentEvents(), - ScriptLoader.hasDelayBefore + ScriptLoader.currentScript, + ScriptLoader.getCurrentEventName(), + ScriptLoader.getCurrentEvents(), + ScriptLoader.hasDelayBefore ); } } diff --git a/src/main/java/com/btk5h/skriptmirror/skript/CondExpressionStatement.java b/src/main/java/com/btk5h/skriptmirror/skript/CondExpressionStatement.java index de6e07a..cfa4412 100644 --- a/src/main/java/com/btk5h/skriptmirror/skript/CondExpressionStatement.java +++ b/src/main/java/com/btk5h/skriptmirror/skript/CondExpressionStatement.java @@ -22,6 +22,7 @@ import java.util.concurrent.Executors; public class CondExpressionStatement extends Condition { + static { Skript.registerCondition(CondExpressionStatement.class, "[(1¦await)] %~javaobject%"); } @@ -69,15 +70,13 @@ public String toString(Event e, boolean debug) { return arg.toString(e, debug); } - @SuppressWarnings("unchecked") @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { arg = SkriptUtil.defendExpression(exprs[0]); - if (!(arg instanceof ExprJavaCall)) { + if (!(arg instanceof ExprJavaCall)) return false; - } isAsynchronous = (parseResult.mark & 1) == 1; isCondition = SkriptLogger.getNode() instanceof SectionNode; @@ -86,6 +85,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye Skript.error("Asynchronous java calls may not be used as conditions."); return false; } + if (isAsynchronous) ScriptLoader.hasDelayBefore = Kleenean.TRUE; diff --git a/src/main/java/com/btk5h/skriptmirror/skript/CondParseLater.java b/src/main/java/com/btk5h/skriptmirror/skript/CondParseLater.java index 89bb5a1..b14c947 100644 --- a/src/main/java/com/btk5h/skriptmirror/skript/CondParseLater.java +++ b/src/main/java/com/btk5h/skriptmirror/skript/CondParseLater.java @@ -64,9 +64,11 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye private Statement getParsedStatement() { if (parsedStatement == null) { + ScriptLoaderState previousState = ScriptLoaderState.copyOfCurrentState(); scriptLoaderState.applyToCurrentState(); - parsedStatement = Statement.parse(statement, - String.format("Could not parse condition/effect at runtime: %s", statement)); + parsedStatement = Statement.parse(statement, "Could not parse condition/effect at runtime: " + + statement); + previousState.applyToCurrentState(); if (parsedStatement == null) { return null; diff --git a/src/main/java/com/btk5h/skriptmirror/skript/EffExpressionStatement.java b/src/main/java/com/btk5h/skriptmirror/skript/EffExpressionStatement.java index 7b32fb1..92880e1 100644 --- a/src/main/java/com/btk5h/skriptmirror/skript/EffExpressionStatement.java +++ b/src/main/java/com/btk5h/skriptmirror/skript/EffExpressionStatement.java @@ -37,7 +37,6 @@ protected void execute(Event e) { @Override protected TriggerItem walk(Event e) { if (isAsynchronous) { - // TODO how to implement async, Bukkit or just other Java (CondExpressionStatement#walk(Event)) Object localVariables = SkriptReflection.getLocals(e); CompletableFuture.runAsync(() -> { SkriptReflection.putLocals(localVariables, e); diff --git a/src/main/java/com/btk5h/skriptmirror/skript/Types.java b/src/main/java/com/btk5h/skriptmirror/skript/Types.java index 3e5e8de..082e351 100644 --- a/src/main/java/com/btk5h/skriptmirror/skript/Types.java +++ b/src/main/java/com/btk5h/skriptmirror/skript/Types.java @@ -123,8 +123,7 @@ protected boolean canBeInstantiated() { } })); - Converters.registerConverter(ClassInfo.class, JavaType.class, - ((Converter) c -> new JavaType(c.getC()))); + Converters.registerConverter(ClassInfo.class, JavaType.class, c -> new JavaType(c.getC())); Classes.registerClass(new ClassInfo<>(Null.class, "null") .parser(new Parser() { diff --git a/src/main/java/com/btk5h/skriptmirror/skript/custom/ExprParseMark.java b/src/main/java/com/btk5h/skriptmirror/skript/custom/ExprParseMark.java index f92bc41..023c2fc 100644 --- a/src/main/java/com/btk5h/skriptmirror/skript/custom/ExprParseMark.java +++ b/src/main/java/com/btk5h/skriptmirror/skript/custom/ExprParseMark.java @@ -40,7 +40,6 @@ public String toString(Event e, boolean debug) { return "parser mark"; } - @SuppressWarnings("unchecked") @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { @@ -52,6 +51,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye )) { Skript.error("The parser mark may only be used in custom syntax.", ErrorQuality.SEMANTIC_ERROR); + return false; } return true; } diff --git a/src/main/java/com/btk5h/skriptmirror/skript/custom/ExprParseRegex.java b/src/main/java/com/btk5h/skriptmirror/skript/custom/ExprParseRegex.java index c401c59..cdc6359 100644 --- a/src/main/java/com/btk5h/skriptmirror/skript/custom/ExprParseRegex.java +++ b/src/main/java/com/btk5h/skriptmirror/skript/custom/ExprParseRegex.java @@ -58,18 +58,18 @@ public String toString(Event e, boolean debug) { return "parser mark"; } - @SuppressWarnings("unchecked") @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { if (!ScriptLoader.isCurrentEvent( - EffectTriggerEvent.class, - ExpressionGetEvent.class, - ExpressionChangeEvent.class, - ConditionCheckEvent.class - )) { + EffectTriggerEvent.class, + ExpressionGetEvent.class, + ExpressionChangeEvent.class, + ConditionCheckEvent.class + )) { Skript.error("The parsed regular expression may only be used in custom syntax.", - ErrorQuality.SEMANTIC_ERROR); + ErrorQuality.SEMANTIC_ERROR); + return false; } index = Utils.parseInt(parseResult.regexes.get(0).group(0)); diff --git a/src/main/java/com/btk5h/skriptmirror/skript/custom/PreloadListener.java b/src/main/java/com/btk5h/skriptmirror/skript/custom/PreloadListener.java index ce70df6..9eec79a 100644 --- a/src/main/java/com/btk5h/skriptmirror/skript/custom/PreloadListener.java +++ b/src/main/java/com/btk5h/skriptmirror/skript/custom/PreloadListener.java @@ -64,13 +64,6 @@ public void onPreScriptLoad(PreScriptLoadEvent preScriptLoadEvent) { } } - // TODO docs for all changes - - // TODO error for non-preloadable syntax when attempted to be used on preload - // TODO and have a concrete boolean for whether a custom syntax section is preloadable - - // TODO usable in section (imports) - public static void handleEventNode(SectionNode sectionNode) { String key = sectionNode.getKey(); assert key != null; diff --git a/src/main/java/com/btk5h/skriptmirror/skript/reflect/ExprJavaCall.java b/src/main/java/com/btk5h/skriptmirror/skript/reflect/ExprJavaCall.java index 37ceb55..ce9dcf7 100644 --- a/src/main/java/com/btk5h/skriptmirror/skript/reflect/ExprJavaCall.java +++ b/src/main/java/com/btk5h/skriptmirror/skript/reflect/ExprJavaCall.java @@ -42,15 +42,18 @@ public class ExprJavaCall implements Expression { /** * A regular expression that captures potential descriptors without actually validating the descriptor. This is done * both for performance reasons and to provide more helpful error messages when using a malformed descriptor. + * See Descriptor's {@code DESCRIPTOR} field for the extended version of this. */ - private static final String LITE_DESCRIPTOR = "[^0-9.][^.]*\\b"; + private static final String LITE_DESCRIPTOR = "(\\[[\\w.$]*])?" + + "([^0-9. ][^. ]*\\b)" + + "(\\[[\\w.$, ]*])?"; static { //noinspection unchecked Skript.registerExpression(ExprJavaCall.class, Object.class, ExpressionType.PATTERN_MATCHES_EVERYTHING, "[(2¦try)] %object%..%string%[\\((1¦[%-objects%])\\)]", - "[(2¦try)] %object%.<" + LITE_DESCRIPTOR + ">[\\((1¦[%-objects%])\\)])", + "[(2¦try)] %object%.<" + LITE_DESCRIPTOR + ">[\\((1¦[%-objects%])\\)]", "[(2¦try)] [a] new %javatype%\\([%-objects%]\\)"); } @@ -81,7 +84,7 @@ public String toString() { private final Class[] types; private final Class superType; - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "unused"}) public ExprJavaCall() { this(null, (Class) Object.class); } @@ -293,21 +296,26 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye try { staticDescriptor = Descriptor.parse(desc, script); + } catch (ImportNotFoundException e) { + Skript.error("The class " + e.getUserType() + " could not be found."); + return false; + } + + if (staticDescriptor == null) { + Skript.error(desc + " is not a valid descriptor."); + return false; + } - if (staticDescriptor == null) { - Skript.error(desc + " is not a valid descriptor."); - return false; - } - - if (staticDescriptor.getJavaClass() != null - && getCallSite(staticDescriptor).size() == 0) { - Skript.error(desc + " refers to a non-existent method/field."); - return false; - } - } catch (ClassNotFoundException e) { - Skript.error(desc + " refers to a non-existent class."); + if (staticDescriptor.getJavaClass() != null && getCallSite(staticDescriptor).size() == 0) { + Skript.error(desc + " refers to a non-existent method/field."); return false; } + + if (staticDescriptor.getParameterTypes() != null && type.equals(CallType.FIELD)) { + Skript.error("You can't pass parameter types to a field call."); + return false; + } + break; case 2: type = CallType.CONSTRUCTOR; @@ -335,10 +343,6 @@ private void directError(String message) { } } - private boolean hasDynamicDescriptor() { - return staticDescriptor == null; - } - private synchronized Collection getCallSite(Descriptor e) { return callSiteCache.computeIfAbsent(e, this::createCallSite); } @@ -479,37 +483,42 @@ type, descriptor, argumentsMessage(arguments), } private Descriptor getDescriptor(Event e) { - if (hasDynamicDescriptor()) { - String desc = dynamicDescriptor.getSingle(e); + if (staticDescriptor != null) + return staticDescriptor; - if (desc == null) { - error(String.format("Dynamic descriptor %s returned null", dynamicDescriptor.toString(e, false))); - return null; - } + String desc = dynamicDescriptor.getSingle(e); - try { - Descriptor parsedDescriptor = Descriptor.parse(desc, script); + if (desc == null) { + error(String.format("Dynamic descriptor %s returned null", dynamicDescriptor.toString(e, false))); + return null; + } - if (parsedDescriptor == null) { - error(String.format("Invalid dynamic descriptor %s (%s)", dynamicDescriptor.toString(e, false), desc)); - return null; - } + Descriptor parsedDescriptor; + try { + parsedDescriptor = Descriptor.parse(desc, script); + } catch (ImportNotFoundException ex) { + error("The class" + ex.getUserType() + " could not be found."); + return null; + } - return parsedDescriptor; - } catch (ClassNotFoundException ex) { - error(ex, String.format("Class could not be found while parsing the dynamic descriptor %s (%s)", - dynamicDescriptor.toString(e, false), desc)); - return null; - } + if (parsedDescriptor == null) { + error(String.format("Invalid dynamic descriptor %s (%s)", dynamicDescriptor.toString(e, false), desc)); + return null; } - return staticDescriptor; + return parsedDescriptor; } + /** + * Returns an array with the same objects as the given array. + */ private static Object[] createStaticArgumentsCopy(Object[] args) { return Arrays.copyOf(args, args.length); } + /** + * Returns an array with the target parameter in index 0, and the arguments parameter in the following indices. + */ private static Object[] createInstanceArgumentsCopy(Object target, Object[] arguments) { Object[] copy = new Object[arguments.length + 1]; copy[0] = target; @@ -517,12 +526,18 @@ private static Object[] createInstanceArgumentsCopy(Object target, Object[] argu return copy; } + /** + * Returns an optional {@link MethodHandle} that matches the given {@link Descriptor} with the given arguments. + */ private Optional findCompatibleMethod(Descriptor descriptor, Object[] args) { return getCallSite(descriptor).stream() - .filter(mh -> matchesArgs(args, mh)) - .findFirst(); + .filter(mh -> matchesArgs(args, mh)) + .findFirst(); } + /** + * Checks if the given arguments match the arguments for the given {@link MethodHandle}. + */ private static boolean matchesArgs(Object[] args, MethodHandle mh) { MethodType mt = mh.type(); Class[] params = mt.parameterArray(); @@ -561,6 +576,9 @@ private static boolean matchesArgs(Object[] args, MethodHandle mh) { return true; } + /** + * Returns whether the given {@link Object} can be converted to the given class. + */ private static boolean canCoerceType(Object o, Class to) { if (to.isInstance(o)) { return true; @@ -607,16 +625,73 @@ private static boolean canCoerceType(Object o, Class to) { return !to.isPrimitive() && o instanceof Null; } + /** + * Converts the given {@link Object} to the given class. + * If {@link #canCoerceType(Object, Class)} returned {@code false}, this + * returns the same {@link Object} as was passed to this method. + */ + private static Object coerceType(Object o, Class to) { + // coerce numeric types + if (to.isPrimitive() && o instanceof Number) { + if (to == byte.class) { + return ((Number) o).byteValue(); + } else if (to == double.class) { + return ((Number) o).doubleValue(); + } else if (to == float.class) { + return ((Number) o).floatValue(); + } else if (to == int.class) { + return ((Number) o).intValue(); + } else if (to == long.class) { + return ((Number) o).longValue(); + } else if (to == short.class) { + return ((Number) o).shortValue(); + } + } + + // coerce arrays of numeric types + if (to.isArray() + && JavaUtil.getArrayDepth(to) == JavaUtil.getArrayDepth(o.getClass()) + && JavaUtil.isNumericClass(JavaUtil.getBaseComponent(to))) { + return JavaUtil.convertNumericArray(o, JavaUtil.getBaseComponent(to)); + } + + // coerce single character strings to chars + if (o instanceof String && (to == char.class || to == Character.class)) { + return ((String) o).charAt(0); + } + + // coerce a Skript ItemType to an ItemStack + if (o instanceof ItemType && to == ItemStack.class) { + return ((ItemType) o).getRandom(); + } + + // coerce javatypes and classinfos into classes + if (to == Class.class) { + if (o instanceof JavaType) { + return ((JavaType) o).getJavaClass(); + } else if (o instanceof ClassInfo) { + return ((ClassInfo) o).getC(); + } + } + + // unwrap null wrapper + if (o instanceof Null) { + return null; + } + + return o; + } + private static Object[] convertTypes(MethodHandle mh, Object[] args) { Class[] params = mh.type().parameterArray(); int varargsIndex = params.length - 1; boolean hasVarargs = mh.isVarargsCollector(); for (int i = 0; i < args.length; i++) { - Class param; boolean loopAtVarargs = hasVarargs && i >= varargsIndex; // varargs parameters are always arrays, but the method handle expects the array to be spread before called + Class param; if (loopAtVarargs) { param = params[varargsIndex].getComponentType(); } else { @@ -636,54 +711,7 @@ private static Object[] convertTypes(MethodHandle mh, Object[] args) { System.arraycopy(varargsArray, 0, args, varargsIndex, varargsLength); } - // coerce numeric types - if (param.isPrimitive() && args[i] instanceof Number) { - if (param == byte.class) { - args[i] = ((Number) args[i]).byteValue(); - } else if (param == double.class) { - args[i] = ((Number) args[i]).doubleValue(); - } else if (param == float.class) { - args[i] = ((Number) args[i]).floatValue(); - } else if (param == int.class) { - args[i] = ((Number) args[i]).intValue(); - } else if (param == long.class) { - args[i] = ((Number) args[i]).longValue(); - } else if (param == short.class) { - args[i] = ((Number) args[i]).shortValue(); - } - } - - // coerce arrays of numeric types - if (param.isArray() - && JavaUtil.getArrayDepth(param) == JavaUtil.getArrayDepth(args[i].getClass()) - && JavaUtil.isNumericClass(JavaUtil.getBaseComponent(param))) { - args[i] = JavaUtil.convertNumericArray(args[i], JavaUtil.getBaseComponent(param)); - } - - // coerce single character strings to chars - if (args[i] instanceof String - && (param == char.class || param == Character.class)) { - args[i] = ((String) args[i]).charAt(0); - } - - // coerce a Skript ItemType to an ItemStack - if (args[i] instanceof ItemType && param == ItemStack.class) { - args[i] = ((ItemType) args[i]).getRandom(); - } - - // coerce javatypes and classinfos into classes - if (param == Class.class) { - if (args[i] instanceof JavaType) { - args[i] = ((JavaType) args[i]).getJavaClass(); - } else if (args[i] instanceof ClassInfo) { - args[i] = ((ClassInfo) args[i]).getC(); - } - } - - // unwrap null wrapper - if (args[i] instanceof Null) { - args[i] = null; - } + args[i] = coerceType(args[i], param); } return args; @@ -716,6 +744,10 @@ private void suggestParameters(Descriptor descriptor) { } } + /** + * Sends fields / methods with names similar to the called field / method. + * Uses {@link StringSimilarity#compare(String, String, int)} for string similarity checks. + */ private void suggestTypo(Descriptor descriptor) { String guess = descriptor.getName(); Class javaClass = descriptor.getJavaClass(); @@ -723,14 +755,14 @@ private void suggestTypo(Descriptor descriptor) { Stream members = getMembers(javaClass); List matches = members - .map(Member::getName) - .filter(m -> !m.equals(guess)) // this would be a parameter mismatch, not a typo - .distinct() - .map(m -> StringSimilarity.compare(guess, m, 3)) - .filter(Objects::nonNull) - .sorted() - .map(StringSimilarity.Result::getRight) - .collect(Collectors.toList()); + .map(Member::getName) + .filter(m -> !m.equals(guess)) // this would be a parameter mismatch, not a typo + .distinct() + .map(m -> StringSimilarity.compare(guess, m, 3)) + .filter(Objects::nonNull) + .sorted() + .map(StringSimilarity.Result::getRight) + .collect(Collectors.toList()); if (!matches.isEmpty()) { directError(String.format("Did you misspell the %s? You may have meant to type one of the following:", type)); @@ -776,8 +808,8 @@ private String argumentsMessage(Object... arguments) { private static String argumentsToString(Object... arguments) { return Arrays.stream(arguments) - .map(arg -> String.format("%s (%s)", - Classes.toString(arg), SkriptMirrorUtil.getDebugName(SkriptMirrorUtil.getClass(arg)))) - .collect(Collectors.joining(", ")); + .map(arg -> String.format("%s (%s)", + Classes.toString(arg), SkriptMirrorUtil.getDebugName(SkriptMirrorUtil.getClass(arg)))) + .collect(Collectors.joining(", ")); } } diff --git a/src/main/java/com/btk5h/skriptmirror/skript/reflect/sections/CondSection.java b/src/main/java/com/btk5h/skriptmirror/skript/reflect/sections/CondSection.java index bfdee66..5e6d215 100644 --- a/src/main/java/com/btk5h/skriptmirror/skript/reflect/sections/CondSection.java +++ b/src/main/java/com/btk5h/skriptmirror/skript/reflect/sections/CondSection.java @@ -8,6 +8,7 @@ import ch.njol.skript.lang.*; import ch.njol.skript.log.SkriptLogger; import ch.njol.util.Kleenean; +import com.btk5h.skriptmirror.ScriptLoaderState; import com.btk5h.skriptmirror.util.SkriptReflection; import com.btk5h.skriptmirror.util.SkriptUtil; import org.bukkit.event.Event; @@ -43,11 +44,12 @@ public CondSection() { sectionNode.getLine()); SkriptReflection.getNodes(newSectionNode).addAll(nodes); nodes.clear(); - Class[] currentEvents = ScriptLoader.getCurrentEvents(); - String currentEventName = ScriptLoader.getCurrentEventName(); + + ScriptLoaderState previousState = ScriptLoaderState.copyOfCurrentState(); ScriptLoader.setCurrentEvent("section event", SectionEvent.class); List triggerItemList = SkriptUtil.getItemsFromNode(newSectionNode); - ScriptLoader.setCurrentEvent(currentEventName, currentEvents); + previousState.applyToCurrentState(); + trigger = new Trigger(SkriptUtil.getCurrentScript(), "section", new SkriptEvent() { @Override public boolean init(Literal[] args, int matchedPattern, SkriptParser.ParseResult parseResult) { diff --git a/src/main/java/com/btk5h/skriptmirror/util/JavaUtil.java b/src/main/java/com/btk5h/skriptmirror/util/JavaUtil.java index 45bdd56..66ef20d 100644 --- a/src/main/java/com/btk5h/skriptmirror/util/JavaUtil.java +++ b/src/main/java/com/btk5h/skriptmirror/util/JavaUtil.java @@ -43,15 +43,15 @@ public final class JavaUtil { public static Stream fields(Class cls) { return Stream.concat( - Arrays.stream(cls.getFields()), - Arrays.stream(cls.getDeclaredFields()) + Arrays.stream(cls.getFields()), + Arrays.stream(cls.getDeclaredFields()) ).distinct(); } public static Stream methods(Class cls) { return Stream.concat( - Arrays.stream(cls.getMethods()), - Arrays.stream(cls.getDeclaredMethods()) + Arrays.stream(cls.getMethods()), + Arrays.stream(cls.getDeclaredMethods()) ).distinct(); } @@ -157,22 +157,21 @@ public interface ExceptionalFunction { R apply(T t) throws Exception; } - @SuppressWarnings("ThrowableNotThrown") public static Function propagateErrors(ExceptionalFunction function) { return t -> { try { return function.apply(t); } catch (Exception e) { - Skript.warning( - String.format("skript-reflect encountered a %s: %s%n" + - "Run Skript with the verbosity 'very high' for the stack trace.", - e.getClass().getSimpleName(), e.getMessage())); + Skript.warning("skript-reflect encountered a " + e.getClass().getSimpleName() + ": " + e.getMessage()); if (Skript.logVeryHigh()) { StringWriter errors = new StringWriter(); e.printStackTrace(new PrintWriter(errors)); Skript.warning(errors.toString()); + } else { + Skript.warning("Run Skript with the verbosity 'very high' for the stack trace."); } + } return null; }; diff --git a/src/main/java/com/btk5h/skriptmirror/util/SkriptMirrorUtil.java b/src/main/java/com/btk5h/skriptmirror/util/SkriptMirrorUtil.java index b63039e..a669a80 100644 --- a/src/main/java/com/btk5h/skriptmirror/util/SkriptMirrorUtil.java +++ b/src/main/java/com/btk5h/skriptmirror/util/SkriptMirrorUtil.java @@ -11,7 +11,13 @@ import java.util.stream.Collectors; public class SkriptMirrorUtil { + /** + * A word ($ also an allowed char) that doesn't start with a digit. + */ public static final String IDENTIFIER = "[_a-zA-Z$][\\w$]*"; + /** + * A full classname (e.g. java.lang.String) + */ public static final String PACKAGE = "(?:" + IDENTIFIER + "\\.)*(?:" + IDENTIFIER + ")"; private static final Pattern TYPE_PREFIXES = Pattern.compile("^[-*~]*"); diff --git a/src/main/java/com/btk5h/skriptmirror/util/StringSimilarity.java b/src/main/java/com/btk5h/skriptmirror/util/StringSimilarity.java index 492f0b6..eef88c4 100644 --- a/src/main/java/com/btk5h/skriptmirror/util/StringSimilarity.java +++ b/src/main/java/com/btk5h/skriptmirror/util/StringSimilarity.java @@ -33,7 +33,14 @@ public int compareTo(Result o) { return getEditDistance() - o.getEditDistance(); } } - + + /** + * Calculates Levenshtein distance between two strings. + *
+ * Source: Apache + *
+ * See LICENSE.TXT for the license. + */ public static Result compare(String left, String right, int threshold) { String first = left; String second = right;