From 73f9b8bef1e5f9ed2c141a4252c8fce6720eedec Mon Sep 17 00:00:00 2001 From: Martin Vandenbussche Date: Sun, 17 Nov 2019 13:25:32 +0100 Subject: [PATCH] Adding composite specification (Issue#1093) (#1094) * Resolution proposition to Issue#1055 (UML diagram left to do) * Deciding not to modify the UML diagram for now * Resolution proposition to Issue#1093 * Code reformatting --- specification/README.md | 98 +++++++++++++++---- .../com/iluwatar/specification/app/App.java | 33 +++++-- .../iluwatar/specification/property/Mass.java | 4 +- .../selector/AbstractSelector.java | 44 +++++++++ .../specification/selector/ColorSelector.java | 3 +- .../selector/ConjunctionSelector.java | 47 +++++++++ .../selector/DisjunctionSelector.java | 47 +++++++++ .../selector/MassEqualSelector.java | 47 +++++++++ .../selector/MassGreaterThanSelector.java | 11 ++- .../selector/MassSmallerThanOrEqSelector.java | 11 ++- .../selector/MovementSelector.java | 3 +- .../selector/NegationSelector.java | 46 +++++++++ .../specification/selector/SizeSelector.java | 3 +- .../iluwatar/specification/app/AppTest.java | 2 - .../specification/creature/CreatureTest.java | 43 ++++---- .../selector/ColorSelectorTest.java | 8 +- .../selector/CompositeSelectorsTest.java | 93 ++++++++++++++++++ .../selector/MassSelectorTest.java | 4 +- .../selector/MovementSelectorTest.java | 10 +- .../selector/SizeSelectorTest.java | 8 +- 20 files changed, 492 insertions(+), 73 deletions(-) create mode 100644 specification/src/main/java/com/iluwatar/specification/selector/AbstractSelector.java create mode 100644 specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java create mode 100644 specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java create mode 100644 specification/src/main/java/com/iluwatar/specification/selector/MassEqualSelector.java create mode 100644 specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java create mode 100644 specification/src/test/java/com/iluwatar/specification/selector/CompositeSelectorsTest.java diff --git a/specification/README.md b/specification/README.md index bb95097e3cc7..e55c0b799217 100644 --- a/specification/README.md +++ b/specification/README.md @@ -31,7 +31,8 @@ Use the Specification pattern when Real world example -> There is a pool of different creatures and we often need to select some subset of them. We can write our search specification such as "creatures that can fly" or "creatures heavier than 500 kilograms" and give it to the party that will perform the filtering. +> There is a pool of different creatures and we often need to select some subset of them. +> We can write our search specification such as "creatures that can fly", "creatures heavier than 500 kilograms", or as a combination of other search specifications, and then give it to the party that will perform the filtering. In Plain Words @@ -44,8 +45,10 @@ Wikipedia says **Programmatic Example** If we look at our creature pool example from above, we have a set of creatures with certain properties.\ -Those properties can be part of a pre-defined, limited set (represented here by the enums Size, Movement and Color); but they can also be discrete (e.g. the mass of a Creature). In this case, it is more appropriate to use what we call "parameterized specification", where the property value can be given as an argument when the Creature is created, allowing for more flexibility. - +Those properties can be part of a pre-defined, limited set (represented here by the enums Size, Movement and Color); but they can also be continuous values (e.g. the mass of a Creature). +In this case, it is more appropriate to use what we call "parameterized specification", where the property value can be given as an argument when the Creature is instantiated, allowing for more flexibility. +A third option is to combine pre-defined and/or parameterized properties using boolean logic, allowing for near-endless selection possibilities (this is called Composite Specification, see below). +The pros and cons of each approach are detailed in the table at the end of this document. ```java public interface Creature { String getName(); @@ -56,8 +59,7 @@ public interface Creature { } ``` -And dragon implementation looks like this. - +And ``Dragon`` implementation looks like this. ```java public class Dragon extends AbstractCreature { @@ -67,10 +69,9 @@ public class Dragon extends AbstractCreature { } ``` -Now that we want to select some subset of them, we use selectors. To select creatures that fly, we should use MovementSelector. - +Now that we want to select some subset of them, we use selectors. To select creatures that fly, we should use ``MovementSelector``. ```java -public class MovementSelector implements Predicate { +public class MovementSelector extends AbstractSelector { private final Movement movement; @@ -85,10 +86,9 @@ public class MovementSelector implements Predicate { } ``` -On the other hand, we selecting creatures heavier than a chosen amount, we use MassGreaterThanSelector. - +On the other hand, when selecting creatures heavier than a chosen amount, we use ``MassGreaterThanSelector``. ```java -public class MassGreaterThanSelector implements Predicate { +public class MassGreaterThanSelector extends AbstractSelector { private final Mass mass; @@ -103,20 +103,84 @@ public class MassGreaterThanSelector implements Predicate { } ``` -With these building blocks in place, we can perform a search for red and flying creatures like this. +With these building blocks in place, we can perform a search for red creatures as follows : +```java + List redCreatures = creatures.stream().filter(new ColorSelector(Color.RED)) + .collect(Collectors.toList()); +``` +But we could also use our parameterized selector like this : ```java - List redAndFlyingCreatures = creatures.stream() - .filter(new ColorSelector(Color.RED).and(new MovementSelector(Movement.FLYING))).collect(Collectors.toList()); + List heavyCreatures = creatures.stream().filter(new MassGreaterThanSelector(500.0) + .collect(Collectors.toList()); ``` -But we could also use our paramterized selector like this. +Our third option is to combine multiple selectors together. Performing a search for special creatures (defined as red, flying, and not small) could be done as follows : +```java + AbstractSelector specialCreaturesSelector = + new ColorSelector(Color.RED).and(new MovementSelector(Movement.FLYING)).and(new SizeSelector(Size.SMALL).not()); + + List specialCreatures = creatures.stream().filter(specialCreaturesSelector) + .collect(Collectors.toList()); +``` + +**More on Composite Specification** + +In Composite Specification, we will create custom instances of ``AbstractSelector`` by combining other selectors (called "leaves") using the three basic logical operators. +These are implemented in ``ConjunctionSelector``, ``DisjunctionSelector`` and ``NegationSelector``. +```java +public abstract class AbstractSelector implements Predicate { + + public AbstractSelector and(AbstractSelector other) { + return new ConjunctionSelector<>(this, other); + } + + public AbstractSelector or(AbstractSelector other) { + return new DisjunctionSelector<>(this, other); + } + public AbstractSelector not() { + return new NegationSelector<>(this); + } +} +``` ```java - List heavyCreatures = creatures.stream() - .filter(new MassGreaterThanSelector(500.0).collect(Collectors.toList()); +public class ConjunctionSelector extends AbstractSelector { + + private List> leafComponents; + + @SafeVarargs + ConjunctionSelector(AbstractSelector... selectors) { + this.leafComponents = List.of(selectors); + } + + /** + * Tests if *all* selectors pass the test. + */ + @Override + public boolean test(T t) { + return leafComponents.stream().allMatch(comp -> (comp.test(t))); + } +} ``` +All that is left to do is now to create leaf selectors (be it hard-coded or parameterized ones) that are as generic as possible, +and we will be able to instantiate the ``AbstractSelector`` class by combining any amount of selectors, as exemplified above. +We should be careful though, as it is easy to make a mistake when combining many logical operators; in particular, we should pay attention to the priority of the operations.\ +In general, Composite Specification is a great way to write more reusable code, as there is no need to create a Selector class for each filtering operation. +Instead, we just create an instance of ``AbstractSelector`` "on the spot", using tour generic "leaf" selectors and some basic boolean logic. + + +**Comparison of the different approaches** + +| Pattern | Usage | Pros | Cons | +|---|---|---|---| +| Hard-Coded Specification | Selection criteria are few and known in advance | + Easy to implement | - Inflexible | +| | | + Expressive | +| Parameterized Specification | Selection criteria are a large range of values (e.g. mass, speed,...) | + Some flexibility | - Still requires special-purpose classes | +| Composite Specification | There are a lot of selection criteria that can be combined in multiple ways, hence it is not feasible to create a class for each selector | + Very flexible, without requiring many specialized classes | - Somewhat more difficult to comprehend | +| | | + Supports logical operations | - You still need to create the base classes used as leaves | + ## Related patterns * Repository diff --git a/specification/src/main/java/com/iluwatar/specification/app/App.java b/specification/src/main/java/com/iluwatar/specification/app/App.java index c1ff8211e9a5..2f9633313d83 100644 --- a/specification/src/main/java/com/iluwatar/specification/app/App.java +++ b/specification/src/main/java/com/iluwatar/specification/app/App.java @@ -31,9 +31,10 @@ import com.iluwatar.specification.creature.Shark; import com.iluwatar.specification.creature.Troll; import com.iluwatar.specification.property.Color; -import com.iluwatar.specification.property.Mass; import com.iluwatar.specification.property.Movement; +import com.iluwatar.specification.selector.AbstractSelector; import com.iluwatar.specification.selector.ColorSelector; +import com.iluwatar.specification.selector.MassEqualSelector; import com.iluwatar.specification.selector.MassGreaterThanSelector; import com.iluwatar.specification.selector.MassSmallerThanOrEqSelector; import com.iluwatar.specification.selector.MovementSelector; @@ -77,13 +78,8 @@ public static void main(String[] args) { List darkCreatures = creatures.stream().filter(new ColorSelector(Color.DARK)).collect(Collectors.toList()); darkCreatures.forEach(c -> LOGGER.info(c.toString())); - // find all red and flying creatures - LOGGER.info("Find all red and flying creatures"); - List redAndFlyingCreatures = - creatures.stream() - .filter(new ColorSelector(Color.RED).and(new MovementSelector(Movement.FLYING))) - .collect(Collectors.toList()); - redAndFlyingCreatures.forEach(c -> LOGGER.info(c.toString())); + + LOGGER.info("\n"); // so-called "parameterized" specification LOGGER.info("Demonstrating parameterized specification :"); // find all creatures heavier than 500kg @@ -98,5 +94,26 @@ public static void main(String[] args) { creatures.stream().filter(new MassSmallerThanOrEqSelector(500.0)) .collect(Collectors.toList()); lightCreatures.forEach(c -> LOGGER.info(c.toString())); + + LOGGER.info("\n"); + // so-called "composite" specification + LOGGER.info("Demonstrating composite specification :"); + // find all red and flying creatures + LOGGER.info("Find all red and flying creatures"); + List redAndFlyingCreatures = + creatures.stream() + .filter(new ColorSelector(Color.RED).and(new MovementSelector(Movement.FLYING))) + .collect(Collectors.toList()); + redAndFlyingCreatures.forEach(c -> LOGGER.info(c.toString())); + // find all creatures dark or red, non-swimming, and heavier than or equal to 400kg + LOGGER.info("Find all scary creatures"); + AbstractSelector scaryCreaturesSelector = new ColorSelector(Color.DARK) + .or(new ColorSelector(Color.RED)).and(new MovementSelector(Movement.SWIMMING).not()) + .and(new MassGreaterThanSelector(400.0).or(new MassEqualSelector(400.0))); + List scaryCreatures = + creatures.stream() + .filter(scaryCreaturesSelector) + .collect(Collectors.toList()); + scaryCreatures.forEach(c -> LOGGER.info(c.toString())); } } diff --git a/specification/src/main/java/com/iluwatar/specification/property/Mass.java b/specification/src/main/java/com/iluwatar/specification/property/Mass.java index 6be1edb3316f..b2d6ddc66f5f 100644 --- a/specification/src/main/java/com/iluwatar/specification/property/Mass.java +++ b/specification/src/main/java/com/iluwatar/specification/property/Mass.java @@ -23,7 +23,9 @@ package com.iluwatar.specification.property; -/** Mass property. */ +/** + * Mass property. + */ public class Mass { private double value; diff --git a/specification/src/main/java/com/iluwatar/specification/selector/AbstractSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/AbstractSelector.java new file mode 100644 index 000000000000..2dea52271152 --- /dev/null +++ b/specification/src/main/java/com/iluwatar/specification/selector/AbstractSelector.java @@ -0,0 +1,44 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.specification.selector; + +import java.util.function.Predicate; + +/** + * Base class for selectors. + */ +public abstract class AbstractSelector implements Predicate { + + public AbstractSelector and(AbstractSelector other) { + return new ConjunctionSelector<>(this, other); + } + + public AbstractSelector or(AbstractSelector other) { + return new DisjunctionSelector<>(this, other); + } + + public AbstractSelector not() { + return new NegationSelector<>(this); + } +} diff --git a/specification/src/main/java/com/iluwatar/specification/selector/ColorSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/ColorSelector.java index 93caf612ff92..cefa5f92ec2a 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/ColorSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/ColorSelector.java @@ -25,12 +25,11 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Color; -import java.util.function.Predicate; /** * Color selector. */ -public class ColorSelector implements Predicate { +public class ColorSelector extends AbstractSelector { private final Color color; diff --git a/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java new file mode 100644 index 000000000000..bd29aa2603b7 --- /dev/null +++ b/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java @@ -0,0 +1,47 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.specification.selector; + +import java.util.List; + +/** + * A Selector defined as the conjunction (AND) of other (leaf) selectors. + */ +public class ConjunctionSelector extends AbstractSelector { + + private List> leafComponents; + + @SafeVarargs + ConjunctionSelector(AbstractSelector... selectors) { + this.leafComponents = List.of(selectors); + } + + /** + * Tests if *all* selectors pass the test. + */ + @Override + public boolean test(T t) { + return leafComponents.stream().allMatch(comp -> (comp.test(t))); + } +} diff --git a/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java new file mode 100644 index 000000000000..1fb38a43dc5b --- /dev/null +++ b/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java @@ -0,0 +1,47 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.specification.selector; + +import java.util.List; + +/** + * A Selector defined as the disjunction (OR) of other (leaf) selectors. + */ +public class DisjunctionSelector extends AbstractSelector { + + private List> leafComponents; + + @SafeVarargs + DisjunctionSelector(AbstractSelector... selectors) { + this.leafComponents = List.of(selectors); + } + + /** + * Tests if *at least one* selector passes the test. + */ + @Override + public boolean test(T t) { + return leafComponents.stream().anyMatch(comp -> comp.test(t)); + } +} diff --git a/specification/src/main/java/com/iluwatar/specification/selector/MassEqualSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/MassEqualSelector.java new file mode 100644 index 000000000000..e2c0c1e7cf0a --- /dev/null +++ b/specification/src/main/java/com/iluwatar/specification/selector/MassEqualSelector.java @@ -0,0 +1,47 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.specification.selector; + +import com.iluwatar.specification.creature.Creature; +import com.iluwatar.specification.property.Mass; + +/** + * Mass selector for values exactly equal than the parameter. + */ +public class MassEqualSelector extends AbstractSelector { + + private final Mass mass; + + /** + * The use of a double as a parameter will spare some typing when instantiating this class. + */ + public MassEqualSelector(double mass) { + this.mass = new Mass(mass); + } + + @Override + public boolean test(Creature t) { + return t.getMass().equals(mass); + } +} diff --git a/specification/src/main/java/com/iluwatar/specification/selector/MassGreaterThanSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/MassGreaterThanSelector.java index d613e6a08efc..56160c12ad58 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/MassGreaterThanSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/MassGreaterThanSelector.java @@ -25,14 +25,17 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Mass; -import java.util.function.Predicate; -/** Mass selector for values greater than the parameter. */ -public class MassGreaterThanSelector implements Predicate { +/** + * Mass selector for values greater than the parameter. + */ +public class MassGreaterThanSelector extends AbstractSelector { private final Mass mass; - /** The use of a double as a parameter will spare some typing when instantiating this class. */ + /** + * The use of a double as a parameter will spare some typing when instantiating this class. + */ public MassGreaterThanSelector(double mass) { this.mass = new Mass(mass); } diff --git a/specification/src/main/java/com/iluwatar/specification/selector/MassSmallerThanOrEqSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/MassSmallerThanOrEqSelector.java index 8a6a9f80e2d5..94ab3cc9a1a8 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/MassSmallerThanOrEqSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/MassSmallerThanOrEqSelector.java @@ -25,14 +25,17 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Mass; -import java.util.function.Predicate; -/** Mass selector for values smaller or equal to the parameter. */ -public class MassSmallerThanOrEqSelector implements Predicate { +/** + * Mass selector for values smaller or equal to the parameter. + */ +public class MassSmallerThanOrEqSelector extends AbstractSelector { private final Mass mass; - /** The use of a double as a parameter will spare some typing when instantiating this class. */ + /** + * The use of a double as a parameter will spare some typing when instantiating this class. + */ public MassSmallerThanOrEqSelector(double mass) { this.mass = new Mass(mass); } diff --git a/specification/src/main/java/com/iluwatar/specification/selector/MovementSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/MovementSelector.java index 1818058c15d8..e2a0bdb15fad 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/MovementSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/MovementSelector.java @@ -25,12 +25,11 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Movement; -import java.util.function.Predicate; /** * Movement selector. */ -public class MovementSelector implements Predicate { +public class MovementSelector extends AbstractSelector { private final Movement movement; diff --git a/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java new file mode 100644 index 000000000000..ad3063000845 --- /dev/null +++ b/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java @@ -0,0 +1,46 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.specification.selector; + + +/** + * A Selector defined as the negation (NOT) of a (leaf) selectors. This is of course only useful + * when used in combination with other composite selectors. + */ +public class NegationSelector extends AbstractSelector { + + private AbstractSelector component; + + NegationSelector(AbstractSelector selector) { + this.component = selector; + } + + /** + * Tests if the selector fails the test (yes). + */ + @Override + public boolean test(T t) { + return !(component.test(t)); + } +} diff --git a/specification/src/main/java/com/iluwatar/specification/selector/SizeSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/SizeSelector.java index a997c034239d..6d5aa85d0bfe 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/SizeSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/SizeSelector.java @@ -25,12 +25,11 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Size; -import java.util.function.Predicate; /** * Size selector. */ -public class SizeSelector implements Predicate { +public class SizeSelector extends AbstractSelector { private final Size size; diff --git a/specification/src/test/java/com/iluwatar/specification/app/AppTest.java b/specification/src/test/java/com/iluwatar/specification/app/AppTest.java index 5f6d950fe6f4..7585a113d34d 100644 --- a/specification/src/test/java/com/iluwatar/specification/app/AppTest.java +++ b/specification/src/test/java/com/iluwatar/specification/app/AppTest.java @@ -26,9 +26,7 @@ import org.junit.jupiter.api.Test; /** - * * Application test - * */ public class AppTest { diff --git a/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java b/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java index ecd3de27f541..58af7248a867 100644 --- a/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java +++ b/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java @@ -23,18 +23,17 @@ package com.iluwatar.specification.creature; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import com.iluwatar.specification.property.Color; import com.iluwatar.specification.property.Mass; import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - import java.util.Collection; import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /** * Date: 12/29/15 - 7:47 PM @@ -48,12 +47,18 @@ public class CreatureTest { */ public static Collection dataProvider() { return List.of( - new Object[]{new Dragon(), "Dragon", Size.LARGE, Movement.FLYING, Color.RED, new Mass(39300.0)}, - new Object[]{new Goblin(), "Goblin", Size.SMALL, Movement.WALKING, Color.GREEN, new Mass(30.0)}, - new Object[]{new KillerBee(), "KillerBee", Size.SMALL, Movement.FLYING, Color.LIGHT, new Mass(6.7)}, - new Object[]{new Octopus(), "Octopus", Size.NORMAL, Movement.SWIMMING, Color.DARK, new Mass(12.0)}, - new Object[]{new Shark(), "Shark", Size.NORMAL, Movement.SWIMMING, Color.LIGHT, new Mass(500.0)}, - new Object[]{new Troll(), "Troll", Size.LARGE, Movement.WALKING, Color.DARK, new Mass(4000.0)} + new Object[]{new Dragon(), "Dragon", Size.LARGE, Movement.FLYING, Color.RED, + new Mass(39300.0)}, + new Object[]{new Goblin(), "Goblin", Size.SMALL, Movement.WALKING, Color.GREEN, + new Mass(30.0)}, + new Object[]{new KillerBee(), "KillerBee", Size.SMALL, Movement.FLYING, Color.LIGHT, + new Mass(6.7)}, + new Object[]{new Octopus(), "Octopus", Size.NORMAL, Movement.SWIMMING, Color.DARK, + new Mass(12.0)}, + new Object[]{new Shark(), "Shark", Size.NORMAL, Movement.SWIMMING, Color.LIGHT, + new Mass(500.0)}, + new Object[]{new Troll(), "Troll", Size.LARGE, Movement.WALKING, Color.DARK, + new Mass(4000.0)} ); } @@ -77,24 +82,28 @@ public void testGetMovement(Creature testedCreature, String name, Size size, Mov @ParameterizedTest @MethodSource("dataProvider") - public void testGetColor(Creature testedCreature, String name, Size size, Movement movement, Color color) { + public void testGetColor(Creature testedCreature, String name, Size size, Movement movement, + Color color) { assertEquals(color, testedCreature.getColor()); } @ParameterizedTest @MethodSource("dataProvider") - public void testGetMass(Creature testedCreature, String name, Size size, Movement movement, Color color, Mass mass) { + public void testGetMass(Creature testedCreature, String name, Size size, Movement movement, + Color color, Mass mass) { assertEquals(mass, testedCreature.getMass()); } @ParameterizedTest @MethodSource("dataProvider") - public void testToString(Creature testedCreature, String name, Size size, Movement movement, Color color, Mass mass) { + public void testToString(Creature testedCreature, String name, Size size, Movement movement, + Color color, Mass mass) { final String toString = testedCreature.toString(); assertNotNull(toString); assertEquals( - String.format("%s [size=%s, movement=%s, color=%s, mass=%s]", name, size, movement, color, mass), - toString + String.format("%s [size=%s, movement=%s, color=%s, mass=%s]", name, size, movement, color, + mass), + toString ); } } \ No newline at end of file diff --git a/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java index cc47ba5955e2..f1029063dc50 100644 --- a/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java +++ b/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java @@ -23,15 +23,15 @@ package com.iluwatar.specification.selector; -import com.iluwatar.specification.creature.Creature; -import com.iluwatar.specification.property.Color; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.iluwatar.specification.creature.Creature; +import com.iluwatar.specification.property.Color; +import org.junit.jupiter.api.Test; + /** * Date: 12/29/15 - 7:35 PM * diff --git a/specification/src/test/java/com/iluwatar/specification/selector/CompositeSelectorsTest.java b/specification/src/test/java/com/iluwatar/specification/selector/CompositeSelectorsTest.java new file mode 100644 index 000000000000..75809c54280f --- /dev/null +++ b/specification/src/test/java/com/iluwatar/specification/selector/CompositeSelectorsTest.java @@ -0,0 +1,93 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.specification.selector; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.iluwatar.specification.creature.Creature; +import com.iluwatar.specification.property.Mass; +import com.iluwatar.specification.property.Movement; +import org.junit.jupiter.api.Test; + +public class CompositeSelectorsTest { + + /** + * Verify if the conjunction selector gives the correct results. + */ + @Test + public void testAndComposition() { + final Creature swimmingHeavyCreature = mock(Creature.class); + when(swimmingHeavyCreature.getMovement()).thenReturn(Movement.SWIMMING); + when(swimmingHeavyCreature.getMass()).thenReturn(new Mass(100.0)); + + final Creature swimmingLightCreature = mock(Creature.class); + when(swimmingLightCreature.getMovement()).thenReturn(Movement.SWIMMING); + when(swimmingLightCreature.getMass()).thenReturn(new Mass(25.0)); + + final AbstractSelector lightAndSwimmingSelector = new MassSmallerThanOrEqSelector( + 50.0).and(new MovementSelector(Movement.SWIMMING)); + assertFalse(lightAndSwimmingSelector.test(swimmingHeavyCreature)); + assertTrue(lightAndSwimmingSelector.test(swimmingLightCreature)); + } + + /** + * Verify if the disjunction selector gives the correct results. + */ + @Test + public void testOrComposition() { + final Creature swimmingHeavyCreature = mock(Creature.class); + when(swimmingHeavyCreature.getMovement()).thenReturn(Movement.SWIMMING); + when(swimmingHeavyCreature.getMass()).thenReturn(new Mass(100.0)); + + final Creature swimmingLightCreature = mock(Creature.class); + when(swimmingLightCreature.getMovement()).thenReturn(Movement.SWIMMING); + when(swimmingLightCreature.getMass()).thenReturn(new Mass(25.0)); + + final AbstractSelector lightOrSwimmingSelector = new MassSmallerThanOrEqSelector(50.0) + .or(new MovementSelector(Movement.SWIMMING)); + assertTrue(lightOrSwimmingSelector.test(swimmingHeavyCreature)); + assertTrue(lightOrSwimmingSelector.test(swimmingLightCreature)); + } + + /** + * Verify if the negation selector gives the correct results. + */ + @Test + public void testNotComposition() { + final Creature swimmingHeavyCreature = mock(Creature.class); + when(swimmingHeavyCreature.getMovement()).thenReturn(Movement.SWIMMING); + when(swimmingHeavyCreature.getMass()).thenReturn(new Mass(100.0)); + + final Creature swimmingLightCreature = mock(Creature.class); + when(swimmingLightCreature.getMovement()).thenReturn(Movement.SWIMMING); + when(swimmingLightCreature.getMass()).thenReturn(new Mass(25.0)); + + final AbstractSelector heavySelector = new MassSmallerThanOrEqSelector(50.0).not(); + assertTrue(heavySelector.test(swimmingHeavyCreature)); + assertFalse(heavySelector.test(swimmingLightCreature)); + } +} diff --git a/specification/src/test/java/com/iluwatar/specification/selector/MassSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/MassSelectorTest.java index eb2f4c5fe7ae..287f401648e6 100644 --- a/specification/src/test/java/com/iluwatar/specification/selector/MassSelectorTest.java +++ b/specification/src/test/java/com/iluwatar/specification/selector/MassSelectorTest.java @@ -34,7 +34,9 @@ public class MassSelectorTest { - /** Verify if the mass selector gives the correct results */ + /** + * Verify if the mass selector gives the correct results. + */ @Test public void testMass() { final Creature lightCreature = mock(Creature.class); diff --git a/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java index e194790fbc2c..f3d2482ad9fc 100644 --- a/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java +++ b/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java @@ -23,15 +23,15 @@ package com.iluwatar.specification.selector; -import com.iluwatar.specification.creature.Creature; -import com.iluwatar.specification.property.Movement; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.iluwatar.specification.creature.Creature; +import com.iluwatar.specification.property.Movement; +import org.junit.jupiter.api.Test; + /** * Date: 12/29/15 - 7:37 PM * @@ -40,7 +40,7 @@ public class MovementSelectorTest { /** - * Verify if the movement selector gives the correct results + * Verify if the movement selector gives the correct results. */ @Test public void testMovement() { diff --git a/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java index 9145f9b810c6..7dcb30ee22f9 100644 --- a/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java +++ b/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java @@ -23,15 +23,15 @@ package com.iluwatar.specification.selector; -import com.iluwatar.specification.creature.Creature; -import com.iluwatar.specification.property.Size; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.iluwatar.specification.creature.Creature; +import com.iluwatar.specification.property.Size; +import org.junit.jupiter.api.Test; + /** * Date: 12/29/15 - 7:43 PM *