diff --git a/src/main/java/ch/njol/skript/expressions/ExprFilter.java b/src/main/java/ch/njol/skript/expressions/ExprFilter.java index 239b720e418..1e280bdff0f 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFilter.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFilter.java @@ -27,83 +27,72 @@ import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.Literal; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.lang.util.SimpleExpression; import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.util.LiteralUtils; -import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; -import ch.njol.util.coll.iterator.ArrayIterator; import com.google.common.collect.Iterators; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import java.lang.reflect.Array; -import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; -import java.util.List; +import java.util.Set; import java.util.regex.Pattern; @Name("Filter") -@Description("Filters a list based on a condition. " + - "For example, if you ran 'broadcast \"something\" and \"something else\" where [string input is \"something\"]', " + - "only \"something\" would be broadcast as it is the only string that matched the condition.") +@Description({ + "Filters a list based on a condition. ", + "For example, if you ran 'broadcast \"something\" and \"something else\" where [string input is \"something\"]', ", + "only \"something\" would be broadcast as it is the only string that matched the condition." +}) @Examples("send \"congrats on being staff!\" to all players where [player input has permission \"staff\"]") @Since("2.2-dev36") @SuppressWarnings({"null", "unchecked"}) public class ExprFilter extends SimpleExpression { - @Nullable - private static ExprFilter parsing; - static { Skript.registerExpression(ExprFilter.class, Object.class, ExpressionType.COMBINED, "%objects% (where|that match) \\[<.+>\\]"); + ParserInstance.registerData(FilterData.class, FilterData::new); } - private Object current; - private List> children = new ArrayList<>(); - private Condition condition; - private String rawCond; - private Expression objects; + private Condition filterCondition; + private String unparsedCondition; + private Expression unfilteredObjects; + private Set> dependentInputs = new HashSet<>(); @Nullable - public static ExprFilter getParsing() { - return parsing; - } + private Object currentFilterValue; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - try { - parsing = this; - objects = LiteralUtils.defendExpression(exprs[0]); - if (objects.isSingle()) - return false; - rawCond = parseResult.regexes.get(0).group(); - condition = Condition.parse(rawCond, "Can't understand this condition: " + rawCond); - } finally { - parsing = null; - } - return condition != null && LiteralUtils.canInitSafely(objects); + unfilteredObjects = LiteralUtils.defendExpression(exprs[0]); + if (unfilteredObjects.isSingle() || !LiteralUtils.canInitSafely(unfilteredObjects)) + return false; + unparsedCondition = parseResult.regexes.get(0).group(); + FilterData filterData = getParser().getData(FilterData.class); + ExprFilter originalParentFilter = filterData.parentFilter; + filterData.parentFilter = this; + filterCondition = Condition.parse(unparsedCondition, "Can't understand this condition: " + unparsedCondition); + filterData.parentFilter = originalParentFilter; + return filterCondition != null; } @NonNull @Override public Iterator iterator(Event event) { - Iterator objIterator = this.objects.iterator(event); - if (objIterator == null) + Iterator unfilteredObjectIterator = unfilteredObjects.iterator(event); + if (unfilteredObjectIterator == null) return Collections.emptyIterator(); - try { - return Iterators.filter(objIterator, object -> { - current = object; - return condition.check(event); - }); - } finally { - current = null; - } + return Iterators.filter(unfilteredObjectIterator, candidateObject -> { + currentFilterValue = candidateObject; + return filterCondition.check(event); + }); } @Override @@ -115,146 +104,66 @@ protected Object[] get(Event event) { } } - public Object getCurrent() { - return current; - } - - private void addChild(ExprInput child) { - children.add(child); - } - - private void removeChild(ExprInput child) { - children.remove(child); - } - @Override public Class getReturnType() { - return objects.getReturnType(); + return unfilteredObjects.getReturnType(); } @Override public boolean isSingle() { - return objects.isSingle(); + return false; } @Override public String toString(Event event, boolean debug) { - return String.format("%s where [%s]", objects.toString(event, debug), rawCond); + return unfilteredObjects.toString(event, debug) + " that match [" + unparsedCondition + "]"; } - @Override - public boolean isLoopOf(String s) { - for (ExprInput child : children) { // if they used player input, let's assume loop-player is valid - if (child.getClassInfo() == null || child.getClassInfo().getUserInputPatterns() == null) - continue; + private boolean matchesAnySpecifiedTypes(String candidateString) { + for (ExprFilterInput dependentInput : dependentInputs) { + ClassInfo specifiedType = dependentInput.getSpecifiedType(); + if (specifiedType == null) + return false; + Pattern[] specifiedTypePatterns = specifiedType.getUserInputPatterns(); + if (specifiedTypePatterns == null) + return false; - for (Pattern pattern : child.getClassInfo().getUserInputPatterns()) { - if (pattern.matcher(s).matches()) + for (Pattern typePattern : specifiedTypePatterns) { + if (typePattern.matcher(candidateString).matches()) { return true; + } } } - return objects.isLoopOf(s); // nothing matched, so we'll rely on the object expression's logic + return false; } - @Name("Filter Input") - @Description("Represents the input in a filter expression. " + - "For example, if you ran 'broadcast \"something\" and \"something else\" where [input is \"something\"]" + - "the condition would be checked twice, using \"something\" and \"something else\" as the inputs.") - @Examples("send \"congrats on being staff!\" to all players where [input has permission \"staff\"]") - @Since("2.2-dev36") - public static class ExprInput extends SimpleExpression { - - static { - Skript.registerExpression(ExprInput.class, Object.class, ExpressionType.COMBINED, - "input", - "%*classinfo% input" - ); - } - - @Nullable - private final ExprInput source; - private final Class[] types; - private final Class superType; - @SuppressWarnings("NotNullFieldNotInitialized") - private ExprFilter parent; - @Nullable - private ClassInfo inputType; - - public ExprInput() { - this(null, (Class) Object.class); - } - public ExprInput(@Nullable ExprInput source, Class... types) { - this.source = source; - if (source != null) { - this.parent = source.parent; - this.inputType = source.inputType; - parent.removeChild(source); - parent.addChild(this); - } - - this.types = types; - this.superType = (Class) Utils.getSuperType(types); - } - - @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - parent = ExprFilter.getParsing(); - - if (parent == null) - return false; - - parent.addChild(this); - inputType = matchedPattern == 0 ? null : ((Literal>) exprs[0]).getSingle(); - return true; - } - - @Override - protected T[] get(Event event) { - Object current = parent.getCurrent(); - if (inputType != null && !inputType.getC().isInstance(current)) { - return null; - } - - try { - return Converters.convert(new Object[]{current}, types, superType); - } catch (ClassCastException e1) { - return (T[]) Array.newInstance(superType, 0); - } - } - - public void setParent(ExprFilter parent) { - this.parent = parent; - } + @Override + public boolean isLoopOf(String candidateString) { + return unfilteredObjects.isLoopOf(candidateString) || matchesAnySpecifiedTypes(candidateString); + } - @Override - public Expression getConvertedExpression(Class... to) { - return new ExprInput<>(this, to); - } + public Set> getDependentInputs() { + return dependentInputs; + } - @Override - public Expression getSource() { - return source == null ? this : source; - } + @Nullable + public Object getCurrentFilterValue() { + return currentFilterValue; + } - @Override - public Class getReturnType() { - return superType; - } + public static class FilterData extends ParserInstance.Data { @Nullable - private ClassInfo getClassInfo() { - return inputType; - } + private ExprFilter parentFilter; - @Override - public boolean isSingle() { - return true; + public FilterData(ParserInstance parserInstance) { + super(parserInstance); } - @Override - public String toString(Event event, boolean debug) { - return inputType == null ? "input" : inputType.getCodeName() + " input"; + @Nullable + public ExprFilter getParentFilter() { + return parentFilter; } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprFilterInput.java b/src/main/java/ch/njol/skript/expressions/ExprFilterInput.java new file mode 100644 index 00000000000..ac1300c9b0f --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprFilterInput.java @@ -0,0 +1,139 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.Utils; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.jaxen.expr.Expr; +import org.skriptlang.skript.lang.converter.Converters; + +import java.lang.reflect.Array; +import java.util.Set; + +@Name("Filter Input") +@Description({ + "Represents the input in a filter expression.", + "For example, if you ran 'broadcast \"something\" and \"something else\" where [input is \"something\"]", + "the condition would be checked twice, using \"something\" and \"something else\" as the inputs." +}) +@Examples("send \"congrats on being staff!\" to all players where [input has permission \"staff\"]") +@Since("2.2-dev36") +public class ExprFilterInput extends SimpleExpression { + + static { + Skript.registerExpression(ExprFilterInput.class, Object.class, ExpressionType.COMBINED, + "input", + "%*classinfo% input" + ); + } + + @Nullable + private final ExprFilterInput source; + private final Class[] types; + private final Class superType; + + private ExprFilter parentFilter; + + @Nullable + private ClassInfo specifiedType; + + public ExprFilterInput() { + this(null, (Class) Object.class); + } + + public ExprFilterInput(@Nullable ExprFilterInput source, Class... types) { + this.source = source; + if (source != null) { + specifiedType = source.specifiedType; + parentFilter = source.parentFilter; + Set> dependentInputs = parentFilter.getDependentInputs(); + dependentInputs.remove(this.source); + dependentInputs.add(this); + + } + this.types = types; + this.superType = (Class) Utils.getSuperType(types); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + parentFilter = getParser().getData(ExprFilter.FilterData.class).getParentFilter(); + if (parentFilter == null) + return false; + specifiedType = matchedPattern == 0 ? null : ((Literal>) exprs[0]).getSingle(); + return true; + } + + @Override + protected T[] get(Event event) { + Object currentValue = parentFilter.getCurrentFilterValue(); + if (currentValue == null || (specifiedType != null && !specifiedType.getC().isInstance(currentValue))) + return (T[]) Array.newInstance(superType, 0); + + try { + return Converters.convert(new Object[]{currentValue}, types, superType); + } catch (ClassCastException exception) { + return (T[]) Array.newInstance(superType, 0); + } + } + + @Override + public Expression getConvertedExpression(Class... to) { + return new ExprFilterInput<>(this, to); + } + + @Override + public Expression getSource() { + return source == null ? this : source; + } + + @Override + public Class getReturnType() { + return superType; + } + + @Nullable + public ClassInfo getSpecifiedType() { + return specifiedType; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public String toString(Event event, boolean debug) { + return specifiedType == null ? "input" : specifiedType.getCodeName() + " input"; + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk b/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk index b1f1738f20d..178700f6f71 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk @@ -3,4 +3,14 @@ test "where filter": assert first element of ({_list::*} where [string input is "foo"]) is "foo" with "ExprFilter filtered incorrectly" assert {_list::*} where [number input is set] is not set with "ExprFilter provided input value when classinfo did not match" assert first element of ({_list::*} where [input is "foo"]) is "foo" with "ExprFilter filtered object input incorrectly" + assert first element of ({_list::*} where [input is "bar"]) is "bar" with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is "bar"]) is 1 with "ExprFilter filtered object input incorrectly" + assert first element of ({_list::*} where [input is "bar"]) is "bar" with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is "bar"]) is 1 with "ExprFilter filtered object input incorrectly" + assert first element of ({_list::*} where [input is "foobar"]) is "foobar" with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is "foobar"]) is 1 with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is "foo" or "bar"]) is 2 with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is set]) is 3 with "ExprFilter filtered object input incorrectly" assert {_list::*} where [false is true] is not set with "ExprFilter returned objects with false condition" + assert ({_list::*} where [input is (("foo" and "bar") where [input is "bar"])]) is "bar" with "Failed filter with filter within condition" + assert (({_list::*} where [input is "foo"]) where [input is "foo"]) is "foo" with "Failed chained filters"