From b6d76a7c0266995c72875759247eeccc2c96cca4 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Tue, 7 Jan 2020 06:38:34 +0400 Subject: [PATCH] * Gain abilities - fixed wrong order effects with changeling ability (all creature type effect, #6147); --- .../GainAbilityDependenciesTest.java | 88 +++++++++++++++++++ Mage/src/main/java/mage/MageObjectImpl.java | 11 ++- .../ConditionalContinuousEffect.java | 8 ++ .../abilities/effects/ContinuousEffect.java | 2 + .../effects/ContinuousEffectImpl.java | 54 ++++++++++++ .../continuous/GainAbilityAllEffect.java | 3 +- .../continuous/GainAbilityAttachedEffect.java | 10 +-- .../GainAbilityControlledEffect.java | 3 +- .../continuous/GainAbilitySourceEffect.java | 4 +- .../continuous/GainAbilityTargetEffect.java | 3 +- .../java/mage/constants/DependencyType.java | 10 ++- .../src/main/java/mage/filter/FilterCard.java | 1 - .../java/mage/filter/FilterPermanent.java | 1 - .../main/java/mage/filter/FilterPlayer.java | 1 - .../java/mage/filter/FilterStackObject.java | 8 +- .../mage/filter/predicate/Predicates.java | 45 ++++++++-- 16 files changed, 220 insertions(+), 32 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/continuous/GainAbilityDependenciesTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/GainAbilityDependenciesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/GainAbilityDependenciesTest.java new file mode 100644 index 00000000000..9ebddab5b2e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/GainAbilityDependenciesTest.java @@ -0,0 +1,88 @@ +package org.mage.test.cards.continuous; + +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.keyword.ChangelingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.NamePredicate; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class GainAbilityDependenciesTest extends CardTestPlayerBase { + + @Test + public void test_GenerationByFilters() { + // auto-dependency must find subtype predicate and add dependecy on it + FilterPermanent filterEmpty = new FilterPermanent("empty"); + FilterPermanent filterSubtype = new FilterPermanent(SubType.HUMAN, "single"); + FilterPermanent filterOr = new FilterPermanent("or"); + filterOr.add(Predicates.or( + SubType.HUMAN.getPredicate(), + SubType.ORC.getPredicate())); + FilterPermanent filterTree = new FilterPermanent("tree"); + filterTree.add(Predicates.and( + new NamePredicate("test"), + Predicates.or( + SubType.HUMAN.getPredicate(), + SubType.ORC.getPredicate()) + )); + + ContinuousEffectImpl effectEmpty = new GainAbilityAllEffect(HasteAbility.getInstance(), Duration.EndOfTurn, filterEmpty); + ContinuousEffectImpl effectSubtype = new GainAbilityAllEffect(HasteAbility.getInstance(), Duration.EndOfTurn, filterSubtype); + ContinuousEffectImpl effectOr = new GainAbilityAllEffect(HasteAbility.getInstance(), Duration.EndOfTurn, filterOr); + ContinuousEffectImpl effectTree = new GainAbilityAllEffect(HasteAbility.getInstance(), Duration.EndOfTurn, filterTree); + + Assert.assertFalse("must haven't depends with empty filter", effectEmpty.getDependedToTypes().contains(DependencyType.AddingCreatureType)); + Assert.assertTrue("must have depend from subtype predicate", effectSubtype.getDependedToTypes().contains(DependencyType.AddingCreatureType)); + Assert.assertTrue("must have depend from or predicate", effectOr.getDependedToTypes().contains(DependencyType.AddingCreatureType)); + Assert.assertTrue("must have depend from tree predicate", effectTree.getDependedToTypes().contains(DependencyType.AddingCreatureType)); + } + + /** + * I had an elephant token equipped with Amorphous Axe attacking and a Tempered Sliver in play. The token did combat + * damage to a player but it didnt get the +1/+1 counter it hsould be getting. + * + * More details: https://github.com/magefree/mage/issues/6147 + */ + @Test + public void test_SliverGain() { + // Equipped creature gets +3/+0 and is every creature type. + // Equip {3} + addCard(Zone.BATTLEFIELD, playerA, "Amorphous Axe"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + // + // Create a 3/3 green Elephant creature token. + addCard(Zone.HAND, playerA, "Elephant Ambush"); // {2}{G}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + // + // Sliver creatures you control have “Whenever this creature deals combat damage to a player, put a +1/+1 counter on it.” + addCard(Zone.BATTLEFIELD, playerA, "Tempered Sliver"); + + // cast token + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elephant Ambush"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("token must exist", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elephant", 1); + + // equip + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip {3}", "Elephant"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkAbility("must have all type ability", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elephant", ChangelingAbility.class, true); + + // attack with +1 token + attack(3, playerA, "Elephant", playerB); + checkPermanentCounters("must have counter", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Elephant", CounterType.P1P1, 1); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage/src/main/java/mage/MageObjectImpl.java b/Mage/src/main/java/mage/MageObjectImpl.java index c06689c5ca6..b68592bb6b1 100644 --- a/Mage/src/main/java/mage/MageObjectImpl.java +++ b/Mage/src/main/java/mage/MageObjectImpl.java @@ -17,6 +17,7 @@ import mage.cards.FrameStyle; import mage.constants.*; import mage.game.Game; import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; import mage.util.GameLog; import mage.util.SubTypeList; @@ -251,8 +252,14 @@ public abstract class MageObjectImpl implements MageObject { if (value.getSubTypeSet() != SubTypeSet.CreatureType) { return false; } - // as it is creature subtype, then check the existence of Changeling - return abilities.contains(ChangelingAbility.getInstance()) || isAllCreatureTypes(); + // as it is a creature subtype, then check the existence of Changeling + Abilities checkList; + if (this instanceof Permanent) { + checkList = ((Permanent) this).getAbilities(game); + } else { + checkList = abilities; + } + return checkList.contains(ChangelingAbility.getInstance()) || isAllCreatureTypes(); } } diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java index c7c9f7518ac..9685b3fd69c 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalContinuousEffect.java @@ -162,6 +162,14 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl { return super.getDependencyTypes(); } + @Override + public EnumSet getDependedToTypes() { + if (effect != null) { + return effect.getDependedToTypes(); + } + return super.getDependedToTypes(); + } + @Override public Set isDependentTo(List allEffectsInLayer) { if (effect != null) { diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java index 3666e45af77..cdea7ccc337 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffect.java @@ -59,6 +59,8 @@ public interface ContinuousEffect extends Effect { void setDependedToType(DependencyType dependencyType); + EnumSet getDependedToTypes(); + void addDependedToType(DependencyType dependencyType); void setStartingControllerAndTurnNum(Game game, UUID startingController, UUID activePlayerId); diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java index 4355daf9b4e..012e935d053 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java @@ -2,12 +2,17 @@ package mage.abilities.effects; import mage.MageObjectReference; import mage.abilities.Ability; +import mage.abilities.CompoundAbility; import mage.abilities.MageSingleton; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.DomainValue; import mage.abilities.dynamicvalue.common.SignInversionDynamicValue; import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.keyword.ChangelingAbility; import mage.constants.*; +import mage.filter.Filter; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; import mage.game.Game; import mage.players.Player; import mage.target.targetpointer.TargetPointer; @@ -319,6 +324,11 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu return dependencyTypes; } + @Override + public EnumSet getDependedToTypes() { + return dependendToTypes; + } + @Override public void addDependencyType(DependencyType dependencyType) { dependencyTypes.add(dependencyType); @@ -341,4 +351,48 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu return this; } + /** + * Auto-generates dependencies on different effects (what's apply first and what's apply second) + */ + public void generateGainAbilityDependencies(Ability abilityToGain, Filter filterToSearch) { + this.addDependencyType(DependencyType.AddingAbility); + this.generateGainAbilityDependenciesFromAbility(abilityToGain); + this.generateGainAbilityDependenciesFromFilter(filterToSearch); + } + + public void generateGainAbilityDependencies(CompoundAbility abilityToGain, Filter filterToSearch) { + this.addDependencyType(DependencyType.AddingAbility); + this.generateGainAbilityDependenciesFromAbility(abilityToGain); + this.generateGainAbilityDependenciesFromFilter(filterToSearch); + } + + private void generateGainAbilityDependenciesFromAbility(CompoundAbility compoundAbility) { + if (compoundAbility == null) return; + for (Ability ability : compoundAbility) { + generateGainAbilityDependenciesFromAbility(ability); + } + } + + private void generateGainAbilityDependenciesFromAbility(Ability ability) { + if (ability == null) return; + + // 1. "Is all type" ability (changeling) + // make dependency + if (ability instanceof ChangelingAbility) { + this.addDependencyType(DependencyType.AddingCreatureType); + } + } + + private void generateGainAbilityDependenciesFromFilter(Filter filter) { + if (filter == null) return; + + // 1. "Is all type" ability (changeling) + // wait dependency + // extraPredicates from some filters is player related, you don't need it here + List list = new ArrayList<>(); + Predicates.collectAllComponents(filter.getPredicates(), list); + if (list.stream().anyMatch(p -> p instanceof SubType.SubTypePredicate)) { + this.addDependedToType(DependencyType.AddingCreatureType); + } + } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAllEffect.java index 46ca7f5899a..40be9f1fab1 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAllEffect.java @@ -55,7 +55,8 @@ public class GainAbilityAllEffect extends ContinuousEffectImpl { this.ability.newId(); this.filter = filter; this.excludeSource = excludeSource; - this.addDependencyType(DependencyType.AddingAbility); + + this.generateGainAbilityDependencies(ability, filter); } public GainAbilityAllEffect(final GainAbilityAllEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java index fb52506093b..3db2d5894c2 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityAttachedEffect.java @@ -3,12 +3,7 @@ package mage.abilities.effects.common.continuous; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffectImpl; -import mage.constants.AttachmentType; -import mage.constants.DependencyType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; +import mage.constants.*; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; @@ -50,7 +45,8 @@ public class GainAbilityAttachedEffect extends ContinuousEffectImpl { } else { this.staticText = rule; } - this.addDependencyType(DependencyType.AddingAbility); + + this.generateGainAbilityDependencies(ability, null); } public GainAbilityAttachedEffect(final GainAbilityAttachedEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledEffect.java index aa2ea4b16c6..02b16b0f3c3 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledEffect.java @@ -53,8 +53,9 @@ public class GainAbilityControlledEffect extends ContinuousEffectImpl { this.ability = ability; this.filter = filter; this.excludeSource = excludeSource; - this.addDependencyType(DependencyType.AddingAbility); setText(); + + this.generateGainAbilityDependencies(ability, filter); } public GainAbilityControlledEffect(final GainAbilityControlledEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilitySourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilitySourceEffect.java index debfeb0b562..0a51322e9dd 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilitySourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilitySourceEffect.java @@ -5,7 +5,6 @@ import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffectImpl; import mage.cards.Card; -import mage.constants.DependencyType; import mage.constants.Duration; import mage.constants.Layer; import mage.constants.Outcome; @@ -48,7 +47,8 @@ public class GainAbilitySourceEffect extends ContinuousEffectImpl implements Sou if (noStaticText) { staticText = null; } - this.addDependencyType(DependencyType.AddingAbility); + + this.generateGainAbilityDependencies(ability, null); } public GainAbilitySourceEffect(final GainAbilitySourceEffect effect) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java index ba4747be4e1..b98e86f8bb8 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityTargetEffect.java @@ -44,7 +44,8 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl { this.ability = ability; staticText = rule; this.onCard = onCard; - this.addDependencyType(DependencyType.AddingAbility); + + this.generateGainAbilityDependencies(ability, null); } public GainAbilityTargetEffect(final GainAbilityTargetEffect effect) { diff --git a/Mage/src/main/java/mage/constants/DependencyType.java b/Mage/src/main/java/mage/constants/DependencyType.java index b7a7ebcc497..2d6d513d85c 100644 --- a/Mage/src/main/java/mage/constants/DependencyType.java +++ b/Mage/src/main/java/mage/constants/DependencyType.java @@ -1,21 +1,23 @@ - package mage.constants; /** * Dependency types are a part of a workaround solution to handle dependencies * of continuous effects. - * + *

+ * All continuous effects can: + * addDependencyType -- make dependency (effect makes some changes) + * addDependedToType -- wait another dependency (effect must wait until all ather effects finished) + *

* http://magiccards.info/rule/613-interaction-of-continuous-effects.html - * * https://github.com/magefree/mage/issues/1259 * - * * @author LevelX2 */ public enum DependencyType { AuraAddingRemoving, ArtifactAddingRemoving, AddingAbility, + AddingCreatureType, BecomeForest, BecomeIsland, BecomeMountain, diff --git a/Mage/src/main/java/mage/filter/FilterCard.java b/Mage/src/main/java/mage/filter/FilterCard.java index d3da298bffa..4aea26bc5f7 100644 --- a/Mage/src/main/java/mage/filter/FilterCard.java +++ b/Mage/src/main/java/mage/filter/FilterCard.java @@ -75,7 +75,6 @@ public class FilterCard extends FilterObject { public Set filter(Set cards, Game game) { return cards.stream().filter(card -> match(card, game)).collect(Collectors.toSet()); - } public boolean hasPredicates() { diff --git a/Mage/src/main/java/mage/filter/FilterPermanent.java b/Mage/src/main/java/mage/filter/FilterPermanent.java index e88c924be94..ca54f83f548 100644 --- a/Mage/src/main/java/mage/filter/FilterPermanent.java +++ b/Mage/src/main/java/mage/filter/FilterPermanent.java @@ -71,5 +71,4 @@ public class FilterPermanent extends FilterObject implements FilterIn public FilterPermanent copy() { return new FilterPermanent(this); } - } diff --git a/Mage/src/main/java/mage/filter/FilterPlayer.java b/Mage/src/main/java/mage/filter/FilterPlayer.java index 8f085ddd67c..dec02387966 100644 --- a/Mage/src/main/java/mage/filter/FilterPlayer.java +++ b/Mage/src/main/java/mage/filter/FilterPlayer.java @@ -56,5 +56,4 @@ public class FilterPlayer extends FilterImpl { public FilterPlayer copy() { return new FilterPlayer(this); } - } diff --git a/Mage/src/main/java/mage/filter/FilterStackObject.java b/Mage/src/main/java/mage/filter/FilterStackObject.java index 8a448dc9096..766dbbf5ee4 100644 --- a/Mage/src/main/java/mage/filter/FilterStackObject.java +++ b/Mage/src/main/java/mage/filter/FilterStackObject.java @@ -1,8 +1,5 @@ package mage.filter; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; import mage.filter.predicate.ObjectPlayer; import mage.filter.predicate.ObjectPlayerPredicate; import mage.filter.predicate.ObjectSourcePlayer; @@ -11,6 +8,10 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.stack.StackObject; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + /** * * @author North @@ -51,5 +52,4 @@ public class FilterStackObject extends FilterObject { public FilterStackObject copy() { return new FilterStackObject(this); } - } diff --git a/Mage/src/main/java/mage/filter/predicate/Predicates.java b/Mage/src/main/java/mage/filter/predicate/Predicates.java index f04ce998bb0..2b4f989d9c8 100644 --- a/Mage/src/main/java/mage/filter/predicate/Predicates.java +++ b/Mage/src/main/java/mage/filter/predicate/Predicates.java @@ -21,9 +21,10 @@ public final class Predicates { /** * Returns a predicate that evaluates to {@code true} if the given predicate evaluates to {@code false}. + * * @param * @param predicate - * @return + * @return */ public static Predicate not(Predicate predicate) { return new NotPredicate(predicate); @@ -34,9 +35,10 @@ public final class Predicates { * components are evaluated in order, and evaluation will be "short-circuited" as soon as a false predicate is * found. It defensively copies the iterable passed in, so future changes to it won't alter the behavior of this * predicate. If {@code components} is empty, the returned predicate will always evaluate to {@code true}. + * * @param * @param components - * @return + * @return */ public static Predicate and(Iterable> components) { return new AndPredicate(defensiveCopy(components)); @@ -47,9 +49,10 @@ public final class Predicates { * components are evaluated in order, and evaluation will be "short-circuited" as soon as a false predicate is * found. It defensively copies the array passed in, so future changes to it won't alter the behavior of this * predicate. If {@code components} is empty, the returned predicate will always evaluate to {@code true}. + * * @param * @param components - * @return + * @return */ public static Predicate and(Predicate... components) { return new AndPredicate(defensiveCopy(components)); @@ -59,10 +62,11 @@ public final class Predicates { * Returns a predicate that evaluates to {@code true} if both of its components evaluate to {@code true}. The * components are evaluated in order, and evaluation will be "short-circuited" as soon as a false predicate is * found. + * * @param * @param first * @param second - * @return + * @return */ public static Predicate and(Predicate first, Predicate second) { return new AndPredicate(Predicates.asList(checkNotNull(first), checkNotNull(second))); @@ -73,9 +77,10 @@ public final class Predicates { * components are evaluated in order, and evaluation will be "short-circuited" as soon as a true predicate is found. * It defensively copies the iterable passed in, so future changes to it won't alter the behavior of this predicate. * If {@code components} is empty, the returned predicate will always evaluate to {@code true}. + * * @param * @param components - * @return + * @return */ public static Predicate or(Iterable> components) { return new OrPredicate(defensiveCopy(components)); @@ -86,9 +91,10 @@ public final class Predicates { * components are evaluated in order, and evaluation will be "short-circuited" as soon as a true predicate is found. * It defensively copies the array passed in, so future changes to it won't alter the behavior of this predicate. If * {@code components} is empty, the returned predicate will always evaluate to {@code true}. + * * @param * @param components - * @return + * @return */ public static Predicate or(Predicate... components) { return new OrPredicate(defensiveCopy(components)); @@ -97,10 +103,11 @@ public final class Predicates { /** * Returns a predicate that evaluates to {@code true} if either of its components evaluates to {@code true}. The * components are evaluated in order, and evaluation will be "short-circuited" as soon as a true predicate is found. + * * @param * @param first * @param second - * @return + * @return */ public static Predicate or(Predicate first, Predicate second) { return new OrPredicate(Predicates.asList(first, second)); @@ -126,6 +133,7 @@ public final class Predicates { public String toString() { return "Not(" + predicate.toString() + ')'; } + private static final long serialVersionUID = 0; } @@ -150,6 +158,7 @@ public final class Predicates { public String toString() { return "And(" + commaJoin(components) + ')'; } + private static final long serialVersionUID = 0; } @@ -173,6 +182,7 @@ public final class Predicates { public String toString() { return "Or(" + commaJoin(components) + ')'; } + private static final long serialVersionUID = 0; } @@ -215,4 +225,25 @@ public final class Predicates { sb.deleteCharAt(sb.length() - 1); return sb.toString(); } + + /** + * Collect real predicates for searching some data (see dependency effect code) + */ + public static void collectAllComponents(Predicate predicate, List res) { + if (predicate instanceof NotPredicate) { + res.add(((NotPredicate) predicate).predicate); + } else if (predicate instanceof AndPredicate) { + collectAllComponents(((AndPredicate) predicate).components, res); + } else if (predicate instanceof OrPredicate) { + collectAllComponents(((OrPredicate) predicate).components, res); + } else { + res.add(predicate); + } + } + + public static void collectAllComponents(List predicates, List res) { + predicates.forEach(p -> { + collectAllComponents(p, res); + }); + } }