diff --git a/CONTRIBUTING.MD b/CONTRIBUTING.MD new file mode 100644 index 000000000..39087fbf1 --- /dev/null +++ b/CONTRIBUTING.MD @@ -0,0 +1,4 @@ +This is great you have something to contribute! + +Before going any further please read the [wiki](https://github.com/iluwatar/java-design-patterns/wiki) +with conventions and rules we used for this project. diff --git a/README.md b/README.md index ac3aadd67..f1ce0bfc7 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,10 @@ that smart and dearly wants an empty line before a heading to be able to display it as such, e.g. website) --> -# Design pattern samples in Java +# Design patterns implemented in Java [![Build status](https://travis-ci.org/iluwatar/java-design-patterns.svg?branch=master)](https://travis-ci.org/iluwatar/java-design-patterns) [![Coverage Status](https://coveralls.io/repos/iluwatar/java-design-patterns/badge.svg?branch=master)](https://coveralls.io/r/iluwatar/java-design-patterns?branch=master) -[![Coverity Scan Build Status](https://scan.coverity.com/projects/5634/badge.svg)](https://scan.coverity.com/projects/5634) [![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) # Introduction @@ -40,7 +39,7 @@ patterns by any of the following approaches # How to contribute -If you are willing to contribute to the project you will find the relevant information in our [developer wiki](https://github.com/iluwatar/java-design-patterns/wiki). +If you are willing to contribute to the project you will find the relevant information in our [developer wiki](https://github.com/iluwatar/java-design-patterns/wiki). We will help you and answer your questions in the [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns). # Credits diff --git a/abstract-factory/index.md b/abstract-factory/index.md index f824f7e0e..f31ed2d01 100644 --- a/abstract-factory/index.md +++ b/abstract-factory/index.md @@ -7,6 +7,7 @@ categories: Creational tags: - Java - Gang Of Four + - Difficulty-Intermediate --- **Also known as:** Kit diff --git a/abstract-factory/pom.xml b/abstract-factory/pom.xml index f8c7fa106..71ea6dc98 100644 --- a/abstract-factory/pom.xml +++ b/abstract-factory/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT abstract-factory diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java index 9a32a091a..cdde3bd8f 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java @@ -1,20 +1,18 @@ package com.iluwatar.abstractfactory; - /** * - * The Abstract Factory pattern provides a way to encapsulate a group of individual factories that - * have a common theme without specifying their concrete classes. In normal usage, the client - * software creates a concrete implementation of the abstract factory and then uses the generic - * interface of the factory to create the concrete objects that are part of the theme. The client - * does not know (or care) which concrete objects it gets from each of these internal factories, - * since it uses only the generic interfaces of their products. This pattern separates the details - * of implementation of a set of objects from their general usage and relies on object composition, - * as object creation is implemented in methods exposed in the factory interface. + * The Abstract Factory pattern provides a way to encapsulate a group of individual factories that have a common theme + * without specifying their concrete classes. In normal usage, the client software creates a concrete implementation of + * the abstract factory and then uses the generic interface of the factory to create the concrete objects that are part + * of the theme. The client does not know (or care) which concrete objects it gets from each of these internal + * factories, since it uses only the generic interfaces of their products. This pattern separates the details of + * implementation of a set of objects from their general usage and relies on object composition, as object creation is + * implemented in methods exposed in the factory interface. *

- * The essence of the Abstract Factory pattern is a factory interface ({@link KingdomFactory}) and - * its implementations ({@link ElfKingdomFactory}, {@link OrcKingdomFactory}). The example uses both - * concrete implementations to create a king, a castle and an army. + * The essence of the Abstract Factory pattern is a factory interface ({@link KingdomFactory}) and its implementations ( + * {@link ElfKingdomFactory}, {@link OrcKingdomFactory}). The example uses both concrete implementations to create a + * king, a castle and an army. * */ public class App { @@ -23,11 +21,8 @@ public class App { private Castle castle; private Army army; - /** * Creates kingdom - * - * @param factory */ public void createKingdom(final KingdomFactory factory) { setKing(factory.createKing()); @@ -47,14 +42,6 @@ public class App { return factory.createKing(); } - Castle getCastle(final KingdomFactory factory) { - return factory.createCastle(); - } - - Army getArmy(final KingdomFactory factory) { - return factory.createArmy(); - } - public King getKing() { return king; } @@ -62,6 +49,10 @@ public class App { private void setKing(final King king) { this.king = king; } + + Castle getCastle(final KingdomFactory factory) { + return factory.createCastle(); + } public Castle getCastle() { return castle; @@ -70,6 +61,10 @@ public class App { private void setCastle(final Castle castle) { this.castle = castle; } + + Army getArmy(final KingdomFactory factory) { + return factory.createArmy(); + } public Army getArmy() { return army; @@ -79,32 +74,32 @@ public class App { this.army = army; } - /** * Program entry point * - * @param args command line args + * @param args + * command line args */ public static void main(String[] args) { - - App app = new App(); - - System.out.println("Elf Kingdom"); - KingdomFactory elfKingdomFactory; - elfKingdomFactory = app.getElfKingdomFactory(); - app.createKingdom(elfKingdomFactory); - System.out.println(app.getArmy().getDescription()); - System.out.println(app.getCastle().getDescription()); - System.out.println(app.getKing().getDescription()); - - System.out.println("\nOrc Kingdom"); - KingdomFactory orcKingdomFactory; - orcKingdomFactory = app.getOrcKingdomFactory(); - app.createKingdom(orcKingdomFactory); - System.out.println(app.getArmy().getDescription()); - System.out.println(app.getCastle().getDescription()); - System.out.println(app.getKing().getDescription()); - + + App app = new App(); + + System.out.println("Elf Kingdom"); + KingdomFactory elfKingdomFactory; + elfKingdomFactory = app.getElfKingdomFactory(); + app.createKingdom(elfKingdomFactory); + System.out.println(app.getArmy().getDescription()); + System.out.println(app.getCastle().getDescription()); + System.out.println(app.getKing().getDescription()); + + System.out.println("\nOrc Kingdom"); + KingdomFactory orcKingdomFactory; + orcKingdomFactory = app.getOrcKingdomFactory(); + app.createKingdom(orcKingdomFactory); + System.out.println(app.getArmy().getDescription()); + System.out.println(app.getCastle().getDescription()); + System.out.println(app.getKing().getDescription()); + } - + } diff --git a/adapter/etc/adapter.png b/adapter/etc/adapter.png index 511bb5880..f43358b04 100644 Binary files a/adapter/etc/adapter.png and b/adapter/etc/adapter.png differ diff --git a/adapter/etc/adapter.ucls b/adapter/etc/adapter.ucls index 8c09f0399..290ff544e 100644 --- a/adapter/etc/adapter.ucls +++ b/adapter/etc/adapter.ucls @@ -1,61 +1,61 @@ - - - + + + - - + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + - - - - - - - - + + + + - + + + + + - - + + diff --git a/adapter/etc/adapter_1.png b/adapter/etc/adapter_1.png deleted file mode 100644 index 64eb34b84..000000000 Binary files a/adapter/etc/adapter_1.png and /dev/null differ diff --git a/adapter/index.md b/adapter/index.md index 36a2a0ad3..f77018e45 100644 --- a/adapter/index.md +++ b/adapter/index.md @@ -7,6 +7,7 @@ categories: Structural tags: - Java - Gang Of Four + - Difficulty-Beginner --- **Also known as:** Wrapper @@ -15,7 +16,7 @@ tags: expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces. -![alt text](./etc/adapter_1.png "Adapter") +![alt text](./etc/adapter.png "Adapter") **Applicability:** Use the Adapter pattern when diff --git a/adapter/pom.xml b/adapter/pom.xml index ddb81441f..736ce16ec 100644 --- a/adapter/pom.xml +++ b/adapter/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT adapter diff --git a/adapter/src/main/java/com/iluwatar/adapter/App.java b/adapter/src/main/java/com/iluwatar/adapter/App.java index d2353eec4..d57cb91e4 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/App.java +++ b/adapter/src/main/java/com/iluwatar/adapter/App.java @@ -5,14 +5,23 @@ package com.iluwatar.adapter; * for an adapter. Interfaces may be incompatible but the inner functionality should suit the need. * The Adapter design pattern allows otherwise incompatible classes to work together by converting * the interface of one class into an interface expected by the clients. - * - *

There are two variations of the Adapter pattern: The class adapter implements the adaptee's + * + *

+ * There are two variations of the Adapter pattern: The class adapter implements the adaptee's * interface whereas the object adapter uses composition to contain the adaptee in the adapter * object. This example uses the object adapter approach. - * - *

The Adapter ({@link GnomeEngineer}) converts the interface of the target class ( - * {@link GoblinGlider}) into a suitable one expected by the client ({@link GnomeEngineeringManager} - * ). + * + *

+ * The Adapter ({@link BattleFishingBoat}) converts the interface of the adaptee class ( + * {@link FishingBoat}) into a suitable one expected by the client ( {@link BattleShip} ). + * + *

+ * The story of this implementation is this.
+ * Pirates are coming! we need a {@link BattleShip} to fight! We have a {@link FishingBoat} and our + * captain. We have no time to make up a new ship! we need to reuse this {@link FishingBoat}. The + * captain needs a battleship which can fire and move. The spec is in {@link BattleShip}. We will + * use the Adapter pattern to reuse {@link FishingBoat}. + * */ public class App { @@ -22,7 +31,8 @@ public class App { * @param args command line args */ public static void main(String[] args) { - Engineer manager = new GnomeEngineeringManager(new GnomeEngineer()); - manager.operateDevice(); + Captain captain = new Captain(new BattleFishingBoat()); + captain.move(); + captain.fire(); } } diff --git a/adapter/src/main/java/com/iluwatar/adapter/BattleFishingBoat.java b/adapter/src/main/java/com/iluwatar/adapter/BattleFishingBoat.java new file mode 100644 index 000000000..3f573337f --- /dev/null +++ b/adapter/src/main/java/com/iluwatar/adapter/BattleFishingBoat.java @@ -0,0 +1,29 @@ +package com.iluwatar.adapter; + +/** + * + * Adapter class. Adapts the interface of the device ({@link FishingBoat}) into {@link BattleShip} + * interface expected by the client ({@link Captain}).
+ * In this case we added a new function fire to suit the interface. We are reusing the + * {@link FishingBoat} without changing itself. The Adapter class can just map the functions of the + * Adaptee or add, delete features of the Adaptee. + * + */ +public class BattleFishingBoat implements BattleShip { + + private FishingBoat boat; + + public BattleFishingBoat() { + boat = new FishingBoat(); + } + + @Override + public void fire() { + System.out.println("fire!"); + } + + @Override + public void move() { + boat.sail(); + } +} diff --git a/adapter/src/main/java/com/iluwatar/adapter/BattleShip.java b/adapter/src/main/java/com/iluwatar/adapter/BattleShip.java new file mode 100644 index 000000000..d4f6036e6 --- /dev/null +++ b/adapter/src/main/java/com/iluwatar/adapter/BattleShip.java @@ -0,0 +1,14 @@ +package com.iluwatar.adapter; + +/** + * The interface expected by the client.
+ * A Battleship can fire and move. + * + */ +public interface BattleShip { + + void fire(); + + void move(); + +} diff --git a/adapter/src/main/java/com/iluwatar/adapter/Captain.java b/adapter/src/main/java/com/iluwatar/adapter/Captain.java new file mode 100644 index 000000000..8c48daf69 --- /dev/null +++ b/adapter/src/main/java/com/iluwatar/adapter/Captain.java @@ -0,0 +1,33 @@ +package com.iluwatar.adapter; + +/** + * The Captain uses {@link BattleShip} to fight.
+ * This is the client in the pattern. + */ +public class Captain implements BattleShip { + + private BattleShip battleship; + + public Captain() { + + } + + public Captain(BattleShip battleship) { + this.battleship = battleship; + } + + public void setBattleship(BattleShip battleship) { + this.battleship = battleship; + } + + @Override + public void fire() { + battleship.fire(); + } + + @Override + public void move() { + battleship.move(); + } + +} diff --git a/adapter/src/main/java/com/iluwatar/adapter/Engineer.java b/adapter/src/main/java/com/iluwatar/adapter/Engineer.java deleted file mode 100644 index a973cb530..000000000 --- a/adapter/src/main/java/com/iluwatar/adapter/Engineer.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.iluwatar.adapter; - -/** - * - * Engineers can operate devices. - * - */ -public interface Engineer { - - void operateDevice(); -} diff --git a/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java b/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java new file mode 100644 index 000000000..509bb8cdb --- /dev/null +++ b/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java @@ -0,0 +1,18 @@ +package com.iluwatar.adapter; + +/** + * + * Device class (adaptee in the pattern). We want to reuse this class + * + */ +public class FishingBoat { + + public void sail() { + System.out.println("The Boat is moving to that place"); + } + + public void fish() { + System.out.println("fishing ..."); + } + +} diff --git a/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineer.java b/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineer.java deleted file mode 100644 index 70e166ac3..000000000 --- a/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineer.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.iluwatar.adapter; - -/** - * - * Adapter class. Adapts the interface of the device ({@link GoblinGlider}) into {@link Engineer} - * interface expected by the client ({@link GnomeEngineeringManager}). - * - */ -public class GnomeEngineer implements Engineer { - - private GoblinGlider glider; - - public GnomeEngineer() { - glider = new GoblinGlider(); - } - - @Override - public void operateDevice() { - glider.attachGlider(); - glider.gainSpeed(); - glider.takeOff(); - } -} diff --git a/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineeringManager.java b/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineeringManager.java deleted file mode 100644 index ff4ddb617..000000000 --- a/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineeringManager.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.iluwatar.adapter; - -/** - * GnomeEngineering manager uses {@link Engineer} to operate devices. - */ -public class GnomeEngineeringManager implements Engineer { - - private Engineer engineer; - - public GnomeEngineeringManager() { - - } - - public GnomeEngineeringManager(Engineer engineer) { - this.engineer = engineer; - } - - @Override - public void operateDevice() { - engineer.operateDevice(); - } - - public void setEngineer(Engineer engineer) { - this.engineer = engineer; - } -} diff --git a/adapter/src/main/java/com/iluwatar/adapter/GoblinGlider.java b/adapter/src/main/java/com/iluwatar/adapter/GoblinGlider.java deleted file mode 100644 index 79a9acef6..000000000 --- a/adapter/src/main/java/com/iluwatar/adapter/GoblinGlider.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.iluwatar.adapter; - -/** - * - * Device class (adaptee in the pattern). - * - */ -public class GoblinGlider { - - public void attachGlider() { - System.out.println("Glider attached."); - } - - public void gainSpeed() { - System.out.println("Gaining speed."); - } - - public void takeOff() { - System.out.println("Lift-off!"); - } -} diff --git a/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java b/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java index 866bfb968..9fce02a3c 100644 --- a/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java +++ b/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java @@ -1,37 +1,25 @@ package com.iluwatar.adapter; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -import java.util.HashMap; -import java.util.Map; - import org.junit.Before; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + /** - * An adapter helps two incompatible interfaces to work together. This is the real world definition - * for an adapter. Interfaces may be incompatible but the inner functionality should suit the need. - * The Adapter design pattern allows otherwise incompatible classes to work together by converting - * the interface of one class into an interface expected by the clients. + * Test class * - *

There are two variations of the Adapter pattern: - * The class adapter implements the adaptee's - * interface whereas the object adapter uses composition to contain the adaptee in the adapter - * object. This example uses the object adapter approach. - * - *

The Adapter ({@link GnomeEngineer}) converts the interface - * of the target class ({@link GoblinGlider}) into a suitable one expected by - * the client ({@link GnomeEngineeringManager} - * ). */ public class AdapterPatternTest { private Map beans; - private static final String ENGINEER_BEAN = "engineer"; + private static final String BATTLESHIP_BEAN = "engineer"; - private static final String MANAGER_BEAN = "manager"; + private static final String CAPTAIN_BEAN = "captain"; /** * This method runs before the test execution and sets the bean objects in the beans Map. @@ -40,29 +28,34 @@ public class AdapterPatternTest { public void setup() { beans = new HashMap<>(); - GnomeEngineer gnomeEngineer = spy(new GnomeEngineer()); - beans.put(ENGINEER_BEAN, gnomeEngineer); + BattleFishingBoat battleFishingBoat = spy(new BattleFishingBoat()); + beans.put(BATTLESHIP_BEAN, battleFishingBoat); - GnomeEngineeringManager manager = new GnomeEngineeringManager(); - manager.setEngineer((GnomeEngineer) beans.get(ENGINEER_BEAN)); - beans.put(MANAGER_BEAN, manager); + Captain captain = new Captain(); + captain.setBattleship((BattleFishingBoat) beans.get(BATTLESHIP_BEAN)); + beans.put(CAPTAIN_BEAN, captain); } /** - * This test asserts that when we call operateDevice() method on a manager bean, it is internally - * calling operateDevice method on the engineer object. The Adapter ({@link GnomeEngineer}) - * converts the interface of the target class ( {@link GoblinGlider}) into a suitable one expected - * by the client ({@link GnomeEngineeringManager} ). + * This test asserts that when we use the move() method on a captain bean(client), it is + * internally calling move method on the battleship object. The Adapter ({@link BattleFishingBoat} + * ) converts the interface of the target class ( {@link FishingBoat}) into a suitable one + * expected by the client ({@link Captain} ). */ @Test public void testAdapter() { - Engineer manager = (Engineer) beans.get(MANAGER_BEAN); + BattleShip captain = (BattleShip) beans.get(CAPTAIN_BEAN); - // when manager is asked to operate device - manager.operateDevice(); + // when captain moves + captain.move(); + + // the captain internally calls the battleship object to move + BattleShip battleship = (BattleShip) beans.get(BATTLESHIP_BEAN); + verify(battleship).move(); + + // same with above with firing + captain.fire(); + verify(battleship).fire(); - // Manager internally calls the engineer object to operateDevice - Engineer engineer = (Engineer) beans.get(ENGINEER_BEAN); - verify(engineer).operateDevice(); } } diff --git a/async-method-invocation/index.md b/async-method-invocation/index.md index dfcee0208..b9fadb886 100644 --- a/async-method-invocation/index.md +++ b/async-method-invocation/index.md @@ -4,7 +4,10 @@ title: Async Method Invocation folder: async-method-invocation permalink: /patterns/async-method-invocation/ categories: Concurrency -tags: Java +tags: + - Java + - Difficulty-Intermediate + - Functional --- **Intent:** Asynchronous method invocation is pattern where the calling thread diff --git a/async-method-invocation/pom.xml b/async-method-invocation/pom.xml index 87dd343ac..3f2a62aee 100644 --- a/async-method-invocation/pom.xml +++ b/async-method-invocation/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT async-method-invocation @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java index 0b8ee3649..6f2d4a8fc 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java @@ -4,24 +4,23 @@ import java.util.concurrent.Callable; /** * This application demonstrates the async method invocation pattern. Key parts of the pattern are - * AsyncResult which is an intermediate container for an asynchronously evaluated - * value, AsyncCallback which can be provided to be executed on task completion and - * AsyncExecutor that manages the execution of the async tasks. + * AsyncResult which is an intermediate container for an asynchronously evaluated value, + * AsyncCallback which can be provided to be executed on task completion and AsyncExecutor + * that manages the execution of the async tasks. *

- * The main method shows example flow of async invocations. The main thread starts multiple tasks - * with variable durations and then continues its own work. When the main thread has done it's job - * it collects the results of the async tasks. Two of the tasks are handled with callbacks, meaning - * the callbacks are executed immediately when the tasks complete. + * The main method shows example flow of async invocations. The main thread starts multiple tasks with variable + * durations and then continues its own work. When the main thread has done it's job it collects the results of the + * async tasks. Two of the tasks are handled with callbacks, meaning the callbacks are executed immediately when the + * tasks complete. *

- * Noteworthy difference of thread usage between the async results and callbacks is that the async - * results are collected in the main thread but the callbacks are executed within the worker - * threads. This should be noted when working with thread pools. + * Noteworthy difference of thread usage between the async results and callbacks is that the async results are collected + * in the main thread but the callbacks are executed within the worker threads. This should be noted when working with + * thread pools. *

- * Java provides its own implementations of async method invocation pattern. FutureTask, - * CompletableFuture and ExecutorService are the real world implementations of this pattern. But due - * to the nature of parallel programming, the implementations are not trivial. This example does not - * take all possible scenarios into account but rather provides a simple version that helps to - * understand the pattern. + * Java provides its own implementations of async method invocation pattern. FutureTask, CompletableFuture and + * ExecutorService are the real world implementations of this pattern. But due to the nature of parallel programming, + * the implementations are not trivial. This example does not take all possible scenarios into account but rather + * provides a simple version that helps to understand the pattern. * * @see AsyncResult * @see AsyncCallback @@ -33,6 +32,9 @@ import java.util.concurrent.Callable; */ public class App { + /** + * Program entry point + */ public static void main(String[] args) throws Exception { // construct a new executor that will run async tasks AsyncExecutor executor = new ThreadAsyncExecutor(); @@ -41,10 +43,8 @@ public class App { AsyncResult asyncResult1 = executor.startProcess(lazyval(10, 500)); AsyncResult asyncResult2 = executor.startProcess(lazyval("test", 300)); AsyncResult asyncResult3 = executor.startProcess(lazyval(50L, 700)); - AsyncResult asyncResult4 = - executor.startProcess(lazyval(20, 400), callback("Callback result 4")); - AsyncResult asyncResult5 = - executor.startProcess(lazyval("callback", 600), callback("Callback result 5")); + AsyncResult asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4")); + AsyncResult asyncResult5 = executor.startProcess(lazyval("callback", 600), callback("Callback result 5")); // emulate processing in the current thread while async tasks are running in their own threads Thread.sleep(350); // Oh boy I'm working hard here @@ -66,8 +66,10 @@ public class App { /** * Creates a callable that lazily evaluates to given value with artificial delay. * - * @param value value to evaluate - * @param delayMillis artificial delay in milliseconds + * @param value + * value to evaluate + * @param delayMillis + * artificial delay in milliseconds * @return new callable for lazy evaluation */ private static Callable lazyval(T value, long delayMillis) { @@ -81,7 +83,8 @@ public class App { /** * Creates a simple callback that logs the complete status of the async result. * - * @param name callback name + * @param name + * callback name * @return new async callback */ private static AsyncCallback callback(String name) { diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java index 6d77df8ec..d64180dad 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java @@ -5,8 +5,6 @@ import java.util.concurrent.ExecutionException; /** * * AsyncResult interface - * - * @param */ public interface AsyncResult { diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java index 300934562..6e86b26e4 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java @@ -29,13 +29,12 @@ public class ThreadAsyncExecutor implements AsyncExecutor { } catch (Exception ex) { result.setException(ex); } - }, "executor-" + idx.incrementAndGet()).start(); + } , "executor-" + idx.incrementAndGet()).start(); return result; } @Override - public T endProcess(AsyncResult asyncResult) throws ExecutionException, - InterruptedException { + public T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException { if (asyncResult.isCompleted()) { return asyncResult.getValue(); } else { @@ -45,9 +44,8 @@ public class ThreadAsyncExecutor implements AsyncExecutor { } /** - * Simple implementation of async result that allows completing it successfully with a value or - * exceptionally with an exception. A really simplified version from its real life cousins - * FutureTask and CompletableFuture. + * Simple implementation of async result that allows completing it successfully with a value or exceptionally with an + * exception. A really simplified version from its real life cousins FutureTask and CompletableFuture. * * @see java.util.concurrent.FutureTask * @see java.util.concurrent.CompletableFuture @@ -71,10 +69,11 @@ public class ThreadAsyncExecutor implements AsyncExecutor { } /** - * Sets the value from successful execution and executes callback if available. Notifies any - * thread waiting for completion. + * Sets the value from successful execution and executes callback if available. Notifies any thread waiting for + * completion. * - * @param value value of the evaluated task + * @param value + * value of the evaluated task */ void setValue(T value) { this.value = value; @@ -86,10 +85,11 @@ public class ThreadAsyncExecutor implements AsyncExecutor { } /** - * Sets the exception from failed execution and executes callback if available. Notifies any - * thread waiting for completion. + * Sets the exception from failed execution and executes callback if available. Notifies any thread waiting for + * completion. * - * @param exception exception of the failed task + * @param exception + * exception of the failed task */ void setException(Exception exception) { this.exception = exception; @@ -102,7 +102,7 @@ public class ThreadAsyncExecutor implements AsyncExecutor { @Override public boolean isCompleted() { - return (state > RUNNING); + return state > RUNNING; } @Override diff --git a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java new file mode 100644 index 000000000..c9d222e55 --- /dev/null +++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java @@ -0,0 +1,290 @@ +package com.iluwatar.async.method.invocation; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Matchers; + +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; +import static org.mockito.internal.verification.VerificationModeFactory.times; + +/** + * Date: 12/6/15 - 10:49 AM + * + * @author Jeroen Meulemeester + */ +public class ThreadAsyncExecutorTest { + + /** + * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)} + */ + @Test(timeout = 3000) + public void testSuccessfulTaskWithoutCallback() throws Exception { + // Instantiate a new executor and start a new 'null' task ... + final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); + + final Object result = new Object(); + final Callable task = mock(Callable.class); + when(task.call()).thenReturn(result); + + final AsyncResult asyncResult = executor.startProcess(task); + assertNotNull(asyncResult); + asyncResult.await(); // Prevent timing issues, and wait until the result is available + assertTrue(asyncResult.isCompleted()); + + // Our task should only execute once ... + verify(task, times(1)).call(); + + // ... and the result should be exactly the same object + assertSame(result, asyncResult.getValue()); + } + + /** + * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)} + */ + @Test(timeout = 3000) + public void testSuccessfulTaskWithCallback() throws Exception { + // Instantiate a new executor and start a new 'null' task ... + final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); + + final Object result = new Object(); + final Callable task = mock(Callable.class); + when(task.call()).thenReturn(result); + + final AsyncCallback callback = mock(AsyncCallback.class); + final AsyncResult asyncResult = executor.startProcess(task, callback); + assertNotNull(asyncResult); + asyncResult.await(); // Prevent timing issues, and wait until the result is available + assertTrue(asyncResult.isCompleted()); + + // Our task should only execute once ... + verify(task, times(1)).call(); + + // ... same for the callback, we expect our object + final ArgumentCaptor> optionalCaptor = ArgumentCaptor.forClass((Class) Optional.class); + verify(callback, times(1)).onComplete(eq(result), optionalCaptor.capture()); + + final Optional optionalException = optionalCaptor.getValue(); + assertNotNull(optionalException); + assertFalse(optionalException.isPresent()); + + // ... and the result should be exactly the same object + assertSame(result, asyncResult.getValue()); + } + + /** + * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)} when a task takes a while + * to execute + */ + @Test(timeout = 5000) + public void testLongRunningTaskWithoutCallback() throws Exception { + // Instantiate a new executor and start a new 'null' task ... + final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); + + final Object result = new Object(); + final Callable task = mock(Callable.class); + when(task.call()).thenAnswer(i -> { + Thread.sleep(1500); + return result; + }); + + final AsyncResult asyncResult = executor.startProcess(task); + assertNotNull(asyncResult); + assertFalse(asyncResult.isCompleted()); + + try { + asyncResult.getValue(); + fail("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task"); + } catch (IllegalStateException e) { + assertNotNull(e.getMessage()); + } + + // Our task should only execute once, but it can take a while ... + verify(task, timeout(3000).times(1)).call(); + + // Prevent timing issues, and wait until the result is available + asyncResult.await(); + assertTrue(asyncResult.isCompleted()); + verifyNoMoreInteractions(task); + + // ... and the result should be exactly the same object + assertSame(result, asyncResult.getValue()); + } + + /** + * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)} when a task + * takes a while to execute + */ + @Test(timeout = 5000) + public void testLongRunningTaskWithCallback() throws Exception { + // Instantiate a new executor and start a new 'null' task ... + final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); + + final Object result = new Object(); + final Callable task = mock(Callable.class); + when(task.call()).thenAnswer(i -> { + Thread.sleep(1500); + return result; + }); + + final AsyncCallback callback = mock(AsyncCallback.class); + final AsyncResult asyncResult = executor.startProcess(task, callback); + assertNotNull(asyncResult); + assertFalse(asyncResult.isCompleted()); + + verifyZeroInteractions(callback); + + try { + asyncResult.getValue(); + fail("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task"); + } catch (IllegalStateException e) { + assertNotNull(e.getMessage()); + } + + // Our task should only execute once, but it can take a while ... + verify(task, timeout(3000).times(1)).call(); + + final ArgumentCaptor> optionalCaptor = ArgumentCaptor.forClass((Class) Optional.class); + verify(callback, timeout(3000).times(1)).onComplete(eq(result), optionalCaptor.capture()); + + final Optional optionalException = optionalCaptor.getValue(); + assertNotNull(optionalException); + assertFalse(optionalException.isPresent()); + + // Prevent timing issues, and wait until the result is available + asyncResult.await(); + assertTrue(asyncResult.isCompleted()); + verifyNoMoreInteractions(task, callback); + + // ... and the result should be exactly the same object + assertSame(result, asyncResult.getValue()); + } + + /** + * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)} when a task takes a while + * to execute, while waiting on the result using {@link ThreadAsyncExecutor#endProcess(AsyncResult)} + */ + @Test(timeout = 5000) + public void testEndProcess() throws Exception { + // Instantiate a new executor and start a new 'null' task ... + final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); + + final Object result = new Object(); + final Callable task = mock(Callable.class); + when(task.call()).thenAnswer(i -> { + Thread.sleep(1500); + return result; + }); + + final AsyncResult asyncResult = executor.startProcess(task); + assertNotNull(asyncResult); + assertFalse(asyncResult.isCompleted()); + + try { + asyncResult.getValue(); + fail("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task"); + } catch (IllegalStateException e) { + assertNotNull(e.getMessage()); + } + + assertSame(result, executor.endProcess(asyncResult)); + verify(task, times(1)).call(); + assertTrue(asyncResult.isCompleted()); + + // Calling end process a second time while already finished should give the same result + assertSame(result, executor.endProcess(asyncResult)); + verifyNoMoreInteractions(task); + } + + /** + * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable)} when the callable is 'null' + */ + @Test(timeout = 3000) + public void testNullTask() throws Exception { + // Instantiate a new executor and start a new 'null' task ... + final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); + final AsyncResult asyncResult = executor.startProcess(null); + + assertNotNull("The AsyncResult should not be 'null', even though the task was 'null'.", asyncResult); + asyncResult.await(); // Prevent timing issues, and wait until the result is available + assertTrue(asyncResult.isCompleted()); + + try { + asyncResult.getValue(); + fail("Expected ExecutionException with NPE as cause"); + } catch (final ExecutionException e) { + assertNotNull(e.getMessage()); + assertNotNull(e.getCause()); + assertEquals(NullPointerException.class, e.getCause().getClass()); + } + + } + + /** + * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)} when the + * callable is 'null', but the asynchronous callback is provided + */ + @Test(timeout = 3000) + public void testNullTaskWithCallback() throws Exception { + // Instantiate a new executor and start a new 'null' task ... + final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); + final AsyncCallback callback = mock(AsyncCallback.class); + final AsyncResult asyncResult = executor.startProcess(null, callback); + + assertNotNull("The AsyncResult should not be 'null', even though the task was 'null'.", asyncResult); + asyncResult.await(); // Prevent timing issues, and wait until the result is available + assertTrue(asyncResult.isCompleted()); + + final ArgumentCaptor> optionalCaptor = ArgumentCaptor.forClass((Class) Optional.class); + verify(callback, times(1)).onComplete(Matchers.isNull(), optionalCaptor.capture()); + + final Optional optionalException = optionalCaptor.getValue(); + assertNotNull(optionalException); + assertTrue(optionalException.isPresent()); + + final Exception exception = optionalException.get(); + assertNotNull(exception); + assertEquals(NullPointerException.class, exception.getClass()); + + try { + asyncResult.getValue(); + fail("Expected ExecutionException with NPE as cause"); + } catch (final ExecutionException e) { + assertNotNull(e.getMessage()); + assertNotNull(e.getCause()); + assertEquals(NullPointerException.class, e.getCause().getClass()); + } + + } + + /** + * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)} when both + * the callable and the asynchronous callback are 'null' + */ + @Test(timeout = 3000) + public void testNullTaskWithNullCallback() throws Exception { + // Instantiate a new executor and start a new 'null' task ... + final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); + final AsyncResult asyncResult = executor.startProcess(null, null); + + assertNotNull("The AsyncResult should not be 'null', even though the task and callback were 'null'.", asyncResult); + asyncResult.await(); // Prevent timing issues, and wait until the result is available + assertTrue(asyncResult.isCompleted()); + + try { + asyncResult.getValue(); + fail("Expected ExecutionException with NPE as cause"); + } catch (final ExecutionException e) { + assertNotNull(e.getMessage()); + assertNotNull(e.getCause()); + assertEquals(NullPointerException.class, e.getCause().getClass()); + } + + } + +} \ No newline at end of file diff --git a/bridge/index.md b/bridge/index.md index 4a1d0bcbb..008325ce5 100644 --- a/bridge/index.md +++ b/bridge/index.md @@ -7,6 +7,7 @@ categories: Structural tags: - Java - Gang Of Four + - Difficulty-Intermediate --- **Also known as:** Handle/Body diff --git a/bridge/pom.xml b/bridge/pom.xml index c17b482a5..8f0c00260 100644 --- a/bridge/pom.xml +++ b/bridge/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT bridge @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/bridge/src/test/java/com/iluwatar/bridge/AppTest.java b/bridge/src/test/java/com/iluwatar/bridge/AppTest.java index 99faad43e..08f956b6a 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/AppTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.bridge; import org.junit.Test; -import com.iluwatar.bridge.App; - /** * * Application test diff --git a/bridge/src/test/java/com/iluwatar/bridge/BlindingMagicWeaponTest.java b/bridge/src/test/java/com/iluwatar/bridge/BlindingMagicWeaponTest.java new file mode 100644 index 000000000..a7a2d1536 --- /dev/null +++ b/bridge/src/test/java/com/iluwatar/bridge/BlindingMagicWeaponTest.java @@ -0,0 +1,33 @@ +package com.iluwatar.bridge; + +import org.junit.Test; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.internal.verification.VerificationModeFactory.times; + +/** + * Date: 12/6/15 - 11:15 PM + * + * @author Jeroen Meulemeester + */ +public class BlindingMagicWeaponTest extends MagicWeaponTest { + + /** + * Invoke all possible actions on the weapon and check if the actions are executed on the actual + * underlying weapon implementation. + */ + @Test + public void testExcalibur() throws Exception { + final Excalibur excalibur = spy(new Excalibur()); + final BlindingMagicWeapon blindingMagicWeapon = new BlindingMagicWeapon(excalibur); + + testBasicWeaponActions(blindingMagicWeapon, excalibur); + + blindingMagicWeapon.blind(); + verify(excalibur, times(1)).blindImp(); + verifyNoMoreInteractions(excalibur); + } + +} diff --git a/bridge/src/test/java/com/iluwatar/bridge/FlyingMagicWeaponTest.java b/bridge/src/test/java/com/iluwatar/bridge/FlyingMagicWeaponTest.java new file mode 100644 index 000000000..55b89bb36 --- /dev/null +++ b/bridge/src/test/java/com/iluwatar/bridge/FlyingMagicWeaponTest.java @@ -0,0 +1,33 @@ +package com.iluwatar.bridge; + +import org.junit.Test; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.internal.verification.VerificationModeFactory.times; + +/** + * Date: 12/6/15 - 11:26 PM + * + * @author Jeroen Meulemeester + */ +public class FlyingMagicWeaponTest extends MagicWeaponTest { + + /** + * Invoke all possible actions on the weapon and check if the actions are executed on the actual + * underlying weapon implementation. + */ + @Test + public void testMjollnir() throws Exception { + final Mjollnir mjollnir = spy(new Mjollnir()); + final FlyingMagicWeapon flyingMagicWeapon = new FlyingMagicWeapon(mjollnir); + + testBasicWeaponActions(flyingMagicWeapon, mjollnir); + + flyingMagicWeapon.fly(); + verify(mjollnir, times(1)).flyImp(); + verifyNoMoreInteractions(mjollnir); + } + +} \ No newline at end of file diff --git a/bridge/src/test/java/com/iluwatar/bridge/MagicWeaponTest.java b/bridge/src/test/java/com/iluwatar/bridge/MagicWeaponTest.java new file mode 100644 index 000000000..eb7bfb34e --- /dev/null +++ b/bridge/src/test/java/com/iluwatar/bridge/MagicWeaponTest.java @@ -0,0 +1,42 @@ +package com.iluwatar.bridge; + +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.internal.verification.VerificationModeFactory.times; + +/** + * Date: 12/6/15 - 11:28 PM + * + * @author Jeroen Meulemeester + */ +public abstract class MagicWeaponTest { + + /** + * Invoke the basic actions of the given weapon, and test if the underlying weapon implementation + * is invoked + * + * @param weaponImpl The spied weapon implementation where actions are bridged to + * @param weapon The weapon, handled by the app + */ + protected final void testBasicWeaponActions(final MagicWeapon weapon, + final MagicWeaponImpl weaponImpl) { + assertNotNull(weapon); + assertNotNull(weaponImpl); + assertNotNull(weapon.getImp()); + + weapon.swing(); + verify(weaponImpl, times(1)).swingImp(); + verifyNoMoreInteractions(weaponImpl); + + weapon.wield(); + verify(weaponImpl, times(1)).wieldImp(); + verifyNoMoreInteractions(weaponImpl); + + weapon.unwield(); + verify(weaponImpl, times(1)).unwieldImp(); + verifyNoMoreInteractions(weaponImpl); + + } + +} diff --git a/bridge/src/test/java/com/iluwatar/bridge/SoulEatingMagicWeaponTest.java b/bridge/src/test/java/com/iluwatar/bridge/SoulEatingMagicWeaponTest.java new file mode 100644 index 000000000..2d9c24083 --- /dev/null +++ b/bridge/src/test/java/com/iluwatar/bridge/SoulEatingMagicWeaponTest.java @@ -0,0 +1,33 @@ +package com.iluwatar.bridge; + +import org.junit.Test; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.internal.verification.VerificationModeFactory.times; + +/** + * Date: 12/6/15 - 11:43 PM + * + * @author Jeroen Meulemeester + */ +public class SoulEatingMagicWeaponTest extends MagicWeaponTest { + + /** + * Invoke all possible actions on the weapon and check if the actions are executed on the actual + * underlying weapon implementation. + */ + @Test + public void testStormBringer() throws Exception { + final Stormbringer stormbringer = spy(new Stormbringer()); + final SoulEatingMagicWeapon soulEatingMagicWeapon = new SoulEatingMagicWeapon(stormbringer); + + testBasicWeaponActions(soulEatingMagicWeapon, stormbringer); + + soulEatingMagicWeapon.eatSoul(); + verify(stormbringer, times(1)).eatSoulImp(); + verifyNoMoreInteractions(stormbringer); + } + +} \ No newline at end of file diff --git a/builder/index.md b/builder/index.md index f350638d2..8f299d116 100644 --- a/builder/index.md +++ b/builder/index.md @@ -7,6 +7,7 @@ categories: Creational tags: - Java - Gang Of Four + - Difficulty-Intermediate --- **Intent:** Separate the construction of a complex object from its diff --git a/builder/pom.xml b/builder/pom.xml index f26494b2e..d718dbef7 100644 --- a/builder/pom.xml +++ b/builder/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT builder diff --git a/builder/src/main/java/com/iluwatar/builder/Hero.java b/builder/src/main/java/com/iluwatar/builder/Hero.java index 54be11ea3..cf7289d51 100644 --- a/builder/src/main/java/com/iluwatar/builder/Hero.java +++ b/builder/src/main/java/com/iluwatar/builder/Hero.java @@ -93,6 +93,9 @@ public class Hero { private Armor armor; private Weapon weapon; + /** + * Constructor + */ public HeroBuilder(Profession profession, String name) { if (profession == null || name == null) { throw new IllegalArgumentException("profession and name can not be null"); diff --git a/builder/src/test/java/com/iluwatar/builder/AppTest.java b/builder/src/test/java/com/iluwatar/builder/AppTest.java index 857f49dc9..270537e8c 100644 --- a/builder/src/test/java/com/iluwatar/builder/AppTest.java +++ b/builder/src/test/java/com/iluwatar/builder/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.builder; import org.junit.Test; -import com.iluwatar.builder.App; - /** * * Application test diff --git a/builder/src/test/java/com/iluwatar/builder/HeroTest.java b/builder/src/test/java/com/iluwatar/builder/HeroTest.java new file mode 100644 index 000000000..2bedf3ef1 --- /dev/null +++ b/builder/src/test/java/com/iluwatar/builder/HeroTest.java @@ -0,0 +1,56 @@ +package com.iluwatar.builder; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/6/15 - 11:01 PM + * + * @author Jeroen Meulemeester + */ +public class HeroTest { + + /** + * Test if we get the expected exception when trying to create a hero without a profession + */ + @Test(expected = IllegalArgumentException.class) + public void testMissingProfession() throws Exception { + new Hero.HeroBuilder(null, "Sir without a job"); + } + + /** + * Test if we get the expected exception when trying to create a hero without a name + */ + @Test(expected = IllegalArgumentException.class) + public void testMissingName() throws Exception { + new Hero.HeroBuilder(Profession.THIEF, null); + } + + /** + * Test if the hero build by the builder has the correct attributes, as requested + */ + @Test + public void testBuildHero() throws Exception { + final String heroName = "Sir Lancelot"; + + final Hero hero = new Hero.HeroBuilder(Profession.WARRIOR, heroName) + .withArmor(Armor.CHAIN_MAIL) + .withWeapon(Weapon.SWORD) + .withHairType(HairType.LONG_CURLY) + .withHairColor(HairColor.BLOND) + .build(); + + assertNotNull(hero); + assertNotNull(hero.toString()); + assertEquals(Profession.WARRIOR, hero.getProfession()); + assertEquals(heroName, hero.getName()); + assertEquals(Armor.CHAIN_MAIL, hero.getArmor()); + assertEquals(Weapon.SWORD, hero.getWeapon()); + assertEquals(HairType.LONG_CURLY, hero.getHairType()); + assertEquals(HairColor.BLOND, hero.getHairColor()); + + } + +} \ No newline at end of file diff --git a/business-delegate/index.md b/business-delegate/index.md index a55febaf9..0b28a5a2f 100644 --- a/business-delegate/index.md +++ b/business-delegate/index.md @@ -4,7 +4,9 @@ title: Business Delegate folder: business-delegate permalink: /patterns/business-delegate/ categories: Business Tier -tags: Java +tags: + - Java + - Difficulty-Intermediate --- **Intent:** The Business Delegate pattern adds an abstraction layer between diff --git a/business-delegate/pom.xml b/business-delegate/pom.xml index d07f2c0ae..204c3b85c 100644 --- a/business-delegate/pom.xml +++ b/business-delegate/pom.xml @@ -6,7 +6,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT business-delegate diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java index a3c9a00e7..7fb709bcf 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java @@ -5,20 +5,20 @@ package com.iluwatar.business.delegate; */ public class BusinessDelegate { - private BusinessLookup lookupService; - private BusinessService businessService; - private ServiceType serviceType; + private BusinessLookup lookupService; + private BusinessService businessService; + private ServiceType serviceType; - public void setLookupService(BusinessLookup businessLookup) { - this.lookupService = businessLookup; - } + public void setLookupService(BusinessLookup businessLookup) { + this.lookupService = businessLookup; + } - public void setServiceType(ServiceType serviceType) { - this.serviceType = serviceType; - } + public void setServiceType(ServiceType serviceType) { + this.serviceType = serviceType; + } - public void doTask() { - businessService = lookupService.getBusinessService(serviceType); - businessService.doProcessing(); - } + public void doTask() { + businessService = lookupService.getBusinessService(serviceType); + businessService.doProcessing(); + } } diff --git a/caching/index.md b/caching/index.md index f79f13e42..d15fbb7d9 100644 --- a/caching/index.md +++ b/caching/index.md @@ -6,6 +6,8 @@ permalink: /patterns/caching/ categories: Other tags: - Java + - Difficulty-Intermediate + - Performance --- **Intent:** To avoid expensive re-acquisition of resources by not releasing diff --git a/caching/pom.xml b/caching/pom.xml index e61b8ab8c..f007f14d4 100644 --- a/caching/pom.xml +++ b/caching/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT caching diff --git a/caching/src/main/java/com/iluwatar/caching/App.java b/caching/src/main/java/com/iluwatar/caching/App.java index c7f55db70..62eca47c3 100644 --- a/caching/src/main/java/com/iluwatar/caching/App.java +++ b/caching/src/main/java/com/iluwatar/caching/App.java @@ -21,7 +21,7 @@ package com.iluwatar.caching; * application data. The cache itself is implemented as an internal (Java) data structure. It adopts * a Least-Recently-Used (LRU) strategy for evicting data from itself when its full. The three * strategies are individually tested. The testing of the cache is restricted towards saving and - * querying of user accounts from the underlying data store ( {@link DBManager}). The main class ( + * querying of user accounts from the underlying data store ( {@link DbManager}). The main class ( * {@link App} is not aware of the underlying mechanics of the application (i.e. save and query) and * whether the data is coming from the cache or the DB (i.e. separation of concern). The AppManager * ({@link AppManager}) handles the transaction of data to-and-from the underlying data store @@ -43,7 +43,7 @@ public class App { * @param args command line args */ public static void main(String[] args) { - AppManager.initDB(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests + AppManager.initDb(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests // and the App class to avoid Maven compilation errors. Set flag to // true to run the tests with MongoDB (provided that MongoDB is // installed and socket connection is open). @@ -65,8 +65,8 @@ public class App { AppManager.save(userAccount1); System.out.println(AppManager.printCacheContent()); - userAccount1 = AppManager.find("001"); - userAccount1 = AppManager.find("001"); + AppManager.find("001"); + AppManager.find("001"); } /** @@ -80,15 +80,15 @@ public class App { AppManager.save(userAccount2); System.out.println(AppManager.printCacheContent()); - userAccount2 = AppManager.find("002"); + AppManager.find("002"); System.out.println(AppManager.printCacheContent()); userAccount2 = AppManager.find("002"); userAccount2.setUserName("Jane G."); AppManager.save(userAccount2); System.out.println(AppManager.printCacheContent()); - userAccount2 = AppManager.find("002"); + AppManager.find("002"); System.out.println(AppManager.printCacheContent()); - userAccount2 = AppManager.find("002"); + AppManager.find("002"); } /** @@ -106,12 +106,12 @@ public class App { AppManager.save(userAccount4); AppManager.save(userAccount5); System.out.println(AppManager.printCacheContent()); - userAccount3 = AppManager.find("003"); + AppManager.find("003"); System.out.println(AppManager.printCacheContent()); UserAccount userAccount6 = new UserAccount("006", "Yasha", "She is an only child."); AppManager.save(userAccount6); System.out.println(AppManager.printCacheContent()); - userAccount4 = AppManager.find("004"); + AppManager.find("004"); System.out.println(AppManager.printCacheContent()); } } diff --git a/caching/src/main/java/com/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java index 08132e327..519226640 100644 --- a/caching/src/main/java/com/iluwatar/caching/AppManager.java +++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java @@ -15,24 +15,30 @@ public class AppManager { private static CachingPolicy cachingPolicy; + private AppManager() { + } + /** * * Developer/Tester is able to choose whether the application should use MongoDB as its underlying * data storage or a simple Java data structure to (temporarily) store the data/objects during * runtime. */ - public static void initDB(boolean useMongoDB) { - if (useMongoDB) { + public static void initDb(boolean useMongoDb) { + if (useMongoDb) { try { - DBManager.connect(); + DbManager.connect(); } catch (ParseException e) { e.printStackTrace(); } } else { - DBManager.createVirtualDB(); + DbManager.createVirtualDb(); } } + /** + * Initialize caching policy + */ public static void initCachingPolicy(CachingPolicy policy) { cachingPolicy = policy; if (cachingPolicy == CachingPolicy.BEHIND) { @@ -50,15 +56,21 @@ public class AppManager { CacheStore.initCapacity(capacity); } - public static UserAccount find(String userID) { + /** + * Find user account + */ + public static UserAccount find(String userId) { if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) { - return CacheStore.readThrough(userID); + return CacheStore.readThrough(userId); } else if (cachingPolicy == CachingPolicy.BEHIND) { - return CacheStore.readThroughWithWriteBackPolicy(userID); + return CacheStore.readThroughWithWriteBackPolicy(userId); } return null; } + /** + * Save user account + */ public static void save(UserAccount userAccount) { if (cachingPolicy == CachingPolicy.THROUGH) { CacheStore.writeThrough(userAccount); diff --git a/caching/src/main/java/com/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java index 2041ac14f..1f4748307 100644 --- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java +++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java @@ -9,73 +9,99 @@ import java.util.ArrayList; */ public class CacheStore { - static LRUCache cache = null; + static LruCache cache = null; - public static void initCapacity(int capacity) { - if (null == cache) - cache = new LRUCache(capacity); - else - cache.setCapacity(capacity); + private CacheStore() { } - public static UserAccount readThrough(String userID) { - if (cache.contains(userID)) { + /** + * Init cache capacity + */ + public static void initCapacity(int capacity) { + if (null == cache) { + cache = new LruCache(capacity); + } else { + cache.setCapacity(capacity); + } + } + + /** + * Get user account using read-through cache + */ + public static UserAccount readThrough(String userId) { + if (cache.contains(userId)) { System.out.println("# Cache Hit!"); - return cache.get(userID); + return cache.get(userId); } System.out.println("# Cache Miss!"); - UserAccount userAccount = DBManager.readFromDB(userID); - cache.set(userID, userAccount); + UserAccount userAccount = DbManager.readFromDb(userId); + cache.set(userId, userAccount); return userAccount; } + /** + * Get user account using write-through cache + */ public static void writeThrough(UserAccount userAccount) { - if (cache.contains(userAccount.getUserID())) { - DBManager.updateDB(userAccount); + if (cache.contains(userAccount.getUserId())) { + DbManager.updateDb(userAccount); } else { - DBManager.writeToDB(userAccount); + DbManager.writeToDb(userAccount); } - cache.set(userAccount.getUserID(), userAccount); + cache.set(userAccount.getUserId(), userAccount); } + /** + * Get user account using write-around cache + */ public static void writeAround(UserAccount userAccount) { - if (cache.contains(userAccount.getUserID())) { - DBManager.updateDB(userAccount); - cache.invalidate(userAccount.getUserID()); // Cache data has been updated -- remove older + if (cache.contains(userAccount.getUserId())) { + DbManager.updateDb(userAccount); + cache.invalidate(userAccount.getUserId()); // Cache data has been updated -- remove older // version from cache. } else { - DBManager.writeToDB(userAccount); + DbManager.writeToDb(userAccount); } } - public static UserAccount readThroughWithWriteBackPolicy(String userID) { - if (cache.contains(userID)) { + /** + * Get user account using read-through cache with write-back policy + */ + public static UserAccount readThroughWithWriteBackPolicy(String userId) { + if (cache.contains(userId)) { System.out.println("# Cache Hit!"); - return cache.get(userID); + return cache.get(userId); } System.out.println("# Cache Miss!"); - UserAccount userAccount = DBManager.readFromDB(userID); + UserAccount userAccount = DbManager.readFromDb(userId); if (cache.isFull()) { System.out.println("# Cache is FULL! Writing LRU data to DB..."); - UserAccount toBeWrittenToDB = cache.getLRUData(); - DBManager.upsertDB(toBeWrittenToDB); + UserAccount toBeWrittenToDb = cache.getLruData(); + DbManager.upsertDb(toBeWrittenToDb); } - cache.set(userID, userAccount); + cache.set(userId, userAccount); return userAccount; } + /** + * Set user account + */ public static void writeBehind(UserAccount userAccount) { - if (cache.isFull() && !cache.contains(userAccount.getUserID())) { + if (cache.isFull() && !cache.contains(userAccount.getUserId())) { System.out.println("# Cache is FULL! Writing LRU data to DB..."); - UserAccount toBeWrittenToDB = cache.getLRUData(); - DBManager.upsertDB(toBeWrittenToDB); + UserAccount toBeWrittenToDb = cache.getLruData(); + DbManager.upsertDb(toBeWrittenToDb); } - cache.set(userAccount.getUserID(), userAccount); + cache.set(userAccount.getUserId(), userAccount); } + /** + * Clears cache + */ public static void clearCache() { - if (null != cache) + if (null != cache) { cache.clear(); + } } /** @@ -83,14 +109,18 @@ public class CacheStore { */ public static void flushCache() { System.out.println("# flushCache..."); - if (null == cache) + if (null == cache) { return; + } ArrayList listOfUserAccounts = cache.getCacheDataInListForm(); for (UserAccount userAccount : listOfUserAccounts) { - DBManager.upsertDB(userAccount); + DbManager.upsertDb(userAccount); } } + /** + * Print user accounts + */ public static String print() { ArrayList listOfUserAccounts = cache.getCacheDataInListForm(); StringBuilder sb = new StringBuilder(); diff --git a/caching/src/main/java/com/iluwatar/caching/DBManager.java b/caching/src/main/java/com/iluwatar/caching/DbManager.java similarity index 72% rename from caching/src/main/java/com/iluwatar/caching/DBManager.java rename to caching/src/main/java/com/iluwatar/caching/DbManager.java index 07a5daeac..bfde07103 100644 --- a/caching/src/main/java/com/iluwatar/caching/DBManager.java +++ b/caching/src/main/java/com/iluwatar/caching/DbManager.java @@ -21,7 +21,7 @@ import com.mongodb.client.model.UpdateOptions; * during runtime (createVirtualDB()).

* */ -public class DBManager { +public class DbManager { private static MongoClient mongoClient; private static MongoDatabase db; @@ -29,21 +29,34 @@ public class DBManager { private static HashMap virtualDB; - public static void createVirtualDB() { + private DbManager() { + } + + /** + * Create DB + */ + public static void createVirtualDb() { useMongoDB = false; virtualDB = new HashMap(); } + /** + * Connect to DB + */ public static void connect() throws ParseException { useMongoDB = true; mongoClient = new MongoClient(); db = mongoClient.getDatabase("test"); } - public static UserAccount readFromDB(String userID) { + /** + * Read user account from DB + */ + public static UserAccount readFromDb(String userId) { if (!useMongoDB) { - if (virtualDB.containsKey(userID)) - return virtualDB.get(userID); + if (virtualDB.containsKey(userId)) { + return virtualDB.get(userId); + } return null; } if (null == db) { @@ -54,18 +67,22 @@ public class DBManager { } } FindIterable iterable = - db.getCollection("user_accounts").find(new Document("userID", userID)); - if (iterable == null) + db.getCollection("user_accounts").find(new Document("userID", userId)); + if (iterable == null) { return null; + } Document doc = iterable.first(); UserAccount userAccount = - new UserAccount(userID, doc.getString("userName"), doc.getString("additionalInfo")); + new UserAccount(userId, doc.getString("userName"), doc.getString("additionalInfo")); return userAccount; } - public static void writeToDB(UserAccount userAccount) { + /** + * Write user account to DB + */ + public static void writeToDb(UserAccount userAccount) { if (!useMongoDB) { - virtualDB.put(userAccount.getUserID(), userAccount); + virtualDB.put(userAccount.getUserId(), userAccount); return; } if (null == db) { @@ -76,13 +93,16 @@ public class DBManager { } } db.getCollection("user_accounts").insertOne( - new Document("userID", userAccount.getUserID()).append("userName", + new Document("userID", userAccount.getUserId()).append("userName", userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo())); } - public static void updateDB(UserAccount userAccount) { + /** + * Update DB + */ + public static void updateDb(UserAccount userAccount) { if (!useMongoDB) { - virtualDB.put(userAccount.getUserID(), userAccount); + virtualDB.put(userAccount.getUserId(), userAccount); return; } if (null == db) { @@ -93,7 +113,7 @@ public class DBManager { } } db.getCollection("user_accounts").updateOne( - new Document("userID", userAccount.getUserID()), + new Document("userID", userAccount.getUserId()), new Document("$set", new Document("userName", userAccount.getUserName()).append( "additionalInfo", userAccount.getAdditionalInfo()))); } @@ -102,9 +122,9 @@ public class DBManager { * * Insert data into DB if it does not exist. Else, update it. */ - public static void upsertDB(UserAccount userAccount) { + public static void upsertDb(UserAccount userAccount) { if (!useMongoDB) { - virtualDB.put(userAccount.getUserID(), userAccount); + virtualDB.put(userAccount.getUserId(), userAccount); return; } if (null == db) { @@ -115,8 +135,8 @@ public class DBManager { } } db.getCollection("user_accounts").updateOne( - new Document("userID", userAccount.getUserID()), - new Document("$set", new Document("userID", userAccount.getUserID()).append("userName", + new Document("userID", userAccount.getUserId()), + new Document("$set", new Document("userID", userAccount.getUserId()).append("userName", userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo())), new UpdateOptions().upsert(true)); } diff --git a/caching/src/main/java/com/iluwatar/caching/LRUCache.java b/caching/src/main/java/com/iluwatar/caching/LruCache.java similarity index 70% rename from caching/src/main/java/com/iluwatar/caching/LRUCache.java rename to caching/src/main/java/com/iluwatar/caching/LruCache.java index 872f97256..e20275a40 100644 --- a/caching/src/main/java/com/iluwatar/caching/LRUCache.java +++ b/caching/src/main/java/com/iluwatar/caching/LruCache.java @@ -12,16 +12,16 @@ import java.util.HashMap; * LRU data is always at the end of the list. * */ -public class LRUCache { +public class LruCache { class Node { - String userID; + String userId; UserAccount userAccount; Node previous; Node next; - public Node(String userID, UserAccount userAccount) { - this.userID = userID; + public Node(String userId, UserAccount userAccount) { + this.userId = userId; this.userAccount = userAccount; } } @@ -31,13 +31,16 @@ public class LRUCache { Node head = null; Node end = null; - public LRUCache(int capacity) { + public LruCache(int capacity) { this.capacity = capacity; } - public UserAccount get(String userID) { - if (cache.containsKey(userID)) { - Node node = cache.get(userID); + /** + * Get user account + */ + public UserAccount get(String userId) { + if (cache.containsKey(userId)) { + Node node = cache.get(userId); remove(node); setHead(node); return node.userAccount; @@ -69,52 +72,63 @@ public class LRUCache { public void setHead(Node node) { node.next = head; node.previous = null; - if (head != null) + if (head != null) { head.previous = node; + } head = node; - if (end == null) + if (end == null) { end = head; + } } - public void set(String userID, UserAccount userAccount) { - if (cache.containsKey(userID)) { - Node old = cache.get(userID); + /** + * Set user account + */ + public void set(String userId, UserAccount userAccount) { + if (cache.containsKey(userId)) { + Node old = cache.get(userId); old.userAccount = userAccount; remove(old); setHead(old); } else { - Node newNode = new Node(userID, userAccount); + Node newNode = new Node(userId, userAccount); if (cache.size() >= capacity) { - System.out.println("# Cache is FULL! Removing " + end.userID + " from cache..."); - cache.remove(end.userID); // remove LRU data from cache. + System.out.println("# Cache is FULL! Removing " + end.userId + " from cache..."); + cache.remove(end.userId); // remove LRU data from cache. remove(end); setHead(newNode); } else { setHead(newNode); } - cache.put(userID, newNode); + cache.put(userId, newNode); } } - public boolean contains(String userID) { - return cache.containsKey(userID); + public boolean contains(String userId) { + return cache.containsKey(userId); } - public void invalidate(String userID) { - System.out.println("# " + userID + " has been updated! Removing older version from cache..."); - Node toBeRemoved = cache.get(userID); + /** + * Invalidate cache for user + */ + public void invalidate(String userId) { + System.out.println("# " + userId + " has been updated! Removing older version from cache..."); + Node toBeRemoved = cache.get(userId); remove(toBeRemoved); - cache.remove(userID); + cache.remove(userId); } public boolean isFull() { return cache.size() >= capacity; } - public UserAccount getLRUData() { + public UserAccount getLruData() { return end.userAccount; } + /** + * Clear cache + */ public void clear() { head = null; end = null; @@ -135,6 +149,9 @@ public class LRUCache { return listOfCacheData; } + /** + * Set cache capacity + */ public void setCapacity(int newCapacity) { if (capacity > newCapacity) { clear(); // Behavior can be modified to accommodate for decrease in cache size. For now, we'll diff --git a/caching/src/main/java/com/iluwatar/caching/UserAccount.java b/caching/src/main/java/com/iluwatar/caching/UserAccount.java index eff0878ad..0e281c429 100644 --- a/caching/src/main/java/com/iluwatar/caching/UserAccount.java +++ b/caching/src/main/java/com/iluwatar/caching/UserAccount.java @@ -6,22 +6,25 @@ package com.iluwatar.caching; * */ public class UserAccount { - private String userID; + private String userId; private String userName; private String additionalInfo; - public UserAccount(String userID, String userName, String additionalInfo) { - this.userID = userID; + /** + * Constructor + */ + public UserAccount(String userId, String userName, String additionalInfo) { + this.userId = userId; this.userName = userName; this.additionalInfo = additionalInfo; } - public String getUserID() { - return userID; + public String getUserId() { + return userId; } - public void setUserID(String userID) { - this.userID = userID; + public void setUserId(String userId) { + this.userId = userId; } public String getUserName() { @@ -42,6 +45,6 @@ public class UserAccount { @Override public String toString() { - return userID + ", " + userName + ", " + additionalInfo; + return userId + ", " + userName + ", " + additionalInfo; } } diff --git a/caching/src/test/java/com/iluwatar/caching/AppTest.java b/caching/src/test/java/com/iluwatar/caching/AppTest.java index ce5cddf08..35917da1c 100644 --- a/caching/src/test/java/com/iluwatar/caching/AppTest.java +++ b/caching/src/test/java/com/iluwatar/caching/AppTest.java @@ -16,7 +16,7 @@ public class AppTest { */ @Before public void setUp() { - AppManager.initDB(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests + AppManager.initDb(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests // to avoid Maven compilation errors. Set flag to true to run the // tests with MongoDB (provided that MongoDB is installed and socket // connection is open). diff --git a/callback/index.md b/callback/index.md index b724f1edc..a70da1ff4 100644 --- a/callback/index.md +++ b/callback/index.md @@ -4,7 +4,11 @@ title: Callback folder: callback permalink: /patterns/callback/ categories: Other -tags: Java +tags: + - Java + - Difficulty-Beginner + - Functional + - Idiom --- **Intent:** Callback is a piece of executable code that is passed as an diff --git a/callback/pom.xml b/callback/pom.xml index dc12efb66..b4b2f6ed0 100644 --- a/callback/pom.xml +++ b/callback/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT callback diff --git a/callback/src/main/java/com/iluwatar/callback/App.java b/callback/src/main/java/com/iluwatar/callback/App.java index 81cb16f73..bc8b08cf0 100644 --- a/callback/src/main/java/com/iluwatar/callback/App.java +++ b/callback/src/main/java/com/iluwatar/callback/App.java @@ -9,6 +9,9 @@ package com.iluwatar.callback; */ public class App { + /** + * Program entry point + */ public static void main(String[] args) { Task task = new SimpleTask(); Callback callback = new Callback() { diff --git a/callback/src/main/java/com/iluwatar/callback/Callback.java b/callback/src/main/java/com/iluwatar/callback/Callback.java index 08939298b..712893873 100644 --- a/callback/src/main/java/com/iluwatar/callback/Callback.java +++ b/callback/src/main/java/com/iluwatar/callback/Callback.java @@ -7,5 +7,5 @@ package com.iluwatar.callback; */ public interface Callback { - public void call(); + void call(); } diff --git a/callback/src/main/java/com/iluwatar/callback/Task.java b/callback/src/main/java/com/iluwatar/callback/Task.java index d3be6c7a0..83e2cd4df 100644 --- a/callback/src/main/java/com/iluwatar/callback/Task.java +++ b/callback/src/main/java/com/iluwatar/callback/Task.java @@ -7,6 +7,9 @@ package com.iluwatar.callback; */ public abstract class Task { + /** + * Execute with callback + */ public final void executeWith(Callback callback) { execute(); if (callback != null) { diff --git a/chain/index.md b/chain/index.md index 9be376324..5432b51b6 100644 --- a/chain/index.md +++ b/chain/index.md @@ -7,6 +7,7 @@ categories: Behavioral tags: - Java - Gang Of Four + - Difficulty-Intermediate --- **Intent:** Avoid coupling the sender of a request to its receiver by giving diff --git a/chain/pom.xml b/chain/pom.xml index 1c044f7e1..ec3ecf1bb 100644 --- a/chain/pom.xml +++ b/chain/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT chain diff --git a/chain/src/main/java/com/iluwatar/chain/OrcCommander.java b/chain/src/main/java/com/iluwatar/chain/OrcCommander.java index 73de1b8b2..6cc495b99 100644 --- a/chain/src/main/java/com/iluwatar/chain/OrcCommander.java +++ b/chain/src/main/java/com/iluwatar/chain/OrcCommander.java @@ -15,6 +15,7 @@ public class OrcCommander extends RequestHandler { public void handleRequest(Request req) { if (req.getRequestType().equals(RequestType.DEFEND_CASTLE)) { printHandling(req); + req.markHandled(); } else { super.handleRequest(req); } diff --git a/chain/src/main/java/com/iluwatar/chain/OrcOfficer.java b/chain/src/main/java/com/iluwatar/chain/OrcOfficer.java index 68df3ec6f..e6d68c19c 100644 --- a/chain/src/main/java/com/iluwatar/chain/OrcOfficer.java +++ b/chain/src/main/java/com/iluwatar/chain/OrcOfficer.java @@ -15,6 +15,7 @@ public class OrcOfficer extends RequestHandler { public void handleRequest(Request req) { if (req.getRequestType().equals(RequestType.TORTURE_PRISONER)) { printHandling(req); + req.markHandled(); } else { super.handleRequest(req); } diff --git a/chain/src/main/java/com/iluwatar/chain/OrcSoldier.java b/chain/src/main/java/com/iluwatar/chain/OrcSoldier.java index d96f51702..bd2a8d11d 100644 --- a/chain/src/main/java/com/iluwatar/chain/OrcSoldier.java +++ b/chain/src/main/java/com/iluwatar/chain/OrcSoldier.java @@ -15,6 +15,7 @@ public class OrcSoldier extends RequestHandler { public void handleRequest(Request req) { if (req.getRequestType().equals(RequestType.COLLECT_TAX)) { printHandling(req); + req.markHandled(); } else { super.handleRequest(req); } diff --git a/chain/src/main/java/com/iluwatar/chain/Request.java b/chain/src/main/java/com/iluwatar/chain/Request.java index 0c62cfd43..5c3256a55 100644 --- a/chain/src/main/java/com/iluwatar/chain/Request.java +++ b/chain/src/main/java/com/iluwatar/chain/Request.java @@ -1,38 +1,78 @@ package com.iluwatar.chain; +import java.util.Objects; + /** - * * Request - * */ public class Request { - private String requestDescription; - private RequestType requestType; + /** + * The type of this request, used by each item in the chain to see if they should or can handle + * this particular request + */ + private final RequestType requestType; - public Request(RequestType requestType, String requestDescription) { - this.setRequestType(requestType); - this.setRequestDescription(requestDescription); + /** + * A description of the request + */ + private final String requestDescription; + + /** + * Indicates if the request is handled or not. A request can only switch state from unhandled to + * handled, there's no way to 'unhandle' a request + */ + private boolean handled = false; + + /** + * Create a new request of the given type and accompanied description. + * + * @param requestType The type of request + * @param requestDescription The description of the request + */ + public Request(final RequestType requestType, final String requestDescription) { + this.requestType = Objects.requireNonNull(requestType); + this.requestDescription = Objects.requireNonNull(requestDescription); } + /** + * Get a description of the request + * + * @return A human readable description of the request + */ public String getRequestDescription() { return requestDescription; } - public void setRequestDescription(String requestDescription) { - this.requestDescription = requestDescription; - } - + /** + * Get the type of this request, used by each person in the chain of command to see if they should + * or can handle this particular request + * + * @return The request type + */ public RequestType getRequestType() { return requestType; } - public void setRequestType(RequestType requestType) { - this.requestType = requestType; + /** + * Mark the request as handled + */ + public void markHandled() { + this.handled = true; + } + + /** + * Indicates if this request is handled or not + * + * @return true when the request is handled, false if not + */ + public boolean isHandled() { + return this.handled; } @Override public String toString() { return getRequestDescription(); } + } diff --git a/chain/src/main/java/com/iluwatar/chain/RequestHandler.java b/chain/src/main/java/com/iluwatar/chain/RequestHandler.java index fd58b9ea8..12db1f51c 100644 --- a/chain/src/main/java/com/iluwatar/chain/RequestHandler.java +++ b/chain/src/main/java/com/iluwatar/chain/RequestHandler.java @@ -13,6 +13,9 @@ public abstract class RequestHandler { this.next = next; } + /** + * Request handler + */ public void handleRequest(Request req) { if (next != null) { next.handleRequest(req); diff --git a/chain/src/test/java/com/iluwatar/chain/AppTest.java b/chain/src/test/java/com/iluwatar/chain/AppTest.java index bd28b007a..f1dc78759 100644 --- a/chain/src/test/java/com/iluwatar/chain/AppTest.java +++ b/chain/src/test/java/com/iluwatar/chain/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.chain; import org.junit.Test; -import com.iluwatar.chain.App; - /** * * Application test diff --git a/chain/src/test/java/com/iluwatar/chain/OrcKingTest.java b/chain/src/test/java/com/iluwatar/chain/OrcKingTest.java new file mode 100644 index 000000000..fd3d573b6 --- /dev/null +++ b/chain/src/test/java/com/iluwatar/chain/OrcKingTest.java @@ -0,0 +1,37 @@ +package com.iluwatar.chain; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/6/15 - 9:29 PM + * + * @author Jeroen Meulemeester + */ +public class OrcKingTest { + + /** + * All possible requests + */ + private static final Request[] REQUESTS = new Request[]{ + new Request(RequestType.DEFEND_CASTLE, "Don't let the barbarians enter my castle!!"), + new Request(RequestType.TORTURE_PRISONER, "Don't just stand there, tickle him!"), + new Request(RequestType.COLLECT_TAX, "Don't steal, the King hates competition ..."), + }; + + @Test + public void testMakeRequest() throws Exception { + final OrcKing king = new OrcKing(); + + for (final Request request : REQUESTS) { + king.makeRequest(request); + assertTrue( + "Expected all requests from King to be handled, but [" + request + "] was not!", + request.isHandled() + ); + } + + } + +} \ No newline at end of file diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml new file mode 100644 index 000000000..a3a2c23a8 --- /dev/null +++ b/checkstyle-suppressions.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/checkstyle.xml b/checkstyle.xml index 0ff943d95..706c188e0 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -25,8 +25,10 @@ + + - + @@ -48,7 +50,7 @@ - + @@ -61,7 +63,7 @@ - + @@ -86,9 +88,6 @@ - - - @@ -97,42 +96,19 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - @@ -180,11 +148,6 @@ - - - - @@ -195,7 +158,7 @@ - + @@ -205,4 +168,5 @@ + diff --git a/command/index.md b/command/index.md index 3fa774d8f..4052a8ae7 100644 --- a/command/index.md +++ b/command/index.md @@ -7,6 +7,7 @@ categories: Behavioral tags: - Java - Gang Of Four + - Difficulty-Intermediate --- **Also known as:** Action, Transaction diff --git a/command/pom.xml b/command/pom.xml index 6001ebc33..837b149f6 100644 --- a/command/pom.xml +++ b/command/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT command diff --git a/command/src/main/java/com/iluwatar/command/Target.java b/command/src/main/java/com/iluwatar/command/Target.java index e12f758ff..731fe4d1f 100644 --- a/command/src/main/java/com/iluwatar/command/Target.java +++ b/command/src/main/java/com/iluwatar/command/Target.java @@ -30,6 +30,9 @@ public abstract class Target { @Override public abstract String toString(); + /** + * Print status + */ public void printStatus() { System.out.println(String.format("%s, [size=%s] [visibility=%s]", this, getSize(), getVisibility())); diff --git a/command/src/main/java/com/iluwatar/command/Wizard.java b/command/src/main/java/com/iluwatar/command/Wizard.java index edef8d3a9..fb6407c74 100644 --- a/command/src/main/java/com/iluwatar/command/Wizard.java +++ b/command/src/main/java/com/iluwatar/command/Wizard.java @@ -15,12 +15,18 @@ public class Wizard { public Wizard() {} + /** + * Cast spell + */ public void castSpell(Command command, Target target) { System.out.println(this + " casts " + command + " at " + target); command.execute(target); undoStack.offerLast(command); } + /** + * Undo last spell + */ public void undoLastSpell() { if (!undoStack.isEmpty()) { Command previousSpell = undoStack.pollLast(); @@ -30,6 +36,9 @@ public class Wizard { } } + /** + * Redo last spell + */ public void redoLastSpell() { if (!redoStack.isEmpty()) { Command previousSpell = redoStack.pollLast(); diff --git a/composite/index.md b/composite/index.md index 4a31a1b33..bf5a920ac 100644 --- a/composite/index.md +++ b/composite/index.md @@ -7,6 +7,7 @@ categories: Structural tags: - Java - Gang Of Four + - Difficulty-Intermediate --- **Intent:** Compose objects into tree structures to represent part-whole diff --git a/composite/pom.xml b/composite/pom.xml index 155108064..584ba5476 100644 --- a/composite/pom.xml +++ b/composite/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT composite diff --git a/composite/src/main/java/com/iluwatar/composite/LetterComposite.java b/composite/src/main/java/com/iluwatar/composite/LetterComposite.java index 1fdf4fdb6..39655fa37 100644 --- a/composite/src/main/java/com/iluwatar/composite/LetterComposite.java +++ b/composite/src/main/java/com/iluwatar/composite/LetterComposite.java @@ -24,6 +24,9 @@ public abstract class LetterComposite { protected abstract void printThisAfter(); + /** + * Print + */ public void print() { printThisBefore(); for (LetterComposite letter : children) { diff --git a/composite/src/main/java/com/iluwatar/composite/Sentence.java b/composite/src/main/java/com/iluwatar/composite/Sentence.java index e6c626ea2..03f0c6949 100644 --- a/composite/src/main/java/com/iluwatar/composite/Sentence.java +++ b/composite/src/main/java/com/iluwatar/composite/Sentence.java @@ -9,6 +9,9 @@ import java.util.List; */ public class Sentence extends LetterComposite { + /** + * Constructor + */ public Sentence(List words) { for (Word w : words) { this.add(w); diff --git a/composite/src/main/java/com/iluwatar/composite/Word.java b/composite/src/main/java/com/iluwatar/composite/Word.java index 3060b0a1b..98c5f0b0d 100644 --- a/composite/src/main/java/com/iluwatar/composite/Word.java +++ b/composite/src/main/java/com/iluwatar/composite/Word.java @@ -9,6 +9,9 @@ import java.util.List; */ public class Word extends LetterComposite { + /** + * Constructor + */ public Word(List letters) { for (Letter l : letters) { this.add(l); diff --git a/composite/src/test/java/com/iluwatar/composite/AppTest.java b/composite/src/test/java/com/iluwatar/composite/AppTest.java index 574e8def4..a5bc613c0 100644 --- a/composite/src/test/java/com/iluwatar/composite/AppTest.java +++ b/composite/src/test/java/com/iluwatar/composite/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.composite; import org.junit.Test; -import com.iluwatar.composite.App; - /** * * Application test diff --git a/composite/src/test/java/com/iluwatar/composite/MessengerTest.java b/composite/src/test/java/com/iluwatar/composite/MessengerTest.java new file mode 100644 index 000000000..9d0b158cc --- /dev/null +++ b/composite/src/test/java/com/iluwatar/composite/MessengerTest.java @@ -0,0 +1,90 @@ +package com.iluwatar.composite; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/11/15 - 8:12 PM + * + * @author Jeroen Meulemeester + */ +public class MessengerTest { + + /** + * The buffer used to capture every write to {@link System#out} + */ + private ByteArrayOutputStream stdOutBuffer = new ByteArrayOutputStream(); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream realStdOut = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + this.stdOutBuffer = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOutBuffer)); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(realStdOut); + } + + /** + * Test the message from the orcs + */ + @Test + public void testMessageFromOrcs() { + final Messenger messenger = new Messenger(); + testMessage( + messenger.messageFromOrcs(), + "Where there is a whip there is a way." + ); + } + + /** + * Test the message from the elves + */ + @Test + public void testMessageFromElves() { + final Messenger messenger = new Messenger(); + testMessage( + messenger.messageFromElves(), + "Much wind pours from your mouth." + ); + } + + /** + * Test if the given composed message matches the expected message + * + * @param composedMessage The composed message, received from the messenger + * @param message The expected message + */ + private void testMessage(final LetterComposite composedMessage, final String message) { + // Test is the composed message has the correct number of words + final String[] words = message.split(" "); + assertNotNull(composedMessage); + assertEquals(words.length, composedMessage.count()); + + // Print the message to the mocked stdOut ... + composedMessage.print(); + + // ... and verify if the message matches with the expected one + assertEquals(message, new String(this.stdOutBuffer.toByteArray()).trim()); + } + +} diff --git a/dao/index.md b/dao/index.md index cf9f43a68..91f6dc776 100644 --- a/dao/index.md +++ b/dao/index.md @@ -3,7 +3,7 @@ layout: pattern title: Data Access Object folder: dao permalink: /patterns/dao/ -categories: Architectural +categories: Persistence Tier tags: - Java - Difficulty-Beginner diff --git a/dao/pom.xml b/dao/pom.xml index 3134dad96..8b0c260e5 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -6,7 +6,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT dao diff --git a/dao/src/main/java/com/iluwatar/dao/App.java b/dao/src/main/java/com/iluwatar/dao/App.java index 2e115d8ce..a9351689d 100644 --- a/dao/src/main/java/com/iluwatar/dao/App.java +++ b/dao/src/main/java/com/iluwatar/dao/App.java @@ -21,7 +21,7 @@ import org.apache.log4j.Logger; */ public class App { - private static Logger LOGGER = Logger.getLogger(App.class); + private static Logger log = Logger.getLogger(App.class); /** * Program entry point. @@ -30,17 +30,17 @@ public class App { */ public static void main(final String[] args) { final CustomerDaoImpl customerDao = new CustomerDaoImpl(generateSampleCustomers()); - LOGGER.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); - LOGGER.info("customerDao.getCusterById(2): " + customerDao.getCustomerById(2)); + log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); + log.info("customerDao.getCusterById(2): " + customerDao.getCustomerById(2)); final Customer customer = new Customer(4, "Dan", "Danson"); customerDao.addCustomer(customer); - LOGGER.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); + log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); customer.setFirstName("Daniel"); customer.setLastName("Danielson"); customerDao.updateCustomer(customer); - LOGGER.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); + log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); customerDao.deleteCustomer(customer); - LOGGER.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); + log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); } /** diff --git a/dao/src/main/java/com/iluwatar/dao/Customer.java b/dao/src/main/java/com/iluwatar/dao/Customer.java index e6d7f7763..ea13daf11 100644 --- a/dao/src/main/java/com/iluwatar/dao/Customer.java +++ b/dao/src/main/java/com/iluwatar/dao/Customer.java @@ -11,6 +11,9 @@ public class Customer { private String firstName; private String lastName; + /** + * Constructor + */ public Customer(final int id, final String firstName, final String lastName) { this.id = id; this.firstName = firstName; @@ -52,10 +55,11 @@ public class Customer { boolean isEqual = false; if (this == o) { isEqual = true; - } else if (o != null && (getClass() == o.getClass())) { + } else if (o != null && getClass() == o.getClass()) { final Customer customer = (Customer) o; - if (getId() == customer.getId()) + if (getId() == customer.getId()) { isEqual = true; + } } return isEqual; } diff --git a/dao/src/main/resources/log4j.xml b/dao/src/main/resources/log4j.xml index 136817f50..906e37170 100644 --- a/dao/src/main/resources/log4j.xml +++ b/dao/src/main/resources/log4j.xml @@ -1,17 +1,17 @@ + xmlns:log4j='http://jakarta.apache.org/log4j/'> - - - - - + + + + + - - - - + + + + \ No newline at end of file diff --git a/decorator/index.md b/decorator/index.md index 61eeeac60..ca6c7bb50 100644 --- a/decorator/index.md +++ b/decorator/index.md @@ -7,6 +7,7 @@ categories: Structural tags: - Java - Gang Of Four + - Difficulty-Beginner --- **Also known as:** Wrapper diff --git a/decorator/pom.xml b/decorator/pom.xml index 044037688..ea30f5b38 100644 --- a/decorator/pom.xml +++ b/decorator/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT decorator @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/decorator/src/test/java/com/iluwatar/decorator/AppTest.java b/decorator/src/test/java/com/iluwatar/decorator/AppTest.java index f6fa96092..4b2ced962 100644 --- a/decorator/src/test/java/com/iluwatar/decorator/AppTest.java +++ b/decorator/src/test/java/com/iluwatar/decorator/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.decorator; import org.junit.Test; -import com.iluwatar.decorator.App; - /** * * Application test diff --git a/decorator/src/test/java/com/iluwatar/decorator/SmartTrollTest.java b/decorator/src/test/java/com/iluwatar/decorator/SmartTrollTest.java new file mode 100644 index 000000000..faafc4a82 --- /dev/null +++ b/decorator/src/test/java/com/iluwatar/decorator/SmartTrollTest.java @@ -0,0 +1,36 @@ +package com.iluwatar.decorator; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; +import static org.mockito.internal.verification.VerificationModeFactory.times; + +/** + * Date: 12/7/15 - 7:47 PM + * + * @author Jeroen Meulemeester + */ +public class SmartTrollTest { + + @Test + public void testSmartTroll() throws Exception { + // Create a normal troll first, but make sure we can spy on it later on. + final Hostile simpleTroll = spy(new Troll()); + + // Now we want to decorate the troll to make it smarter ... + final Hostile smartTroll = new SmartTroll(simpleTroll); + assertEquals(30, smartTroll.getAttackPower()); + verify(simpleTroll, times(1)).getAttackPower(); + + // Check if the smart troll actions are delegated to the decorated troll + smartTroll.attack(); + verify(simpleTroll, times(1)).attack(); + + smartTroll.fleeBattle(); + verify(simpleTroll, times(1)).fleeBattle(); + verifyNoMoreInteractions(simpleTroll); + + } + +} diff --git a/decorator/src/test/java/com/iluwatar/decorator/TrollTest.java b/decorator/src/test/java/com/iluwatar/decorator/TrollTest.java new file mode 100644 index 000000000..56f541cfc --- /dev/null +++ b/decorator/src/test/java/com/iluwatar/decorator/TrollTest.java @@ -0,0 +1,62 @@ +package com.iluwatar.decorator; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.PrintStream; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; +import static org.mockito.internal.verification.VerificationModeFactory.times; + +/** + * Date: 12/7/15 - 7:26 PM + * + * @author Jeroen Meulemeester + */ +public class TrollTest { + + /** + * The mocked standard out stream, required since the actions don't have any influence on other + * objects, except for writing to the std-out using {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + @Test + public void testTrollActions() throws Exception { + final Troll troll = new Troll(); + assertEquals(10, troll.getAttackPower()); + + troll.attack(); + verify(this.stdOutMock, times(1)).println(eq("The troll swings at you with a club!")); + + troll.fleeBattle(); + verify(this.stdOutMock, times(1)).println(eq("The troll shrieks in horror and runs away!")); + + verifyNoMoreInteractions(this.stdOutMock); + } + +} \ No newline at end of file diff --git a/delegation/etc/delegation.png b/delegation/etc/delegation.png new file mode 100644 index 000000000..375ef4d6b Binary files /dev/null and b/delegation/etc/delegation.png differ diff --git a/delegation/etc/delegation.ucls b/delegation/etc/delegation.ucls new file mode 100644 index 000000000..e3ce08873 --- /dev/null +++ b/delegation/etc/delegation.ucls @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/delegation/index.md b/delegation/index.md new file mode 100644 index 000000000..2064a5bf7 --- /dev/null +++ b/delegation/index.md @@ -0,0 +1,27 @@ +--- +layout: pattern +title: Delegation +folder: delegation +permalink: /patterns/delegation/ +categories: Behavioral +tags: + - Java + - Difficulty-Beginner +--- + +**Also known as:** Proxy Pattern + +**Intent:** It is a technique where an object expresses certain behavior to the outside but in +reality delegates responsibility for implementing that behaviour to an associated object. + +![alt text](./etc/delegation.png "Delegate") + +**Applicability:** Use the Delegate pattern in order to achieve the following + +* Reduce the coupling of methods to their class +* Components that behave identically, but realize that this situation can change in the future. + +**Credits** + +* [Delegate Pattern: Wikipedia ](https://en.wikipedia.org/wiki/Delegation_pattern) +* [Proxy Pattern: Wikipedia ](https://en.wikipedia.org/wiki/Proxy_pattern) \ No newline at end of file diff --git a/delegation/pom.xml b/delegation/pom.xml new file mode 100644 index 000000000..3d9ca390d --- /dev/null +++ b/delegation/pom.xml @@ -0,0 +1,27 @@ + + + + + java-design-patterns + com.iluwatar + 1.10.0-SNAPSHOT + + 4.0.0 + + delegation + + + + junit + junit + test + + + com.github.stefanbirkner + system-rules + test + + + \ No newline at end of file diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/App.java b/delegation/src/main/java/com/iluwatar/delegation/simple/App.java new file mode 100644 index 000000000..050380c18 --- /dev/null +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/App.java @@ -0,0 +1,38 @@ +package com.iluwatar.delegation.simple; + +import com.iluwatar.delegation.simple.printers.CanonPrinter; +import com.iluwatar.delegation.simple.printers.EpsonPrinter; +import com.iluwatar.delegation.simple.printers.HpPrinter; + +/** + * The delegate pattern provides a mechanism to abstract away the implementation and control of the desired action. + * The class being called in this case {@link PrinterController} is not responsible for the actual desired action, + * but is actually delegated to a helper class either {@link CanonPrinter}, {@link EpsonPrinter} or {@link HpPrinter}. + * The consumer does not have or require knowledge of the actual class carrying out the action, only the + * container on which they are calling. + * + * In this example the delegates are {@link EpsonPrinter}, {@link HpPrinter} and {@link CanonPrinter} they all implement + * {@link Printer}. The {@link PrinterController} class also implements {@link Printer}. However neither provide the + * functionality of {@link Printer} by printing to the screen, they actually call upon the instance of {@link Printer} + * that they were instantiated with. Therefore delegating the behaviour to another class. + */ +public class App { + + public static final String MESSAGE_TO_PRINT = "hello world"; + + /** + * Program entry point + * + * @param args command line args + */ + public static void main(String[] args) { + PrinterController hpPrinterController = new PrinterController(new HpPrinter()); + PrinterController canonPrinterController = new PrinterController(new CanonPrinter()); + PrinterController epsonPrinterController = new PrinterController(new EpsonPrinter()); + + hpPrinterController.print(MESSAGE_TO_PRINT); + canonPrinterController.print(MESSAGE_TO_PRINT); + epsonPrinterController.print(MESSAGE_TO_PRINT); + } + +} diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/Printer.java b/delegation/src/main/java/com/iluwatar/delegation/simple/Printer.java new file mode 100644 index 000000000..91531dfce --- /dev/null +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/Printer.java @@ -0,0 +1,23 @@ +package com.iluwatar.delegation.simple; + +import com.iluwatar.delegation.simple.printers.CanonPrinter; +import com.iluwatar.delegation.simple.printers.EpsonPrinter; +import com.iluwatar.delegation.simple.printers.HpPrinter; + +/** + * Interface that both the Controller and the Delegate will implement. + * + * @see CanonPrinter + * @see EpsonPrinter + * @see HpPrinter + */ +public interface Printer { + + /** + * Method that takes a String to print to the screen. This will be implemented on both the + * controller and the delegate allowing the controller to call the same method on the delegate class. + * + * @param message to be printed to the screen + */ + void print(final String message); +} diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java b/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java new file mode 100644 index 000000000..d237f0871 --- /dev/null +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java @@ -0,0 +1,23 @@ +package com.iluwatar.delegation.simple; + +public class PrinterController implements Printer { + + private final Printer printer; + + public PrinterController(Printer printer) { + this.printer = printer; + } + + /** + * This method is implemented from {@link Printer} however instead on providing an + * implementation, it instead calls upon the class passed through the constructor. This is the delegate, + * hence the pattern. Therefore meaning that the caller does not care of the implementing class only the owning + * controller. + * + * @param message to be printed to the screen + */ + @Override + public void print(String message) { + printer.print(message); + } +} diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java new file mode 100644 index 000000000..ef6386429 --- /dev/null +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java @@ -0,0 +1,21 @@ +package com.iluwatar.delegation.simple.printers; + +import com.iluwatar.delegation.simple.Printer; + +/** + * Specialised Implementation of {@link Printer} for a Canon Printer, in + * this case the message to be printed is appended to "Canon Printer : " + * + * @see Printer + */ +public class CanonPrinter implements Printer { + + /** + * {@inheritDoc} + */ + @Override + public void print(String message) { + System.out.print("Canon Printer : " + message); + } + +} diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java new file mode 100644 index 000000000..780d12bcb --- /dev/null +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java @@ -0,0 +1,21 @@ +package com.iluwatar.delegation.simple.printers; + +import com.iluwatar.delegation.simple.Printer; + +/** + * Specialised Implementation of {@link Printer} for a Epson Printer, in + * this case the message to be printed is appended to "Epson Printer : " + * + * @see Printer + */ +public class EpsonPrinter implements Printer { + + /** + * {@inheritDoc} + */ + @Override + public void print(String message) { + System.out.print("Epson Printer : " + message); + } + +} diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java new file mode 100644 index 000000000..be8845ece --- /dev/null +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java @@ -0,0 +1,21 @@ +package com.iluwatar.delegation.simple.printers; + +import com.iluwatar.delegation.simple.Printer; + +/** + * Specialised Implementation of {@link Printer} for a HP Printer, in + * this case the message to be printed is appended to "HP Printer : " + * + * @see Printer + */ +public class HpPrinter implements Printer { + + /** + * {@inheritDoc} + */ + @Override + public void print(String message) { + System.out.print("HP Printer : " + message); + } + +} diff --git a/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java b/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java new file mode 100644 index 000000000..189baa856 --- /dev/null +++ b/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java @@ -0,0 +1,13 @@ +package com.iluwatar.delegation.simple; + +import org.junit.Test; + +public class AppTest { + + @Test + public void test() { + String[] args = {}; + App.main(args); + } + +} diff --git a/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java b/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java new file mode 100644 index 000000000..9442b3033 --- /dev/null +++ b/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java @@ -0,0 +1,43 @@ +package com.iluwatar.delegation.simple; + +import com.iluwatar.delegation.simple.printers.CanonPrinter; +import com.iluwatar.delegation.simple.printers.EpsonPrinter; +import com.iluwatar.delegation.simple.printers.HpPrinter; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.SystemOutRule; + +import static org.junit.Assert.assertEquals; + +public class DelegateTest { + + private static final String MESSAGE = "Test Message Printed"; + + @Rule + public final SystemOutRule systemOutRule = new SystemOutRule().enableLog(); + + @Test + public void testCanonPrinter() throws Exception { + PrinterController printerController = new PrinterController(new CanonPrinter()); + printerController.print(MESSAGE); + + assertEquals("Canon Printer : Test Message Printed", systemOutRule.getLog()); + } + + @Test + public void testHpPrinter() throws Exception { + PrinterController printerController = new PrinterController(new HpPrinter()); + printerController.print(MESSAGE); + + assertEquals("HP Printer : Test Message Printed", systemOutRule.getLog()); + } + + @Test + public void testEpsonPrinter() throws Exception { + PrinterController printerController = new PrinterController(new EpsonPrinter()); + printerController.print(MESSAGE); + + assertEquals("Epson Printer : Test Message Printed", systemOutRule.getLog()); + } + +} diff --git a/dependency-injection/index.md b/dependency-injection/index.md index f6ead97a7..4caa30c94 100644 --- a/dependency-injection/index.md +++ b/dependency-injection/index.md @@ -4,7 +4,9 @@ title: Dependency Injection folder: dependency-injection permalink: /patterns/dependency-injection/ categories: Behavioral -tags: Java +tags: + - Java + - Difficulty-Beginner --- **Intent:** Dependency Injection is a software design pattern in which one or diff --git a/dependency-injection/pom.xml b/dependency-injection/pom.xml index b05451afa..e0aee6a6a 100644 --- a/dependency-injection/pom.xml +++ b/dependency-injection/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT dependency-injection @@ -13,6 +13,11 @@ junit junit test + + + org.mockito + mockito-core + test com.google.inject diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedWizardTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedWizardTest.java new file mode 100644 index 000000000..5f7733a99 --- /dev/null +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedWizardTest.java @@ -0,0 +1,40 @@ +package com.iluwatar.dependency.injection; + +import org.junit.Test; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/10/15 - 8:40 PM + * + * @author Jeroen Meulemeester + */ +public class AdvancedWizardTest extends StdOutTest { + + /** + * Test if the {@link AdvancedWizard} smokes whatever instance of {@link Tobacco} is passed to him + * through the constructor parameter + */ + @Test + public void testSmokeEveryThing() throws Exception { + + final Tobacco[] tobaccos = { + new OldTobyTobacco(), new RivendellTobacco(), new SecondBreakfastTobacco() + }; + + for (final Tobacco tobacco : tobaccos) { + final AdvancedWizard advancedWizard = new AdvancedWizard(tobacco); + advancedWizard.smoke(); + + // Verify if the wizard is smoking the correct tobacco ... + verify(getStdOutMock(), times(1)).println("AdvancedWizard smoking " + tobacco.getClass().getSimpleName()); + + // ... and nothing else is happening. + verifyNoMoreInteractions(getStdOutMock()); + } + + } + +} \ No newline at end of file diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java index 8d6411028..36f016e47 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.dependency.injection; import org.junit.Test; -import com.iluwatar.dependency.injection.App; - /** * * Application test diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/GuiceWizardTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/GuiceWizardTest.java new file mode 100644 index 000000000..d84ffad84 --- /dev/null +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/GuiceWizardTest.java @@ -0,0 +1,78 @@ +package com.iluwatar.dependency.injection; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; + +import org.junit.Test; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/10/15 - 8:57 PM + * + * @author Jeroen Meulemeester + */ +public class GuiceWizardTest extends StdOutTest { + + /** + * Test if the {@link GuiceWizard} smokes whatever instance of {@link Tobacco} is passed to him + * through the constructor parameter + */ + @Test + public void testSmokeEveryThingThroughConstructor() throws Exception { + + final Tobacco[] tobaccos = { + new OldTobyTobacco(), new RivendellTobacco(), new SecondBreakfastTobacco() + }; + + for (final Tobacco tobacco : tobaccos) { + final GuiceWizard guiceWizard = new GuiceWizard(tobacco); + guiceWizard.smoke(); + + // Verify if the wizard is smoking the correct tobacco ... + verify(getStdOutMock(), times(1)).println("GuiceWizard smoking " + tobacco.getClass().getSimpleName()); + + // ... and nothing else is happening. + verifyNoMoreInteractions(getStdOutMock()); + } + + } + + /** + * Test if the {@link GuiceWizard} smokes whatever instance of {@link Tobacco} is passed to him + * through the Guice google inject framework + */ + @Test + public void testSmokeEveryThingThroughInjectionFramework() throws Exception { + + @SuppressWarnings("unchecked") + final Class[] tobaccos = new Class[]{ + OldTobyTobacco.class, RivendellTobacco.class, SecondBreakfastTobacco.class + }; + + for (final Class tobaccoClass : tobaccos) { + // Configure the tobacco in the injection framework ... + final Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Tobacco.class).to(tobaccoClass); + } + }); + + // ... and create a new wizard with it + final GuiceWizard guiceWizard = injector.getInstance(GuiceWizard.class); + guiceWizard.smoke(); + + // Verify if the wizard is smoking the correct tobacco ... + verify(getStdOutMock(), times(1)).println("GuiceWizard smoking " + tobaccoClass.getSimpleName()); + + // ... and nothing else is happening. + verifyNoMoreInteractions(getStdOutMock()); + } + + } + +} \ No newline at end of file diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/SimpleWizardTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/SimpleWizardTest.java new file mode 100644 index 000000000..9b3f4ea3a --- /dev/null +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/SimpleWizardTest.java @@ -0,0 +1,26 @@ +package com.iluwatar.dependency.injection; + +import org.junit.Test; + +import static org.mockito.Mockito.*; + +/** + * Date: 12/10/15 - 8:26 PM + * + * @author Jeroen Meulemeester + */ +public class SimpleWizardTest extends StdOutTest { + + /** + * Test if the {@link SimpleWizard} does the only thing it can do: Smoke it's {@link + * OldTobyTobacco} + */ + @Test + public void testSmoke() { + final SimpleWizard simpleWizard = new SimpleWizard(); + simpleWizard.smoke(); + verify(getStdOutMock(), times(1)).println("SimpleWizard smoking OldTobyTobacco"); + verifyNoMoreInteractions(getStdOutMock()); + } + +} \ No newline at end of file diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/StdOutTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/StdOutTest.java new file mode 100644 index 000000000..13c18fcd1 --- /dev/null +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.dependency.injection; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since the actions of the wizard don't + * have any influence on any other accessible objects, except for writing to std-out using {@link + * System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } +} diff --git a/double-checked-locking/index.md b/double-checked-locking/index.md index b1b0108ec..05ec2006f 100644 --- a/double-checked-locking/index.md +++ b/double-checked-locking/index.md @@ -4,7 +4,10 @@ title: Double Checked Locking folder: double-checked-locking permalink: /patterns/double-checked-locking/ categories: Concurrency -tags: Java +tags: + - Java + - Difficulty-Beginner + - Idiom --- **Intent:** Reduce the overhead of acquiring a lock by first testing the diff --git a/double-checked-locking/pom.xml b/double-checked-locking/pom.xml index a9c0f220b..465184e4c 100644 --- a/double-checked-locking/pom.xml +++ b/double-checked-locking/pom.xml @@ -3,7 +3,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT double-checked-locking @@ -12,5 +12,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java index 0cc62c995..79bf6aefd 100644 --- a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java +++ b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java @@ -28,7 +28,7 @@ public class App { ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 3; i++) { executorService.execute(() -> { - while (inventory.addItem(new Item())); + while (inventory.addItem(new Item())) {}; }); } diff --git a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java index b51e000a7..1011b78b4 100644 --- a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java +++ b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java @@ -1,6 +1,7 @@ package com.iluwatar.doublechecked.locking; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -16,12 +17,18 @@ public class Inventory { private final List items; private final Lock lock; + /** + * Constructor + */ public Inventory(int inventorySize) { this.inventorySize = inventorySize; this.items = new ArrayList<>(inventorySize); this.lock = new ReentrantLock(); } + /** + * Add item + */ public boolean addItem(Item item) { if (items.size() < inventorySize) { lock.lock(); @@ -38,4 +45,14 @@ public class Inventory { } return false; } + + /** + * Get all the items in the inventory + * + * @return All the items of the inventory, as an unmodifiable list + */ + public final List getItems() { + return Collections.unmodifiableList(items); + } + } diff --git a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java index 5efe06215..bba4970a3 100644 --- a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java +++ b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java @@ -6,7 +6,4 @@ package com.iluwatar.doublechecked.locking; * */ public class Item { - - private String name; - private int level; } diff --git a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java index bd88f223c..012d00648 100644 --- a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java +++ b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.doublechecked.locking; import org.junit.Test; -import com.iluwatar.doublechecked.locking.App; - /** * * Application test diff --git a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java new file mode 100644 index 000000000..a09f19e57 --- /dev/null +++ b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java @@ -0,0 +1,110 @@ +package com.iluwatar.doublechecked.locking; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.io.PrintStream; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static junit.framework.Assert.assertTrue; +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/10/15 - 9:34 PM + * + * @author Jeroen Meulemeester + */ +public class InventoryTest { + + /** + * The mocked standard out {@link PrintStream}, used to verify a steady increasing size of the + * {@link Inventory} while adding items from multiple threads concurrently + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * The number of threads used to stress test the locking of the {@link Inventory#addItem(Item)} + * method + */ + private static final int THREAD_COUNT = 8; + + /** + * The maximum number of {@link Item}s allowed in the {@link Inventory} + */ + private static final int INVENTORY_SIZE = 1000; + + /** + * Concurrently add multiple items to the inventory, and check if the items were added in order by + * checking the stdOut for continuous growth of the inventory. When 'items.size()=xx' shows up out + * of order, it means that the locking is not ok, increasing the risk of going over the inventory + * item limit. + */ + @Test(timeout = 10000) + public void testAddItem() throws Exception { + // Create a new inventory with a limit of 1000 items and put some load on the add method + final Inventory inventory = new Inventory(INVENTORY_SIZE); + final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT); + for (int i = 0; i < THREAD_COUNT; i++) { + executorService.execute(() -> { + while (inventory.addItem(new Item())) {}; + }); + } + + // Wait until all threads have finished + executorService.shutdown(); + executorService.awaitTermination(5, TimeUnit.SECONDS); + + // Check the number of items in the inventory. It should not have exceeded the allowed maximum + final List items = inventory.getItems(); + assertNotNull(items); + assertEquals(INVENTORY_SIZE, items.size()); + + // Capture all stdOut messages ... + final ArgumentCaptor stdOutCaptor = ArgumentCaptor.forClass(String.class); + verify(this.stdOutMock, times(INVENTORY_SIZE)).println(stdOutCaptor.capture()); + + // ... verify if we got all 1000 + final List values = stdOutCaptor.getAllValues(); + assertEquals(INVENTORY_SIZE, values.size()); + + // ... and check if the inventory size is increasing continuously + for (int i = 0; i < values.size(); i++) { + assertNotNull(values.get(i)); + assertTrue(values.get(i).contains("items.size()=" + (i + 1))); + } + + verifyNoMoreInteractions(this.stdOutMock); + } + +} \ No newline at end of file diff --git a/double-dispatch/index.md b/double-dispatch/index.md index 6847b7a41..a660e0f88 100644 --- a/double-dispatch/index.md +++ b/double-dispatch/index.md @@ -4,7 +4,10 @@ title: Double Dispatch folder: double-dispatch permalink: /patterns/double-dispatch/ categories: Other -tags: Java +tags: + - Java + - Difficulty-Intermediate + - Idiom --- **Intent:** Double Dispatch pattern is a way to create maintainable dynamic diff --git a/double-dispatch/pom.xml b/double-dispatch/pom.xml index 290ddb68b..719827cf0 100644 --- a/double-dispatch/pom.xml +++ b/double-dispatch/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT double-dispatch @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java index 6514feb7f..98e19b770 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java @@ -5,22 +5,20 @@ import java.util.List; /** * - * When a message with a parameter is sent to an object, the resultant behaviour is defined by the - * implementation of that method in the receiver. Sometimes the behaviour must also be determined by - * the type of the parameter. + * When a message with a parameter is sent to an object, the resultant behaviour is defined by the implementation of + * that method in the receiver. Sometimes the behaviour must also be determined by the type of the parameter. *

- * One way to implement this would be to create multiple instanceof-checks for the methods - * parameter. However, this creates a maintenance issue. When new types are added we would also need - * to change the method's implementation and add a new instanceof-check. This violates the single - * responsibility principle - a class should have only one reason to change. + * One way to implement this would be to create multiple instanceof-checks for the methods parameter. However, this + * creates a maintenance issue. When new types are added we would also need to change the method's implementation and + * add a new instanceof-check. This violates the single responsibility principle - a class should have only one reason + * to change. *

- * Instead of the instanceof-checks a better way is to make another virtual call on the parameter - * object. This way new functionality can be easily added without the need to modify existing - * implementation (open-closed principle). + * Instead of the instanceof-checks a better way is to make another virtual call on the parameter object. This way new + * functionality can be easily added without the need to modify existing implementation (open-closed principle). *

- * In this example we have hierarchy of objects ({@link GameObject}) that can collide to each other. - * Each object has its own coordinates which are checked against the other objects' coordinates. If - * there is an overlap, then the objects collide utilizing the Double Dispatch pattern. + * In this example we have hierarchy of objects ({@link GameObject}) that can collide to each other. Each object has its + * own coordinates which are checked against the other objects' coordinates. If there is an overlap, then the objects + * collide utilizing the Double Dispatch pattern. * */ public class App { @@ -28,7 +26,8 @@ public class App { /** * Program entry point * - * @param args command line args + * @param args + * command line args */ public static void main(String[] args) { // initialize game objects and print their status @@ -42,8 +41,9 @@ public class App { // collision check objects.stream().forEach(o1 -> objects.stream().forEach(o2 -> { - if (o1 != o2 && o1.intersectsWith(o2)) + if (o1 != o2 && o1.intersectsWith(o2)) { o1.collision(o2); + } })); System.out.println(""); diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java index db26265cc..e1e3eab7b 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java @@ -12,6 +12,9 @@ public class Rectangle { private int right; private int bottom; + /** + * Constructor + */ public Rectangle(int left, int top, int right, int bottom) { this.left = left; this.top = top; diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java index c5cd213b5..83caca613 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.doubledispatch; import org.junit.Test; -import com.iluwatar.doubledispatch.App; - /** * * Application test diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/CollisionTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/CollisionTest.java new file mode 100644 index 000000000..6792a5d37 --- /dev/null +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/CollisionTest.java @@ -0,0 +1,136 @@ +package com.iluwatar.doubledispatch; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; +import java.util.Objects; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class CollisionTest { + + /** + * The mocked standard out {@link PrintStream}, required if some of the actions on the tested + * object don't have a direct influence on any other accessible objects, except for writing to + * std-out using {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + + /** + * Get the tested object + * + * @return The tested object, should never return 'null' + */ + abstract O getTestedObject(); + + /** + * Collide the tested item with the other given item and verify if the damage and fire state is as + * expected + * + * @param other The other object we have to collide with + * @param otherDamaged Indicates if the other object should be damaged after the collision + * @param otherOnFire Indicates if the other object should be burning after the collision + * @param thisDamaged Indicates if the test object should be damaged after the collision + * @param thisOnFire Indicates if the other object should be burning after the collision + * @param description The expected description of the collision + */ + void testCollision(final GameObject other, final boolean otherDamaged, final boolean otherOnFire, + final boolean thisDamaged, final boolean thisOnFire, final String description) { + + Objects.requireNonNull(other); + Objects.requireNonNull(getTestedObject()); + + final O tested = getTestedObject(); + + tested.collision(other); + + verify(getStdOutMock(), times(1)).println(description); + verifyNoMoreInteractions(getStdOutMock()); + + testOnFire(other, tested, otherOnFire); + testDamaged(other, tested, otherDamaged); + + testOnFire(tested, other, thisOnFire); + testDamaged(tested, other, thisDamaged); + + } + + /** + * Test if the fire state of the target matches the expected state after colliding with the given + * object + * + * @param target The target object + * @param other The other object + * @param expectTargetOnFire The expected state of fire on the target object + */ + private void testOnFire(final GameObject target, final GameObject other, final boolean expectTargetOnFire) { + final String targetName = target.getClass().getSimpleName(); + final String otherName = other.getClass().getSimpleName(); + + final String errorMessage = expectTargetOnFire + ? "Expected [" + targetName + "] to be on fire after colliding with [" + otherName + "] but it was not!" + : "Expected [" + targetName + "] not to be on fire after colliding with [" + otherName + "] but it was!"; + + assertEquals(errorMessage, expectTargetOnFire, target.isOnFire()); + } + + /** + * Test if the damage state of the target matches the expected state after colliding with the + * given object + * + * @param target The target object + * @param other The other object + * @param expectedDamage The expected state of damage on the target object + */ + private void testDamaged(final GameObject target, final GameObject other, final boolean expectedDamage) { + final String targetName = target.getClass().getSimpleName(); + final String otherName = other.getClass().getSimpleName(); + + final String errorMessage = expectedDamage + ? "Expected [" + targetName + "] to be damaged after colliding with [" + otherName + "] but it was not!" + : "Expected [" + targetName + "] not to be damaged after colliding with [" + otherName + "] but it was!"; + + assertEquals(errorMessage, expectedDamage, target.isDamaged()); + } + +} diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/FlamingAsteroidTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/FlamingAsteroidTest.java new file mode 100644 index 000000000..4cbc052c7 --- /dev/null +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/FlamingAsteroidTest.java @@ -0,0 +1,88 @@ +package com.iluwatar.doubledispatch; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/10/15 - 11:31 PM + * + * @author Jeroen Meulemeester + */ +public class FlamingAsteroidTest extends CollisionTest { + + @Override + final FlamingAsteroid getTestedObject() { + return new FlamingAsteroid(1, 2, 3, 4); + } + + /** + * Test the constructor parameters + */ + @Test + public void testConstructor() { + final FlamingAsteroid asteroid = new FlamingAsteroid(1, 2, 3, 4); + assertEquals(1, asteroid.getLeft()); + assertEquals(2, asteroid.getTop()); + assertEquals(3, asteroid.getRight()); + assertEquals(4, asteroid.getBottom()); + assertTrue(asteroid.isOnFire()); + assertFalse(asteroid.isDamaged()); + assertEquals("FlamingAsteroid at [1,2,3,4] damaged=false onFire=true", asteroid.toString()); + } + + /** + * Test what happens we collide with an asteroid + */ + @Test + public void testCollideFlamingAsteroid() { + testCollision( + new FlamingAsteroid(1, 2, 3, 4), + false, true, + false, true, + "FlamingAsteroid hits FlamingAsteroid." + ); + } + + /** + * Test what happens we collide with an meteoroid + */ + @Test + public void testCollideMeteoroid() { + testCollision( + new Meteoroid(1, 1, 3, 4), + false, false, + false, true, + "FlamingAsteroid hits Meteoroid." + ); + } + + /** + * Test what happens we collide with ISS + */ + @Test + public void testCollideSpaceStationIss() { + testCollision( + new SpaceStationIss(1, 1, 3, 4), + true, true, + false, true, + "FlamingAsteroid hits SpaceStationIss. SpaceStationIss is damaged! SpaceStationIss is set on fire!" + ); + } + + /** + * Test what happens we collide with MIR + */ + @Test + public void testCollideSpaceStationMir() { + testCollision( + new SpaceStationMir(1, 1, 3, 4), + true, true, + false, true, + "FlamingAsteroid hits SpaceStationMir. SpaceStationMir is damaged! SpaceStationMir is set on fire!" + ); + } + +} \ No newline at end of file diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/MeteoroidTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/MeteoroidTest.java new file mode 100644 index 000000000..6f90fbaab --- /dev/null +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/MeteoroidTest.java @@ -0,0 +1,87 @@ +package com.iluwatar.doubledispatch; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * Date: 12/10/15 - 11:31 PM + * + * @author Jeroen Meulemeester + */ +public class MeteoroidTest extends CollisionTest { + + @Override + final Meteoroid getTestedObject() { + return new Meteoroid(1, 2, 3, 4); + } + + /** + * Test the constructor parameters + */ + @Test + public void testConstructor() { + final Meteoroid meteoroid = new Meteoroid(1, 2, 3, 4); + assertEquals(1, meteoroid.getLeft()); + assertEquals(2, meteoroid.getTop()); + assertEquals(3, meteoroid.getRight()); + assertEquals(4, meteoroid.getBottom()); + assertFalse(meteoroid.isOnFire()); + assertFalse(meteoroid.isDamaged()); + assertEquals("Meteoroid at [1,2,3,4] damaged=false onFire=false", meteoroid.toString()); + } + + /** + * Test what happens we collide with an asteroid + */ + @Test + public void testCollideFlamingAsteroid() { + testCollision( + new FlamingAsteroid(1, 1, 3, 4), + false, true, + false, false, + "Meteoroid hits FlamingAsteroid." + ); + } + + /** + * Test what happens we collide with an meteoroid + */ + @Test + public void testCollideMeteoroid() { + testCollision( + new Meteoroid(1, 1, 3, 4), + false, false, + false, false, + "Meteoroid hits Meteoroid." + ); + } + + /** + * Test what happens we collide with ISS + */ + @Test + public void testCollideSpaceStationIss() { + testCollision( + new SpaceStationIss(1, 1, 3, 4), + true, false, + false, false, + "Meteoroid hits SpaceStationIss. SpaceStationIss is damaged!" + ); + } + + /** + * Test what happens we collide with MIR + */ + @Test + public void testCollideSpaceStationMir() { + testCollision( + new SpaceStationMir(1, 1, 3, 4), + true, false, + false, false, + "Meteoroid hits SpaceStationMir. SpaceStationMir is damaged!" + ); + } + +} \ No newline at end of file diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java index fad10c490..e2563f7db 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java @@ -1,22 +1,47 @@ package com.iluwatar.doubledispatch; -import org.junit.Assert; import org.junit.Test; -import com.iluwatar.doubledispatch.Rectangle; +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** - * * Unit test for Rectangle - * */ public class RectangleTest { + /** + * Test if the values passed through the constructor matches the values fetched from the getters + */ @Test - public void test() { - Assert.assertTrue(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(0, 0, 1, 1))); - Assert.assertTrue(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(-1, -5, 7, 8))); - Assert.assertFalse(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(2, 2, 3, 3))); - Assert.assertFalse(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(-2, -2, -1, -1))); + public void testConstructor() { + final Rectangle rectangle = new Rectangle(1, 2, 3, 4); + assertEquals(1, rectangle.getLeft()); + assertEquals(2, rectangle.getTop()); + assertEquals(3, rectangle.getRight()); + assertEquals(4, rectangle.getBottom()); } + + /** + * Test if the values passed through the constructor matches the values in the {@link + * #toString()} + */ + @Test + public void testToString() throws Exception { + final Rectangle rectangle = new Rectangle(1, 2, 3, 4); + assertEquals("[1,2,3,4]", rectangle.toString()); + } + + /** + * Test if the {@link Rectangle} class can detect if it intersects with another rectangle. + */ + @Test + public void testIntersection() { + assertTrue(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(0, 0, 1, 1))); + assertTrue(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(-1, -5, 7, 8))); + assertFalse(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(2, 2, 3, 3))); + assertFalse(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(-2, -2, -1, -1))); + } + } diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationIssTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationIssTest.java new file mode 100644 index 000000000..f3ce24e9c --- /dev/null +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationIssTest.java @@ -0,0 +1,87 @@ +package com.iluwatar.doubledispatch; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * Date: 12/10/15 - 11:31 PM + * + * @author Jeroen Meulemeester + */ +public class SpaceStationIssTest extends CollisionTest { + + @Override + final SpaceStationIss getTestedObject() { + return new SpaceStationIss(1, 2, 3, 4); + } + + /** + * Test the constructor parameters + */ + @Test + public void testConstructor() { + final SpaceStationIss iss = new SpaceStationIss(1, 2, 3, 4); + assertEquals(1, iss.getLeft()); + assertEquals(2, iss.getTop()); + assertEquals(3, iss.getRight()); + assertEquals(4, iss.getBottom()); + assertFalse(iss.isOnFire()); + assertFalse(iss.isDamaged()); + assertEquals("SpaceStationIss at [1,2,3,4] damaged=false onFire=false", iss.toString()); + } + + /** + * Test what happens we collide with an asteroid + */ + @Test + public void testCollideFlamingAsteroid() { + testCollision( + new FlamingAsteroid(1, 1, 3, 4), + false, true, + false, false, + "SpaceStationIss hits FlamingAsteroid." + ); + } + + /** + * Test what happens we collide with an meteoroid + */ + @Test + public void testCollideMeteoroid() { + testCollision( + new Meteoroid(1, 1, 3, 4), + false, false, + false, false, + "SpaceStationIss hits Meteoroid." + ); + } + + /** + * Test what happens we collide with ISS + */ + @Test + public void testCollideSpaceStationIss() { + testCollision( + new SpaceStationIss(1, 1, 3, 4), + true, false, + false, false, + "SpaceStationIss hits SpaceStationIss. SpaceStationIss is damaged!" + ); + } + + /** + * Test what happens we collide with MIR + */ + @Test + public void testCollideSpaceStationMir() { + testCollision( + new SpaceStationMir(1, 1, 3, 4), + true, false, + false, false, + "SpaceStationIss hits SpaceStationMir. SpaceStationMir is damaged!" + ); + } + +} \ No newline at end of file diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationMirTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationMirTest.java new file mode 100644 index 000000000..7d557dd30 --- /dev/null +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationMirTest.java @@ -0,0 +1,87 @@ +package com.iluwatar.doubledispatch; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * Date: 12/10/15 - 11:31 PM + * + * @author Jeroen Meulemeester + */ +public class SpaceStationMirTest extends CollisionTest { + + @Override + final SpaceStationMir getTestedObject() { + return new SpaceStationMir(1, 2, 3, 4); + } + + /** + * Test the constructor parameters + */ + @Test + public void testConstructor() { + final SpaceStationMir mir = new SpaceStationMir(1, 2, 3, 4); + assertEquals(1, mir.getLeft()); + assertEquals(2, mir.getTop()); + assertEquals(3, mir.getRight()); + assertEquals(4, mir.getBottom()); + assertFalse(mir.isOnFire()); + assertFalse(mir.isDamaged()); + assertEquals("SpaceStationMir at [1,2,3,4] damaged=false onFire=false", mir.toString()); + } + + /** + * Test what happens we collide with an asteroid + */ + @Test + public void testCollideFlamingAsteroid() { + testCollision( + new FlamingAsteroid(1, 1, 3, 4), + false, true, + false, false, + "SpaceStationMir hits FlamingAsteroid." + ); + } + + /** + * Test what happens we collide with an meteoroid + */ + @Test + public void testCollideMeteoroid() { + testCollision( + new Meteoroid(1, 1, 3, 4), + false, false, + false, false, + "SpaceStationMir hits Meteoroid." + ); + } + + /** + * Test what happens we collide with ISS + */ + @Test + public void testCollideSpaceStationIss() { + testCollision( + new SpaceStationIss(1, 1, 3, 4), + true, false, + false, false, + "SpaceStationMir hits SpaceStationIss. SpaceStationIss is damaged!" + ); + } + + /** + * Test what happens we collide with MIR + */ + @Test + public void testCollideSpaceStationMir() { + testCollision( + new SpaceStationMir(1, 1, 3, 4), + true, false, + false, false, + "SpaceStationMir hits SpaceStationMir. SpaceStationMir is damaged!" + ); + } + +} \ No newline at end of file diff --git a/event-aggregator/index.md b/event-aggregator/index.md index 1c89c7188..05e94e0de 100644 --- a/event-aggregator/index.md +++ b/event-aggregator/index.md @@ -4,7 +4,9 @@ title: Event Aggregator folder: event-aggregator permalink: /patterns/event-aggregator/ categories: Structural -tags: Java +tags: + - Java + - Difficulty-Beginner --- **Intent:** A system with lots of objects can lead to complexities when a diff --git a/event-aggregator/pom.xml b/event-aggregator/pom.xml index d5001f992..70d585cbb 100644 --- a/event-aggregator/pom.xml +++ b/event-aggregator/pom.xml @@ -4,7 +4,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT event-aggregator @@ -13,5 +13,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java index 0cb76c215..d0c37c3a8 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.event.aggregator; import org.junit.Test; -import com.iluwatar.event.aggregator.App; - /** * * Application test diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java new file mode 100644 index 000000000..37bd36da4 --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java @@ -0,0 +1,133 @@ +package com.iluwatar.event.aggregator; + +import org.junit.Test; + +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/12/15 - 10:58 PM + * + * @author Jeroen Meulemeester + */ +public abstract class EventEmitterTest { + + /** + * Factory used to create a new instance of the test object with a default observer + */ + private final Function factoryWithDefaultObserver; + + /** + * Factory used to create a new instance of the test object without passing a default observer + */ + private final Supplier factoryWithoutDefaultObserver; + + /** + * The day of the week an event is expected + */ + private final Weekday specialDay; + + /** + * The expected event, emitted on the special day + */ + private final Event event; + + /** + * Create a new event emitter test, using the given test object factories, special day and event + */ + EventEmitterTest(final Weekday specialDay, final Event event, + final Function factoryWithDefaultObserver, + final Supplier factoryWithoutDefaultObserver) { + + this.specialDay = specialDay; + this.event = event; + this.factoryWithDefaultObserver = Objects.requireNonNull(factoryWithDefaultObserver); + this.factoryWithoutDefaultObserver = Objects.requireNonNull(factoryWithoutDefaultObserver); + } + + /** + * Go over every day of the month, and check if the event is emitted on the given day. This test + * is executed twice, once without a default emitter and once with + */ + @Test + public void testAllDays() { + testAllDaysWithoutDefaultObserver(specialDay, event); + testAllDaysWithDefaultObserver(specialDay, event); + } + + /** + * Pass each week of the day, day by day to the event emitter and verify of the given observers + * received the correct event on the special day. + * + * @param specialDay The special day on which an event is emitted + * @param event The expected event emitted by the test object + * @param emitter The event emitter + * @param observers The registered observer mocks + */ + private void testAllDays(final Weekday specialDay, final Event event, final E emitter, + final EventObserver... observers) { + + for (final Weekday weekday : Weekday.values()) { + // Pass each week of the day, day by day to the event emitter + emitter.timePasses(weekday); + + if (weekday == specialDay) { + // On a special day, every observer should have received the event + for (final EventObserver observer : observers) { + verify(observer, times(1)).onEvent(eq(event)); + } + } else { + // On any other normal day, the observers should have received nothing at all + verifyZeroInteractions(observers); + } + } + + // The observers should not have received any additional events after the week + verifyNoMoreInteractions(observers); + } + + /** + * Go over every day of the month, and check if the event is emitted on the given day. Use an + * event emitter without a default observer + * + * @param specialDay The special day on which an event is emitted + * @param event The expected event emitted by the test object + */ + private void testAllDaysWithoutDefaultObserver(final Weekday specialDay, final Event event) { + final EventObserver observer1 = mock(EventObserver.class); + final EventObserver observer2 = mock(EventObserver.class); + + final E emitter = this.factoryWithoutDefaultObserver.get(); + emitter.registerObserver(observer1); + emitter.registerObserver(observer2); + + testAllDays(specialDay, event, emitter, observer1, observer2); + } + + /** + * Go over every day of the month, and check if the event is emitted on the given day. + * + * @param specialDay The special day on which an event is emitted + * @param event The expected event emitted by the test object + */ + private void testAllDaysWithDefaultObserver(final Weekday specialDay, final Event event) { + final EventObserver defaultObserver = mock(EventObserver.class); + final EventObserver observer1 = mock(EventObserver.class); + final EventObserver observer2 = mock(EventObserver.class); + + final E emitter = this.factoryWithDefaultObserver.apply(defaultObserver); + emitter.registerObserver(observer1); + emitter.registerObserver(observer2); + + testAllDays(specialDay, event, emitter, defaultObserver, observer1, observer2); + } + +} diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventTest.java new file mode 100644 index 000000000..3f2cdb0fe --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventTest.java @@ -0,0 +1,27 @@ +package com.iluwatar.event.aggregator; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/12/15 - 2:52 PM + * + * @author Jeroen Meulemeester + */ +public class EventTest { + + /** + * Verify if every event has a non-null, non-empty description + */ + @Test + public void testToString() { + for (final Event event : Event.values()) { + final String toString = event.toString(); + assertNotNull(toString); + assertFalse(toString.trim().isEmpty()); + } + } + +} \ No newline at end of file diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java new file mode 100644 index 000000000..c1d054936 --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java @@ -0,0 +1,67 @@ +package com.iluwatar.event.aggregator; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/12/15 - 3:04 PM + * + * @author Jeroen Meulemeester + */ +public class KingJoffreyTest { + + /** + * The mocked standard out {@link PrintStream}, required since {@link KingJoffrey} does nothing + * except for writing to std-out using {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Test if {@link KingJoffrey} tells us what event he received + */ + @Test + public void testOnEvent() { + final KingJoffrey kingJoffrey = new KingJoffrey(); + + for (final Event event : Event.values()) { + verifyZeroInteractions(this.stdOutMock); + kingJoffrey.onEvent(event); + + final String expectedMessage = "Received event from the King's Hand: " + event.toString(); + verify(this.stdOutMock, times(1)).println(expectedMessage); + verifyNoMoreInteractions(this.stdOutMock); + } + + } + +} \ No newline at end of file diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java new file mode 100644 index 000000000..e62bb3f52 --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java @@ -0,0 +1,48 @@ +package com.iluwatar.event.aggregator; + +import org.junit.Test; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/12/15 - 10:57 AM + * + * @author Jeroen Meulemeester + */ +public class KingsHandTest extends EventEmitterTest { + + /** + * Create a new test instance, using the correct object factory + */ + public KingsHandTest() { + super(null, null, KingsHand::new, KingsHand::new); + } + + /** + * The {@link KingsHand} is both an {@link EventEmitter} as an {@link EventObserver} so verify if every + * event received is passed up to it's superior, in most cases {@link KingJoffrey} but now just a + * mocked observer. + */ + @Test + public void testPassThrough() throws Exception { + final EventObserver observer = mock(EventObserver.class); + final KingsHand kingsHand = new KingsHand(observer); + + // The kings hand should not pass any events before he received one + verifyZeroInteractions(observer); + + // Verify if each event is passed on to the observer, nothing less, nothing more. + for (final Event event : Event.values()) { + kingsHand.onEvent(event); + verify(observer, times(1)).onEvent(eq(event)); + verifyNoMoreInteractions(observer); + } + + } + +} \ No newline at end of file diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordBaelishTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordBaelishTest.java new file mode 100644 index 000000000..dbc867859 --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordBaelishTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.event.aggregator; + +/** + * Date: 12/12/15 - 10:57 AM + * + * @author Jeroen Meulemeester + */ +public class LordBaelishTest extends EventEmitterTest { + + /** + * Create a new test instance, using the correct object factory + */ + public LordBaelishTest() { + super(Weekday.FRIDAY, Event.STARK_SIGHTED, LordBaelish::new, LordBaelish::new); + } + +} \ No newline at end of file diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordVarysTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordVarysTest.java new file mode 100644 index 000000000..050af5576 --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordVarysTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.event.aggregator; + +/** + * Date: 12/12/15 - 10:57 AM + * + * @author Jeroen Meulemeester + */ +public class LordVarysTest extends EventEmitterTest { + + /** + * Create a new test instance, using the correct object factory + */ + public LordVarysTest() { + super(Weekday.SATURDAY, Event.TRAITOR_DETECTED, LordVarys::new, LordVarys::new); + } + +} \ No newline at end of file diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java new file mode 100644 index 000000000..435e07821 --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.event.aggregator; + +/** + * Date: 12/12/15 - 10:57 AM + * + * @author Jeroen Meulemeester + */ +public class ScoutTest extends EventEmitterTest { + + /** + * Create a new test instance, using the correct object factory + */ + public ScoutTest() { + super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new); + } + +} \ No newline at end of file diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/WeekdayTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/WeekdayTest.java new file mode 100644 index 000000000..37b300851 --- /dev/null +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/WeekdayTest.java @@ -0,0 +1,24 @@ +package com.iluwatar.event.aggregator; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/12/15 - 2:12 PM + * + * @author Jeroen Meulemeester + */ +public class WeekdayTest { + + @Test + public void testToString() throws Exception { + for (final Weekday weekday : Weekday.values()) { + final String toString = weekday.toString(); + assertNotNull(toString); + assertEquals(weekday.name(), toString.toUpperCase()); + } + } + +} \ No newline at end of file diff --git a/exclude-pmd.properties b/exclude-pmd.properties new file mode 100644 index 000000000..d97b3b827 --- /dev/null +++ b/exclude-pmd.properties @@ -0,0 +1,3 @@ +com.iluwatar.servicelayer.common.BaseEntity=UnusedPrivateField +com.iluwatar.doublechecked.locking.App=EmptyStatementNotInLoop,EmptyWhileStmt +com.iluwatar.doublechecked.locking.InventoryTest=EmptyStatementNotInLoop,EmptyWhileStmt diff --git a/execute-around/index.md b/execute-around/index.md index 56ece4ac4..8e62d5a8c 100644 --- a/execute-around/index.md +++ b/execute-around/index.md @@ -4,7 +4,10 @@ title: Execute Around folder: execute-around permalink: /patterns/execute-around/ categories: Other -tags: Java +tags: + - Java + - Difficulty-Beginner + - Idiom --- **Intent:** Execute Around idiom frees the user from certain actions that diff --git a/execute-around/pom.xml b/execute-around/pom.xml index dae6a4dad..d644d6a0f 100644 --- a/execute-around/pom.xml +++ b/execute-around/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT execute-around diff --git a/execute-around/src/main/java/com/iluwatar/execute/around/App.java b/execute-around/src/main/java/com/iluwatar/execute/around/App.java index 4a0648dbe..4695b8df5 100644 --- a/execute-around/src/main/java/com/iluwatar/execute/around/App.java +++ b/execute-around/src/main/java/com/iluwatar/execute/around/App.java @@ -17,9 +17,6 @@ public class App { /** * Program entry point - * - * @param args command line args - * @throws IOException */ public static void main(String[] args) throws IOException { diff --git a/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java b/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java index be89ff9ce..e1a9073ef 100644 --- a/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java +++ b/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java @@ -11,6 +11,9 @@ import java.io.IOException; */ public class SimpleFileWriter { + /** + * Constructor + */ public SimpleFileWriter(String filename, FileWriterAction action) throws IOException { FileWriter writer = new FileWriter(filename); try { diff --git a/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java b/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java index 9eb3dbf5f..80dff657b 100644 --- a/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java +++ b/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java @@ -1,13 +1,11 @@ package com.iluwatar.execute.around; -import java.io.File; -import java.io.IOException; - import org.junit.After; import org.junit.Before; import org.junit.Test; -import com.iluwatar.execute.around.App; +import java.io.File; +import java.io.IOException; /** * diff --git a/execute-around/src/test/java/com/iluwatar/execute/around/SimpleFileWriterTest.java b/execute-around/src/test/java/com/iluwatar/execute/around/SimpleFileWriterTest.java new file mode 100644 index 000000000..1f4380fe4 --- /dev/null +++ b/execute-around/src/test/java/com/iluwatar/execute/around/SimpleFileWriterTest.java @@ -0,0 +1,74 @@ +package com.iluwatar.execute.around; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/12/15 - 3:21 PM + * + * @author Jeroen Meulemeester + */ +public class SimpleFileWriterTest { + + /** + * Create a temporary folder, used to generate files in during this test + */ + @Rule + public final TemporaryFolder testFolder = new TemporaryFolder(); + + /** + * Verify if the given writer is not 'null' + */ + @Test + public void testWriterNotNull() throws Exception { + final File temporaryFile = this.testFolder.newFile(); + new SimpleFileWriter(temporaryFile.getPath(), Assert::assertNotNull); + } + + /** + * Test if the {@link SimpleFileWriter} creates a file if it doesn't exist + */ + @Test + public void testNonExistentFile() throws Exception { + final File nonExistingFile = new File(this.testFolder.getRoot(), "non-existing-file"); + assertFalse(nonExistingFile.exists()); + + new SimpleFileWriter(nonExistingFile.getPath(), Assert::assertNotNull); + assertTrue(nonExistingFile.exists()); + } + + /** + * Test if the data written to the file writer actually gets in the file + */ + @Test + public void testActualWrite() throws Exception { + final String testMessage = "Test message"; + + final File temporaryFile = this.testFolder.newFile(); + assertTrue(temporaryFile.exists()); + + new SimpleFileWriter(temporaryFile.getPath(), writer -> writer.write(testMessage)); + assertTrue(Files.lines(temporaryFile.toPath()).allMatch(testMessage::equals)); + } + + /** + * Verify if an {@link IOException} during the write ripples through + */ + @Test(expected = IOException.class) + public void testIoException() throws Exception { + final File temporaryFile = this.testFolder.newFile(); + new SimpleFileWriter(temporaryFile.getPath(), writer -> { + throw new IOException(""); + }); + } + +} diff --git a/facade/index.md b/facade/index.md index 59ff888b2..9ad9cb985 100644 --- a/facade/index.md +++ b/facade/index.md @@ -7,6 +7,7 @@ categories: Structural tags: - Java - Gang Of Four + - Difficulty-Beginner --- **Intent:** Provide a unified interface to a set of interfaces in a subsystem. diff --git a/facade/pom.xml b/facade/pom.xml index 4447cc03b..56f308ae4 100644 --- a/facade/pom.xml +++ b/facade/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT facade @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java b/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java index d6b653eaa..fd37e40c5 100644 --- a/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java +++ b/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java @@ -16,6 +16,9 @@ public class DwarvenGoldmineFacade { private final List workers; + /** + * Constructor + */ public DwarvenGoldmineFacade() { workers = new ArrayList<>(); workers.add(new DwarvenGoldDigger()); diff --git a/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java b/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java index d329fe84b..3190c9365 100644 --- a/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java +++ b/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java @@ -46,6 +46,9 @@ public abstract class DwarvenMineWorker { } } + /** + * Perform actions + */ public void action(Action... actions) { for (Action action : actions) { action(action); diff --git a/facade/src/test/java/com/iluwatar/facade/AppTest.java b/facade/src/test/java/com/iluwatar/facade/AppTest.java index 49b7c01c4..3de38f26e 100644 --- a/facade/src/test/java/com/iluwatar/facade/AppTest.java +++ b/facade/src/test/java/com/iluwatar/facade/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.facade; import org.junit.Test; -import com.iluwatar.facade.App; - /** * * Application test diff --git a/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java b/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java new file mode 100644 index 000000000..9a9bc5d66 --- /dev/null +++ b/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java @@ -0,0 +1,103 @@ +package com.iluwatar.facade; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.PrintStream; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.internal.verification.VerificationModeFactory.times; + +/** + * Date: 12/9/15 - 9:40 PM + * + * @author Jeroen Meulemeester + */ +public class DwarvenGoldmineFacadeTest { + + /** + * The mocked standard out {@link PrintStream}, required since the actions on the gold mine facade + * don't have any influence on any other accessible objects, except for writing to std-out using + * {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Test a complete day cycle in the gold mine by executing all three different steps: {@link + * DwarvenGoldmineFacade#startNewDay()}, {@link DwarvenGoldmineFacade#digOutGold()} and {@link + * DwarvenGoldmineFacade#endDay()}. + * + * See if the workers are doing what's expected from them on each step. + */ + @Test + public void testFullWorkDay() { + final DwarvenGoldmineFacade goldMine = new DwarvenGoldmineFacade(); + goldMine.startNewDay(); + + // On the start of a day, all workers should wake up ... + verify(this.stdOutMock, times(1)).println(eq("Dwarf gold digger wakes up.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarf cart operator wakes up.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarven tunnel digger wakes up.")); + + // ... and go to the mine + verify(this.stdOutMock, times(1)).println(eq("Dwarf gold digger goes to the mine.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarf cart operator goes to the mine.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarven tunnel digger goes to the mine.")); + + // No other actions were invoked, so the workers shouldn't have done (printed) anything else + verifyNoMoreInteractions(this.stdOutMock); + + // Now do some actual work, start digging gold! + goldMine.digOutGold(); + + // Since we gave the dig command, every worker should be doing it's job ... + verify(this.stdOutMock, times(1)).println(eq("Dwarf gold digger digs for gold.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarf cart operator moves gold chunks out of the mine.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarven tunnel digger creates another promising tunnel.")); + + // Again, they shouldn't be doing anything else. + verifyNoMoreInteractions(this.stdOutMock); + + // Enough gold, lets end the day. + goldMine.endDay(); + + // Check if the workers go home ... + verify(this.stdOutMock, times(1)).println(eq("Dwarf gold digger goes home.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarf cart operator goes home.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarven tunnel digger goes home.")); + + // ... and go to sleep. We need well rested workers the next day :) + verify(this.stdOutMock, times(1)).println(eq("Dwarf gold digger goes to sleep.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarf cart operator goes to sleep.")); + verify(this.stdOutMock, times(1)).println(eq("Dwarven tunnel digger goes to sleep.")); + + // Every worker should be sleeping now, no other actions allowed + verifyNoMoreInteractions(this.stdOutMock); + } + +} diff --git a/factory-method/pom.xml b/factory-method/pom.xml index 97c1c5681..f3dc2646a 100644 --- a/factory-method/pom.xml +++ b/factory-method/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT factory-method diff --git a/faq.md b/faq.md index b98bc7589..69f7b795e 100644 --- a/faq.md +++ b/faq.md @@ -3,7 +3,7 @@ layout: page title: FAQ permalink: /faq/ icon: fa-question -page-index: 2 +page-index: 1 --- ### Q1: What is the difference between State and Strategy patterns? {#Q1} @@ -64,4 +64,4 @@ Flyweight. ### Q7: What are the differences between FluentInterface and Builder patterns? {#Q7} -Fluent interfaces are sometimes confused with the Builder pattern, because they share method chaining and a fluent usage. However, fluent interfaces are not primarily used to create shared (mutable) objects, but to configure complex objects without having to respecify the target object on every property change. \ No newline at end of file +Fluent interfaces are sometimes confused with the Builder pattern, because they share method chaining and a fluent usage. However, fluent interfaces are not primarily used to create shared (mutable) objects, but to configure complex objects without having to respecify the target object on every property change. diff --git a/fluentinterface/index.md b/fluentinterface/index.md index 27a4d1a26..0cabed598 100644 --- a/fluentinterface/index.md +++ b/fluentinterface/index.md @@ -7,6 +7,7 @@ categories: Other tags: - Java - Difficulty-Intermediate + - Functional --- **Intent:** A fluent interface provides an easy-readable, flowing interface, that often mimics a domain specific language. Using this pattern results in code that can be read nearly as human language. diff --git a/fluentinterface/pom.xml b/fluentinterface/pom.xml index 48ec08161..1260bad3d 100644 --- a/fluentinterface/pom.xml +++ b/fluentinterface/pom.xml @@ -5,7 +5,7 @@ java-design-patterns com.iluwatar - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT 4.0.0 @@ -16,5 +16,10 @@ junit test + + org.mockito + mockito-core + test + \ No newline at end of file diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/app/App.java b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/app/App.java index bdff83e17..4e5ab3767 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/app/App.java +++ b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/app/App.java @@ -1,15 +1,19 @@ package com.iluwatar.fluentinterface.app; +import static java.lang.String.valueOf; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.StringJoiner; +import java.util.function.Function; +import java.util.function.Predicate; + import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; import com.iluwatar.fluentinterface.fluentiterable.lazy.LazyFluentIterable; import com.iluwatar.fluentinterface.fluentiterable.simple.SimpleFluentIterable; -import java.util.*; -import java.util.function.Function; -import java.util.function.Predicate; - -import static java.lang.String.valueOf; - /** * The Fluent Interface pattern is useful when you want to provide an easy readable, flowing API. * Those interfaces tend to mimic domain specific languages, so they can nearly be read as human @@ -24,6 +28,9 @@ import static java.lang.String.valueOf; */ public class App { + /** + * Program entry point + */ public static void main(String[] args) { List integerList = new ArrayList<>(); @@ -58,7 +65,7 @@ public class App { List lastTwoOfFirstFourStringMapped = LazyFluentIterable.from(integerList).filter(positives()).first(4).last(2) - .map(number -> "String[" + String.valueOf(number) + "]").asList(); + .map(number -> "String[" + valueOf(number) + "]").asList(); prettyPrint( "The lazy list contains the last two of the first four positive numbers mapped to Strings: ", lastTwoOfFirstFourStringMapped); @@ -78,19 +85,19 @@ public class App { } private static Predicate negatives() { - return integer -> (integer < 0); + return integer -> integer < 0; } private static Predicate positives() { - return integer -> (integer > 0); + return integer -> integer > 0; } private static void prettyPrint(String prefix, Iterable iterable) { - prettyPrint(", ", prefix, ".", iterable); + prettyPrint(", ", prefix, iterable); } - private static void prettyPrint(String delimiter, String prefix, String suffix, - Iterable iterable) { + private static void prettyPrint(String delimiter, String prefix, + Iterable iterable) { StringJoiner joiner = new StringJoiner(delimiter, prefix, "."); Iterator iterator = iterable.iterator(); while (iterator.hasNext()) { diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java index e80356d8e..dae300c4e 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java +++ b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java @@ -5,8 +5,6 @@ import java.util.Iterator; /** * This class is used to realize LazyFluentIterables. It decorates a given iterator. Does not * support consecutive hasNext() calls. - * - * @param */ public abstract class DecoratingIterator implements Iterator { @@ -16,8 +14,6 @@ public abstract class DecoratingIterator implements Iterator { /** * Creates an iterator that decorates the given iterator. - * - * @param fromIterator */ public DecoratingIterator(Iterator fromIterator) { this.fromIterator = fromIterator; diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java index 560b10189..e887ad556 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java +++ b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java @@ -53,10 +53,9 @@ public class LazyFluentIterable implements FluentIterable { public TYPE computeNext() { while (fromIterator.hasNext()) { TYPE candidate = fromIterator.next(); - if (!predicate.test(candidate)) { - continue; + if (predicate.test(candidate)) { + return candidate; } - return candidate; } return null; @@ -94,12 +93,10 @@ public class LazyFluentIterable implements FluentIterable { @Override public TYPE computeNext() { - if (currentIndex < count) { - if (fromIterator.hasNext()) { - TYPE candidate = fromIterator.next(); - currentIndex++; - return candidate; - } + if (currentIndex < count && fromIterator.hasNext()) { + TYPE candidate = fromIterator.next(); + currentIndex++; + return candidate; } return null; } @@ -188,11 +185,12 @@ public class LazyFluentIterable implements FluentIterable { @Override public NEW_TYPE computeNext() { - while (oldTypeIterator.hasNext()) { + if (oldTypeIterator.hasNext()) { TYPE candidate = oldTypeIterator.next(); return function.apply(candidate); + } else { + return null; } - return null; } }; } @@ -215,7 +213,7 @@ public class LazyFluentIterable implements FluentIterable { return new DecoratingIterator(iterable.iterator()) { @Override public TYPE computeNext() { - return fromIterator.next(); + return fromIterator.hasNext() ? fromIterator.next() : null; } }; } diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java index 19283152e..ef1859529 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java +++ b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java @@ -1,12 +1,16 @@ package com.iluwatar.fluentinterface.fluentiterable.simple; -import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; - -import java.util.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Spliterator; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; +import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; + /** * This is a simple implementation of the FluentIterable interface. It evaluates all chained * operations eagerly. This implementation would be costly to be utilized in real applications. diff --git a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java index 29ad885c0..2268b0428 100644 --- a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java +++ b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.fluentinterface.app; import org.junit.Test; -import com.iluwatar.fluentinterface.app.App; - public class AppTest { @Test diff --git a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java new file mode 100644 index 000000000..baabbe096 --- /dev/null +++ b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java @@ -0,0 +1,170 @@ +package com.iluwatar.fluentinterface.fluentiterable; + +import org.junit.Test; + +import java.util.*; +import java.util.function.Consumer; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Date: 12/12/15 - 7:00 PM + * + * @author Jeroen Meulemeester + */ +public abstract class FluentIterableTest { + + /** + * Create a new {@link FluentIterable} from the given integers + * + * @param integers The integers + * @return The new iterable, use for testing + */ + protected abstract FluentIterable createFluentIterable(final Iterable integers); + + @Test + public void testFirst() throws Exception { + final List integers = Arrays.asList(1, 2, 3, 10, 9, 8); + final Optional first = createFluentIterable(integers).first(); + assertNotNull(first); + assertTrue(first.isPresent()); + assertEquals(integers.get(0), first.get()); + } + + @Test + public void testFirstEmptyCollection() throws Exception { + final List integers = Collections.emptyList(); + final Optional first = createFluentIterable(integers).first(); + assertNotNull(first); + assertFalse(first.isPresent()); + } + + @Test + public void testFirstCount() throws Exception { + final List integers = Arrays.asList(1, 2, 3, 10, 9, 8); + final List first4 = createFluentIterable(integers) + .first(4) + .asList(); + + assertNotNull(first4); + assertEquals(4, first4.size()); + + assertEquals(integers.get(0), first4.get(0)); + assertEquals(integers.get(1), first4.get(1)); + assertEquals(integers.get(2), first4.get(2)); + assertEquals(integers.get(3), first4.get(3)); + } + + @Test + public void testFirstCountLessItems() throws Exception { + final List integers = Arrays.asList(1, 2, 3); + final List first4 = createFluentIterable(integers) + .first(4) + .asList(); + + assertNotNull(first4); + assertEquals(3, first4.size()); + + assertEquals(integers.get(0), first4.get(0)); + assertEquals(integers.get(1), first4.get(1)); + assertEquals(integers.get(2), first4.get(2)); + } + + @Test + public void testLast() throws Exception { + final List integers = Arrays.asList(1, 2, 3, 10, 9, 8); + final Optional last = createFluentIterable(integers).last(); + assertNotNull(last); + assertTrue(last.isPresent()); + assertEquals(integers.get(integers.size() - 1), last.get()); + } + + @Test + public void testLastEmptyCollection() throws Exception { + final List integers = Collections.emptyList(); + final Optional last = createFluentIterable(integers).last(); + assertNotNull(last); + assertFalse(last.isPresent()); + } + + @Test + public void testLastCount() throws Exception { + final List integers = Arrays.asList(1, 2, 3, 10, 9, 8); + final List last4 = createFluentIterable(integers) + .last(4) + .asList(); + + assertNotNull(last4); + assertEquals(4, last4.size()); + assertEquals(Integer.valueOf(3), last4.get(0)); + assertEquals(Integer.valueOf(10), last4.get(1)); + assertEquals(Integer.valueOf(9), last4.get(2)); + assertEquals(Integer.valueOf(8), last4.get(3)); + } + + @Test + public void testLastCountLessItems() throws Exception { + final List integers = Arrays.asList(1, 2, 3); + final List last4 = createFluentIterable(integers) + .last(4) + .asList(); + + assertNotNull(last4); + assertEquals(3, last4.size()); + + assertEquals(Integer.valueOf(1), last4.get(0)); + assertEquals(Integer.valueOf(2), last4.get(1)); + assertEquals(Integer.valueOf(3), last4.get(2)); + } + + @Test + public void testFilter() throws Exception { + final List integers = Arrays.asList(1, 2, 3, 10, 9, 8); + final List evenItems = createFluentIterable(integers) + .filter(i -> i % 2 == 0) + .asList(); + + assertNotNull(evenItems); + assertEquals(3, evenItems.size()); + assertEquals(Integer.valueOf(2), evenItems.get(0)); + assertEquals(Integer.valueOf(10), evenItems.get(1)); + assertEquals(Integer.valueOf(8), evenItems.get(2)); + } + + @Test + public void testMap() throws Exception { + final List integers = Arrays.asList(1, 2, 3); + final List longs = createFluentIterable(integers) + .map(Integer::longValue) + .asList(); + + assertNotNull(longs); + assertEquals(integers.size(), longs.size()); + assertEquals(Long.valueOf(1), longs.get(0)); + assertEquals(Long.valueOf(2), longs.get(1)); + assertEquals(Long.valueOf(3), longs.get(2)); + } + + @Test + public void testForEach() throws Exception { + final List integers = Arrays.asList(1, 2, 3); + + final Consumer consumer = mock(Consumer.class); + createFluentIterable(integers).forEach(consumer); + + verify(consumer, times(1)).accept(Integer.valueOf(1)); + verify(consumer, times(1)).accept(Integer.valueOf(2)); + verify(consumer, times(1)).accept(Integer.valueOf(3)); + verifyNoMoreInteractions(consumer); + + } + + @Test + public void testSpliterator() throws Exception { + final List integers = Arrays.asList(1, 2, 3); + final Spliterator split = createFluentIterable(integers).spliterator(); + assertNotNull(split); + } + +} \ No newline at end of file diff --git a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java new file mode 100644 index 000000000..aa51327e3 --- /dev/null +++ b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java @@ -0,0 +1,18 @@ +package com.iluwatar.fluentinterface.fluentiterable.lazy; + +import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; +import com.iluwatar.fluentinterface.fluentiterable.FluentIterableTest; + +/** + * Date: 12/12/15 - 7:56 PM + * + * @author Jeroen Meulemeester + */ +public class LazyFluentIterableTest extends FluentIterableTest { + + @Override + protected FluentIterable createFluentIterable(Iterable integers) { + return LazyFluentIterable.from(integers); + } + +} diff --git a/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java new file mode 100644 index 000000000..360d6e222 --- /dev/null +++ b/fluentinterface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java @@ -0,0 +1,18 @@ +package com.iluwatar.fluentinterface.fluentiterable.simple; + +import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; +import com.iluwatar.fluentinterface.fluentiterable.FluentIterableTest; + +/** + * Date: 12/12/15 - 7:56 PM + * + * @author Jeroen Meulemeester + */ +public class SimpleFluentIterableTest extends FluentIterableTest { + + @Override + protected FluentIterable createFluentIterable(Iterable integers) { + return SimpleFluentIterable.fromCopyOf(integers); + } + +} diff --git a/flux/index.md b/flux/index.md index 227237168..c1ef0b4f9 100644 --- a/flux/index.md +++ b/flux/index.md @@ -4,7 +4,9 @@ title: Flux folder: flux permalink: /patterns/flux/ categories: Presentation Tier -tags: Java +tags: + - Java + - Difficulty-Intermediate --- **Intent:** Flux eschews MVC in favor of a unidirectional data flow. When a diff --git a/flux/pom.xml b/flux/pom.xml index 28a634ceb..aff383800 100644 --- a/flux/pom.xml +++ b/flux/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT flux @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java b/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java index 26c836b0e..1ff624e11 100644 --- a/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java +++ b/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java @@ -31,6 +31,9 @@ public class Dispatcher { stores.add(store); } + /** + * Menu item selected handler + */ public void menuItemSelected(MenuItem menuItem) { dispatchAction(new MenuAction(menuItem)); switch (menuItem) { diff --git a/flux/src/main/java/com/iluwatar/flux/view/View.java b/flux/src/main/java/com/iluwatar/flux/view/View.java index a642b5b2c..892527fdc 100644 --- a/flux/src/main/java/com/iluwatar/flux/view/View.java +++ b/flux/src/main/java/com/iluwatar/flux/view/View.java @@ -9,7 +9,7 @@ import com.iluwatar.flux.store.Store; */ public interface View { - public void storeChanged(Store store); + void storeChanged(Store store); - public void render(); + void render(); } diff --git a/flux/src/test/java/com/iluwatar/flux/action/ContentTest.java b/flux/src/test/java/com/iluwatar/flux/action/ContentTest.java new file mode 100644 index 000000000..7781c1d90 --- /dev/null +++ b/flux/src/test/java/com/iluwatar/flux/action/ContentTest.java @@ -0,0 +1,24 @@ +package com.iluwatar.flux.action; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/12/15 - 10:11 PM + * + * @author Jeroen Meulemeester + */ +public class ContentTest { + + @Test + public void testToString() throws Exception { + for (final Content content : Content.values()) { + final String toString = content.toString(); + assertNotNull(toString); + assertFalse(toString.trim().isEmpty()); + } + } + +} diff --git a/flux/src/test/java/com/iluwatar/flux/action/MenuItemTest.java b/flux/src/test/java/com/iluwatar/flux/action/MenuItemTest.java new file mode 100644 index 000000000..02fa781e6 --- /dev/null +++ b/flux/src/test/java/com/iluwatar/flux/action/MenuItemTest.java @@ -0,0 +1,24 @@ +package com.iluwatar.flux.action; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/12/15 - 10:15 PM + * + * @author Jeroen Meulemeester + */ +public class MenuItemTest { + + @Test + public void testToString() throws Exception { + for (final MenuItem menuItem : MenuItem.values()) { + final String toString = menuItem.toString(); + assertNotNull(toString); + assertFalse(toString.trim().isEmpty()); + } + } + +} diff --git a/flux/src/test/java/com/iluwatar/flux/app/AppTest.java b/flux/src/test/java/com/iluwatar/flux/app/AppTest.java index 918c76acd..d833c321c 100644 --- a/flux/src/test/java/com/iluwatar/flux/app/AppTest.java +++ b/flux/src/test/java/com/iluwatar/flux/app/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.flux.app; import org.junit.Test; -import com.iluwatar.flux.app.App; - /** * * Application test diff --git a/flux/src/test/java/com/iluwatar/flux/dispatcher/DispatcherTest.java b/flux/src/test/java/com/iluwatar/flux/dispatcher/DispatcherTest.java new file mode 100644 index 000000000..7c66e7a81 --- /dev/null +++ b/flux/src/test/java/com/iluwatar/flux/dispatcher/DispatcherTest.java @@ -0,0 +1,91 @@ +package com.iluwatar.flux.dispatcher; + +import com.iluwatar.flux.action.Action; +import com.iluwatar.flux.action.ActionType; +import com.iluwatar.flux.action.Content; +import com.iluwatar.flux.action.ContentAction; +import com.iluwatar.flux.action.MenuAction; +import com.iluwatar.flux.action.MenuItem; +import com.iluwatar.flux.store.Store; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/12/15 - 8:22 PM + * + * @author Jeroen Meulemeester + */ +public class DispatcherTest { + + /** + * Dispatcher is a singleton with no way to reset it's internal state back to the beginning. + * Replace the instance with a fresh one before each test to make sure test cases have no + * influence on each other. + */ + @Before + public void setUp() throws Exception { + final Constructor constructor; + constructor = Dispatcher.class.getDeclaredConstructor(); + constructor.setAccessible(true); + + final Field field = Dispatcher.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(Dispatcher.getInstance(), constructor.newInstance()); + } + + @Test + public void testGetInstance() throws Exception { + assertNotNull(Dispatcher.getInstance()); + assertSame(Dispatcher.getInstance(), Dispatcher.getInstance()); + } + + @Test + public void testMenuItemSelected() throws Exception { + final Dispatcher dispatcher = Dispatcher.getInstance(); + + final Store store = mock(Store.class); + dispatcher.registerStore(store); + dispatcher.menuItemSelected(MenuItem.HOME); + dispatcher.menuItemSelected(MenuItem.COMPANY); + + // We expect 4 events, 2 menu selections and 2 content change actions + final ArgumentCaptor actionCaptor = ArgumentCaptor.forClass(Action.class); + verify(store, times(4)).onAction(actionCaptor.capture()); + verifyNoMoreInteractions(store); + + final List actions = actionCaptor.getAllValues(); + final List menuActions = actions.stream() + .filter(a -> a.getType().equals(ActionType.MENU_ITEM_SELECTED)) + .map(a -> (MenuAction) a) + .collect(Collectors.toList()); + + final List contentActions = actions.stream() + .filter(a -> a.getType().equals(ActionType.CONTENT_CHANGED)) + .map(a -> (ContentAction) a) + .collect(Collectors.toList()); + + assertEquals(2, menuActions.size()); + assertEquals(1, menuActions.stream().map(MenuAction::getMenuItem).filter(MenuItem.HOME::equals).count()); + assertEquals(1, menuActions.stream().map(MenuAction::getMenuItem).filter(MenuItem.COMPANY::equals).count()); + + assertEquals(2, contentActions.size()); + assertEquals(1, contentActions.stream().map(ContentAction::getContent).filter(Content.PRODUCTS::equals).count()); + assertEquals(1, contentActions.stream().map(ContentAction::getContent).filter(Content.COMPANY::equals).count()); + + } + +} diff --git a/flux/src/test/java/com/iluwatar/flux/store/ContentStoreTest.java b/flux/src/test/java/com/iluwatar/flux/store/ContentStoreTest.java new file mode 100644 index 000000000..00a7a924d --- /dev/null +++ b/flux/src/test/java/com/iluwatar/flux/store/ContentStoreTest.java @@ -0,0 +1,46 @@ +package com.iluwatar.flux.store; + +import com.iluwatar.flux.action.Content; +import com.iluwatar.flux.action.ContentAction; +import com.iluwatar.flux.action.MenuAction; +import com.iluwatar.flux.action.MenuItem; +import com.iluwatar.flux.view.View; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/12/15 - 10:18 PM + * + * @author Jeroen Meulemeester + */ +public class ContentStoreTest { + + @Test + public void testOnAction() throws Exception { + final ContentStore contentStore = new ContentStore(); + + final View view = mock(View.class); + contentStore.registerView(view); + + verifyZeroInteractions(view); + + // Content should not react on menu action ... + contentStore.onAction(new MenuAction(MenuItem.PRODUCTS)); + verifyZeroInteractions(view); + + // ... but it should react on a content action + contentStore.onAction(new ContentAction(Content.COMPANY)); + verify(view, times(1)).storeChanged(eq(contentStore)); + verifyNoMoreInteractions(view); + assertEquals(Content.COMPANY, contentStore.getContent()); + + } + +} diff --git a/flux/src/test/java/com/iluwatar/flux/store/MenuStoreTest.java b/flux/src/test/java/com/iluwatar/flux/store/MenuStoreTest.java new file mode 100644 index 000000000..6fdc4e5d3 --- /dev/null +++ b/flux/src/test/java/com/iluwatar/flux/store/MenuStoreTest.java @@ -0,0 +1,46 @@ +package com.iluwatar.flux.store; + +import com.iluwatar.flux.action.Content; +import com.iluwatar.flux.action.ContentAction; +import com.iluwatar.flux.action.MenuAction; +import com.iluwatar.flux.action.MenuItem; +import com.iluwatar.flux.view.View; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/12/15 - 10:18 PM + * + * @author Jeroen Meulemeester + */ +public class MenuStoreTest { + + @Test + public void testOnAction() throws Exception { + final MenuStore menuStore = new MenuStore(); + + final View view = mock(View.class); + menuStore.registerView(view); + + verifyZeroInteractions(view); + + // Menu should not react on content action ... + menuStore.onAction(new ContentAction(Content.COMPANY)); + verifyZeroInteractions(view); + + // ... but it should react on a menu action + menuStore.onAction(new MenuAction(MenuItem.PRODUCTS)); + verify(view, times(1)).storeChanged(eq(menuStore)); + verifyNoMoreInteractions(view); + assertEquals(MenuItem.PRODUCTS, menuStore.getSelected()); + + } + +} diff --git a/flux/src/test/java/com/iluwatar/flux/view/ContentViewTest.java b/flux/src/test/java/com/iluwatar/flux/view/ContentViewTest.java new file mode 100644 index 000000000..cf452233b --- /dev/null +++ b/flux/src/test/java/com/iluwatar/flux/view/ContentViewTest.java @@ -0,0 +1,32 @@ +package com.iluwatar.flux.view; + +import com.iluwatar.flux.action.Content; +import com.iluwatar.flux.store.ContentStore; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/12/15 - 10:31 PM + * + * @author Jeroen Meulemeester + */ +public class ContentViewTest { + + @Test + public void testStoreChanged() throws Exception { + final ContentStore store = mock(ContentStore.class); + when(store.getContent()).thenReturn(Content.PRODUCTS); + + final ContentView view = new ContentView(); + view.storeChanged(store); + + verify(store, times(1)).getContent(); + verifyNoMoreInteractions(store); + } + +} diff --git a/flux/src/test/java/com/iluwatar/flux/view/MenuViewTest.java b/flux/src/test/java/com/iluwatar/flux/view/MenuViewTest.java new file mode 100644 index 000000000..08a601c71 --- /dev/null +++ b/flux/src/test/java/com/iluwatar/flux/view/MenuViewTest.java @@ -0,0 +1,49 @@ +package com.iluwatar.flux.view; + +import com.iluwatar.flux.action.Action; +import com.iluwatar.flux.action.MenuItem; +import com.iluwatar.flux.dispatcher.Dispatcher; +import com.iluwatar.flux.store.MenuStore; +import com.iluwatar.flux.store.Store; +import org.junit.Test; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +/** + * Date: 12/12/15 - 10:31 PM + * + * @author Jeroen Meulemeester + */ +public class MenuViewTest { + + @Test + public void testStoreChanged() throws Exception { + final MenuStore store = mock(MenuStore.class); + when(store.getSelected()).thenReturn(MenuItem.HOME); + + final MenuView view = new MenuView(); + view.storeChanged(store); + + verify(store, times(1)).getSelected(); + verifyNoMoreInteractions(store); + } + + @Test + public void testItemClicked() throws Exception { + final Store store = mock(Store.class); + Dispatcher.getInstance().registerStore(store); + + final MenuView view = new MenuView(); + view.itemClicked(MenuItem.PRODUCTS); + + // We should receive a menu click action and a content changed action + verify(store, times(2)).onAction(any(Action.class)); + + } + +} diff --git a/flyweight/index.md b/flyweight/index.md index e2273c197..41a45f555 100644 --- a/flyweight/index.md +++ b/flyweight/index.md @@ -7,6 +7,8 @@ categories: Structural tags: - Java - Gang Of Four + - Difficulty-Intermediate + - Performance --- **Intent:** Use sharing to support large numbers of fine-grained objects diff --git a/flyweight/pom.xml b/flyweight/pom.xml index 3aa89d15c..fe282ea2e 100644 --- a/flyweight/pom.xml +++ b/flyweight/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT flyweight diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java b/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java index 15206a84a..8418e01e6 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java @@ -1,6 +1,7 @@ package com.iluwatar.flyweight; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -13,6 +14,9 @@ public class AlchemistShop { private List topShelf; private List bottomShelf; + /** + * Constructor + */ public AlchemistShop() { topShelf = new ArrayList<>(); bottomShelf = new ArrayList<>(); @@ -39,6 +43,27 @@ public class AlchemistShop { bottomShelf.add(factory.createPotion(PotionType.HOLY_WATER)); } + /** + * Get a read-only list of all the items on the top shelf + * + * @return The top shelf potions + */ + public final List getTopShelf() { + return Collections.unmodifiableList(this.topShelf); + } + + /** + * Get a read-only list of all the items on the bottom shelf + * + * @return The bottom shelf potions + */ + public final List getBottomShelf() { + return Collections.unmodifiableList(this.bottomShelf); + } + + /** + * Enumerate potions + */ public void enumerate() { System.out.println("Enumerating top shelf potions\n"); diff --git a/flyweight/src/test/java/com/iluwatar/flyweight/AlchemistShopTest.java b/flyweight/src/test/java/com/iluwatar/flyweight/AlchemistShopTest.java new file mode 100644 index 000000000..d99a98cf9 --- /dev/null +++ b/flyweight/src/test/java/com/iluwatar/flyweight/AlchemistShopTest.java @@ -0,0 +1,40 @@ +package com.iluwatar.flyweight; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/12/15 - 10:54 PM + * + * @author Jeroen Meulemeester + */ +public class AlchemistShopTest { + + @Test + public void testShop() throws Exception { + final AlchemistShop shop = new AlchemistShop(); + + final List bottomShelf = shop.getBottomShelf(); + assertNotNull(bottomShelf); + assertEquals(5, bottomShelf.size()); + + final List topShelf = shop.getTopShelf(); + assertNotNull(topShelf); + assertEquals(8, topShelf.size()); + + final List allPotions = new ArrayList<>(); + allPotions.addAll(topShelf); + allPotions.addAll(bottomShelf); + + // There are 13 potion instances, but only 5 unique instance types + assertEquals(13, allPotions.size()); + assertEquals(5, allPotions.stream().map(System::identityHashCode).distinct().count()); + + } + +} diff --git a/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java b/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java index 16fdb005e..5e0bd98b8 100644 --- a/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java +++ b/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.flyweight; import org.junit.Test; -import com.iluwatar.flyweight.App; - /** * * Application test diff --git a/front-controller/index.md b/front-controller/index.md index ba593a157..603bfef2b 100644 --- a/front-controller/index.md +++ b/front-controller/index.md @@ -4,7 +4,9 @@ title: Front Controller folder: front-controller permalink: /patterns/front-controller/ categories: Presentation Tier -tags: Java +tags: + - Java + - Difficulty-Intermediate --- **Intent:** Introduce a common handler for all requests for a web site. This diff --git a/front-controller/pom.xml b/front-controller/pom.xml index 5b0ef2155..3f56aaa8c 100644 --- a/front-controller/pom.xml +++ b/front-controller/pom.xml @@ -6,7 +6,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT front-controller @@ -15,5 +15,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/App.java b/front-controller/src/main/java/com/iluwatar/front/controller/App.java index 18a92d37d..1beac119c 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/App.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/App.java @@ -2,32 +2,34 @@ package com.iluwatar.front.controller; /** * - * The Front Controller is a presentation tier pattern. Essentially it defines a - * controller that handles all requests for a web site. + * The Front Controller is a presentation tier pattern. Essentially it defines a controller that + * handles all requests for a web site. *

- * The Front Controller pattern consolidates request handling through a single handler - * object ({@link FrontController}). This object can carry out the common the behavior such as + * The Front Controller pattern consolidates request handling through a single handler object ( + * {@link FrontController}). This object can carry out the common the behavior such as * authorization, request logging and routing requests to corresponding views. *

- * Typically the requests are mapped to command objects ({@link Command}) which then display - * the correct view ({@link View}). + * Typically the requests are mapped to command objects ({@link Command}) which then display the + * correct view ({@link View}). *

* In this example we have implemented two views: {@link ArcherView} and {@link CatapultView}. These - * are displayed by sending correct request to the {@link FrontController} object. For example, - * the {@link ArcherView} gets displayed when {@link FrontController} receives request "Archer". When + * are displayed by sending correct request to the {@link FrontController} object. For example, the + * {@link ArcherView} gets displayed when {@link FrontController} receives request "Archer". When * the request is unknown, we display the error view ({@link ErrorView}). * */ public class App { - - /** - * Program entry point - * @param args command line args - */ - public static void main(String[] args) { - FrontController controller = new FrontController(); - controller.handleRequest("Archer"); - controller.handleRequest("Catapult"); - controller.handleRequest("foobar"); - } + + /** + * Program entry point + * + * @param args + * command line args + */ + public static void main(String[] args) { + FrontController controller = new FrontController(); + controller.handleRequest("Archer"); + controller.handleRequest("Catapult"); + controller.handleRequest("foobar"); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java b/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java index b3963d8e9..bb44d34f0 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java @@ -7,9 +7,9 @@ package com.iluwatar.front.controller; */ public class ApplicationException extends RuntimeException { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - public ApplicationException(Throwable cause) { - super(cause); - } + public ApplicationException(Throwable cause) { + super(cause); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java index 117aa0c8c..8396d5cfc 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java @@ -7,8 +7,8 @@ package com.iluwatar.front.controller; */ public class ArcherCommand implements Command { - @Override - public void process() { - new ArcherView().display(); - } + @Override + public void process() { + new ArcherView().display(); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java index d8cae33c1..d16fe8b71 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java @@ -7,8 +7,8 @@ package com.iluwatar.front.controller; */ public class ArcherView implements View { - @Override - public void display() { - System.out.println("Displaying archers"); - } + @Override + public void display() { + System.out.println("Displaying archers"); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java index fae5d1753..b5ad9e37c 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java @@ -7,8 +7,8 @@ package com.iluwatar.front.controller; */ public class CatapultCommand implements Command { - @Override - public void process() { - new CatapultView().display(); - } + @Override + public void process() { + new CatapultView().display(); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java index 9ad94d522..161b4ed4e 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java @@ -7,8 +7,8 @@ package com.iluwatar.front.controller; */ public class CatapultView implements View { - @Override - public void display() { - System.out.println("Displaying catapults"); - } + @Override + public void display() { + System.out.println("Displaying catapults"); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/Command.java b/front-controller/src/main/java/com/iluwatar/front/controller/Command.java index 95bd00129..2ad41a629 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/Command.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/Command.java @@ -6,6 +6,6 @@ package com.iluwatar.front.controller; * */ public interface Command { - - void process(); + + void process(); } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java b/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java index 04c31dd34..c1045c821 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java @@ -7,8 +7,8 @@ package com.iluwatar.front.controller; */ public class ErrorView implements View { - @Override - public void display() { - System.out.println("Error 500"); - } + @Override + public void display() { + System.out.println("Error 500"); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java b/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java index 5a6dc2a78..6b84d7f78 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java @@ -2,33 +2,33 @@ package com.iluwatar.front.controller; /** * - * FrontController is the handler class that takes in all the requests and - * renders the correct response. + * FrontController is the handler class that takes in all the requests and renders the correct + * response. * */ public class FrontController { - - public void handleRequest(String request) { - Command command = getCommand(request); - command.process(); - } - - private Command getCommand(String request) { - Class commandClass = getCommandClass(request); - try { - return (Command) commandClass.newInstance(); - } catch (Exception e) { - throw new ApplicationException(e); - } - } - - private Class getCommandClass(String request) { - Class result; - try { - result = Class.forName("com.iluwatar." + request + "Command"); - } catch (ClassNotFoundException e) { - result = UnknownCommand.class; - } - return result; - } + + public void handleRequest(String request) { + Command command = getCommand(request); + command.process(); + } + + private Command getCommand(String request) { + Class commandClass = getCommandClass(request); + try { + return (Command) commandClass.newInstance(); + } catch (Exception e) { + throw new ApplicationException(e); + } + } + + private Class getCommandClass(String request) { + Class result; + try { + result = Class.forName("com.iluwatar.front.controller." + request + "Command"); + } catch (ClassNotFoundException e) { + result = UnknownCommand.class; + } + return result; + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java b/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java index f8f93e7e0..d800d4db0 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java @@ -7,8 +7,8 @@ package com.iluwatar.front.controller; */ public class UnknownCommand implements Command { - @Override - public void process() { - new ErrorView().display(); - } + @Override + public void process() { + new ErrorView().display(); + } } diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/View.java b/front-controller/src/main/java/com/iluwatar/front/controller/View.java index 29c5f0fcb..55bb187ce 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/View.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/View.java @@ -7,5 +7,5 @@ package com.iluwatar.front.controller; */ public interface View { - void display(); + void display(); } diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java index 2c28aa8ce..cc09de662 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java @@ -2,18 +2,16 @@ package com.iluwatar.front.controller; import org.junit.Test; -import com.iluwatar.front.controller.App; - /** * * Application test * */ public class AppTest { - - @Test - public void test() { - String[] args = {}; - App.main(args); - } + + @Test + public void test() { + String[] args = {}; + App.main(args); + } } diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/ApplicationExceptionTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/ApplicationExceptionTest.java new file mode 100644 index 000000000..4b038cfda --- /dev/null +++ b/front-controller/src/test/java/com/iluwatar/front/controller/ApplicationExceptionTest.java @@ -0,0 +1,20 @@ +package com.iluwatar.front.controller; + +import static org.junit.Assert.assertSame; + +import org.junit.Test; + +/** + * Date: 12/13/15 - 1:35 PM + * + * @author Jeroen Meulemeester + */ +public class ApplicationExceptionTest { + + @Test + public void testCause() throws Exception { + final Exception cause = new Exception(); + assertSame(cause, new ApplicationException(cause).getCause()); + } + +} \ No newline at end of file diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/CommandTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/CommandTest.java new file mode 100644 index 000000000..fa85caa39 --- /dev/null +++ b/front-controller/src/test/java/com/iluwatar/front/controller/CommandTest.java @@ -0,0 +1,62 @@ +package com.iluwatar.front.controller; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/13/15 - 1:39 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class CommandTest extends StdOutTest { + + @Parameters + public static List data() { + final List parameters = new ArrayList<>(); + parameters.add(new Object[]{"Archer", "Displaying archers"}); + parameters.add(new Object[]{"Catapult", "Displaying catapults"}); + parameters.add(new Object[]{"NonExistentCommand", "Error 500"}); + return parameters; + } + + /** + * The view that's been tested + */ + private final String request; + + /** + * The expected display message + */ + private final String displayMessage; + + /** + * Create a new instance of the {@link CommandTest} with the given view and expected message + * + * @param request The request that's been tested + * @param displayMessage The expected display message + */ + public CommandTest(final String request, final String displayMessage) { + this.displayMessage = displayMessage; + this.request = request; + } + + @Test + public void testDisplay() { + final FrontController frontController = new FrontController(); + verifyZeroInteractions(getStdOutMock()); + frontController.handleRequest(request); + verify(getStdOutMock()).println(displayMessage); + verifyNoMoreInteractions(getStdOutMock()); + } + +} diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/FrontControllerTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/FrontControllerTest.java new file mode 100644 index 000000000..9bc4253c0 --- /dev/null +++ b/front-controller/src/test/java/com/iluwatar/front/controller/FrontControllerTest.java @@ -0,0 +1,61 @@ +package com.iluwatar.front.controller; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/13/15 - 1:39 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class FrontControllerTest extends StdOutTest { + + @Parameters + public static List data() { + final List parameters = new ArrayList<>(); + parameters.add(new Object[]{new ArcherCommand(), "Displaying archers"}); + parameters.add(new Object[]{new CatapultCommand(), "Displaying catapults"}); + parameters.add(new Object[]{new UnknownCommand(), "Error 500"}); + return parameters; + } + + /** + * The view that's been tested + */ + private final Command command; + + /** + * The expected display message + */ + private final String displayMessage; + + /** + * Create a new instance of the {@link FrontControllerTest} with the given view and expected message + * + * @param command The command that's been tested + * @param displayMessage The expected display message + */ + public FrontControllerTest(final Command command, final String displayMessage) { + this.displayMessage = displayMessage; + this.command = command; + } + + @Test + public void testDisplay() { + verifyZeroInteractions(getStdOutMock()); + this.command.process(); + verify(getStdOutMock()).println(displayMessage); + verifyNoMoreInteractions(getStdOutMock()); + } + +} diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/StdOutTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/StdOutTest.java new file mode 100644 index 000000000..31d061b08 --- /dev/null +++ b/front-controller/src/test/java/com/iluwatar/front/controller/StdOutTest.java @@ -0,0 +1,54 @@ +package com.iluwatar.front.controller; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since the actions of the views don't have + * any influence on any other accessible objects, except for writing to std-out using {@link + * System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/ViewTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/ViewTest.java new file mode 100644 index 000000000..fb2df1c60 --- /dev/null +++ b/front-controller/src/test/java/com/iluwatar/front/controller/ViewTest.java @@ -0,0 +1,61 @@ +package com.iluwatar.front.controller; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/13/15 - 1:39 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class ViewTest extends StdOutTest { + + @Parameters + public static List data() { + final List parameters = new ArrayList<>(); + parameters.add(new Object[]{new ArcherView(), "Displaying archers"}); + parameters.add(new Object[]{new CatapultView(), "Displaying catapults"}); + parameters.add(new Object[]{new ErrorView(), "Error 500"}); + return parameters; + } + + /** + * The view that's been tested + */ + private final View view; + + /** + * The expected display message + */ + private final String displayMessage; + + /** + * Create a new instance of the {@link ViewTest} with the given view and expected message + * + * @param view The view that's been tested + * @param displayMessage The expected display message + */ + public ViewTest(final View view, final String displayMessage) { + this.displayMessage = displayMessage; + this.view = view; + } + + @Test + public void testDisplay() { + verifyZeroInteractions(getStdOutMock()); + this.view.display(); + verify(getStdOutMock()).println(displayMessage); + verifyNoMoreInteractions(getStdOutMock()); + } + +} \ No newline at end of file diff --git a/half-sync-half-async/index.md b/half-sync-half-async/index.md index dc1930e3b..5eb93c058 100644 --- a/half-sync-half-async/index.md +++ b/half-sync-half-async/index.md @@ -4,7 +4,9 @@ title: Half-Sync/Half-Async folder: half-sync-half-async permalink: /patterns/half-sync-half-async/ categories: Concurrency -tags: Java +tags: + - Java + - Difficulty-Intermediate --- **Intent:** The Half-Sync/Half-Async pattern decouples synchronous I/O from diff --git a/half-sync-half-async/pom.xml b/half-sync-half-async/pom.xml index e7a27c321..7ea1a203b 100644 --- a/half-sync-half-async/pom.xml +++ b/half-sync-half-async/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT half-sync-half-async @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java index 80f2eefb2..b67b602ca 100644 --- a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java +++ b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java @@ -21,17 +21,15 @@ import java.util.concurrent.LinkedBlockingQueue; * *

* APPLICABILITY
- *

    - *
  • UNIX network subsystems - In operating systems network operations are carried out - * asynchronously with help of hardware level interrupts.
  • - *
  • CORBA - At the asynchronous layer one thread is associated with each socket that is connected + * UNIX network subsystems - In operating systems network operations are carried out + * asynchronously with help of hardware level interrupts.
    + * CORBA - At the asynchronous layer one thread is associated with each socket that is connected * to the client. Thread blocks waiting for CORBA requests from the client. On receiving request it * is inserted in the queuing layer which is then picked up by synchronous layer which processes the - * request and sends response back to the client.
  • - *
  • Android AsyncTask framework - Framework provides a way to execute long running blocking + * request and sends response back to the client.
    + * Android AsyncTask framework - Framework provides a way to execute long running blocking * calls, such as downloading a file, in background threads so that the UI thread remains free to - * respond to user inputs. - *
+ * respond to user inputs.
* *

* IMPLEMENTATION
@@ -121,6 +119,7 @@ public class App { try { Thread.sleep(i); } catch (InterruptedException e) { + System.out.println(e); } return (i) * (i + 1) / 2; } diff --git a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java index 457dffa20..3be340c02 100644 --- a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java +++ b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java @@ -50,6 +50,7 @@ public class AsynchronousService { task.onPreCall(); } catch (Exception e) { task.onError(e); + return; } service.submit(new FutureTask(task) { diff --git a/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java new file mode 100644 index 000000000..16b51d0b5 --- /dev/null +++ b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java @@ -0,0 +1,72 @@ +package com.iluwatar.halfsynchalfasync; + +import org.junit.Test; +import org.mockito.InOrder; + +import java.io.IOException; +import java.util.concurrent.LinkedBlockingQueue; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; + +/** + * Date: 12/12/15 - 11:15 PM + * + * @author Jeroen Meulemeester + */ +public class AsynchronousServiceTest { + + @Test + public void testPerfectExecution() throws Exception { + final AsynchronousService service = new AsynchronousService(new LinkedBlockingQueue<>()); + final AsyncTask task = mock(AsyncTask.class); + final Object result = new Object(); + when(task.call()).thenReturn(result); + service.execute(task); + + verify(task, timeout(2000)).onPostCall(eq(result)); + + final InOrder inOrder = inOrder(task); + inOrder.verify(task, times(1)).onPreCall(); + inOrder.verify(task, times(1)).call(); + inOrder.verify(task, times(1)).onPostCall(eq(result)); + + verifyNoMoreInteractions(task); + } + + @Test + public void testCallException() throws Exception { + final AsynchronousService service = new AsynchronousService(new LinkedBlockingQueue<>()); + final AsyncTask task = mock(AsyncTask.class); + final IOException exception = new IOException(); + when(task.call()).thenThrow(exception); + service.execute(task); + + verify(task, timeout(2000)).onError(eq(exception)); + + final InOrder inOrder = inOrder(task); + inOrder.verify(task, times(1)).onPreCall(); + inOrder.verify(task, times(1)).call(); + inOrder.verify(task, times(1)).onError(exception); + + verifyNoMoreInteractions(task); + } + + @Test + public void testPreCallException() throws Exception { + final AsynchronousService service = new AsynchronousService(new LinkedBlockingQueue<>()); + final AsyncTask task = mock(AsyncTask.class); + final IllegalStateException exception = new IllegalStateException(); + doThrow(exception).when(task).onPreCall(); + service.execute(task); + + verify(task, timeout(2000)).onError(eq(exception)); + + final InOrder inOrder = inOrder(task); + inOrder.verify(task, times(1)).onPreCall(); + inOrder.verify(task, times(1)).onError(exception); + + verifyNoMoreInteractions(task); + } + +} \ No newline at end of file diff --git a/intercepting-filter/index.md b/intercepting-filter/index.md index 41825745b..3aa70ba02 100644 --- a/intercepting-filter/index.md +++ b/intercepting-filter/index.md @@ -4,7 +4,9 @@ title: Intercepting Filter folder: intercepting-filter permalink: /patterns/intercepting-filter/ categories: Behavioral -tags: Java +tags: + - Java + - Difficulty-Intermediate --- **Intent:** Provide pluggable filters to conduct necessary pre-processing and diff --git a/intercepting-filter/pom.xml b/intercepting-filter/pom.xml index 1f1d89e96..d18126ba9 100644 --- a/intercepting-filter/pom.xml +++ b/intercepting-filter/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT intercepting-filter @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java index c1aa0780b..38a762483 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java @@ -14,7 +14,8 @@ public class AddressFilter extends AbstractFilter { String result = super.execute(order); if (order.getAddress() == null || order.getAddress().isEmpty()) { return result + "Invalid address! "; - } else + } else { return result; + } } } diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java index 817ae7587..f0a2267d2 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java @@ -34,7 +34,7 @@ public class App { * @param args command line args */ public static void main(String[] args) { - FilterManager filterManager = new FilterManager(new Target()); + FilterManager filterManager = new FilterManager(); filterManager.addFilter(new NameFilter()); filterManager.addFilter(new ContactFilter()); filterManager.addFilter(new AddressFilter()); diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java index 02499ed0a..5934da75c 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java @@ -32,8 +32,12 @@ public class Client extends JFrame { private JLabel jl; private JTextField[] jtFields; private JTextArea[] jtAreas; - private JButton clearButton, processButton; + private JButton clearButton; + private JButton processButton; + /** + * Constructor + */ public Client() { super("Client System"); setDefaultCloseOperation(EXIT_ON_CLOSE); diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java index 9496bde36..a1ea5b4ee 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java @@ -11,30 +11,21 @@ public interface Filter { /** * Execute order processing filter. - * - * @param order - * @return empty string on success, otherwise error message. */ String execute(Order order); /** * Set next filter in chain after this. - * - * @param filter */ void setNext(Filter filter); /** * Get next filter in chain after this. - * - * @return */ Filter getNext(); /** * Get last filter in the chain. - * - * @return */ Filter getLast(); } diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java index 987678cc7..e8f3ca70f 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java @@ -10,12 +10,15 @@ public class FilterChain { private Filter chain; - private final Target target; - - public FilterChain(Target target) { - this.target = target; + /** + * Constructor + */ + public FilterChain() { } + /** + * Adds filter + */ public void addFilter(Filter filter) { if (chain == null) { chain = filter; @@ -24,6 +27,9 @@ public class FilterChain { } } + /** + * Execute filter chain + */ public String execute(Order order) { if (chain != null) { return chain.execute(order); diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java index 7cdaab103..d6e01598e 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java @@ -10,8 +10,8 @@ public class FilterManager { private FilterChain filterChain; - public FilterManager(Target target) { - filterChain = new FilterChain(target); + public FilterManager() { + filterChain = new FilterChain(); } public void addFilter(Filter filter) { diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java index a458e475b..2f431caad 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java @@ -14,7 +14,7 @@ public class NameFilter extends AbstractFilter { String result = super.execute(order); if (order.getName() == null || order.getName().isEmpty() || order.getName().matches(".*[^\\w|\\s]+.*")) { - return result + "Invalid order! "; + return result + "Invalid name! "; } else { return result; } diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java index 5b30fee35..53d1a3dd9 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java @@ -14,6 +14,9 @@ public class Order { public Order() {} + /** + * Constructor + */ public Order(String name, String contactNumber, String address, String depositNumber, String order) { this.name = name; this.contactNumber = contactNumber; diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java index cb96cd6e0..ffb13c160 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java @@ -29,6 +29,9 @@ public class Target extends JFrame { private DefaultTableModel dtm; private JButton del; + /** + * Constructor + */ public Target() { super("Order System"); setDefaultCloseOperation(EXIT_ON_CLOSE); @@ -67,8 +70,9 @@ public class Target extends JFrame { @Override public void actionPerformed(ActionEvent e) { int temp = jt.getSelectedRow(); - if (temp == -1) + if (temp == -1) { return; + } int temp2 = jt.getSelectedRowCount(); for (int i = 0; i < temp2; i++) { dtm.removeRow(temp); diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java index bcdf7c09b..9abdcc181 100644 --- a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.intercepting.filter; import org.junit.Test; -import com.iluwatar.intercepting.filter.App; - /** * * Application test. diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterManagerTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterManagerTest.java new file mode 100644 index 000000000..022bd7586 --- /dev/null +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterManagerTest.java @@ -0,0 +1,46 @@ +package com.iluwatar.intercepting.filter; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +/** + * Date: 12/13/15 - 3:01 PM + * + * @author Jeroen Meulemeester + */ +public class FilterManagerTest { + + @Test + public void testFilterRequest() throws Exception { + final Target target = mock(Target.class); + final FilterManager filterManager = new FilterManager(); + assertEquals("RUNNING...", filterManager.filterRequest(mock(Order.class))); + verifyZeroInteractions(target); + } + + @Test + public void testAddFilter() throws Exception { + final Target target = mock(Target.class); + final FilterManager filterManager = new FilterManager(); + + verifyZeroInteractions(target); + + final Filter filter = mock(Filter.class); + when(filter.execute(any(Order.class))).thenReturn("filter"); + + filterManager.addFilter(filter); + + final Order order = mock(Order.class); + assertEquals("filter", filterManager.filterRequest(order)); + + verify(filter, times(1)).execute(any(Order.class)); + verifyZeroInteractions(target, filter, order); + } +} \ No newline at end of file diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterTest.java new file mode 100644 index 000000000..71d9bf250 --- /dev/null +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterTest.java @@ -0,0 +1,98 @@ +package com.iluwatar.intercepting.filter; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.ArrayList; +import java.util.List; + +import static junit.framework.TestCase.assertSame; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * Date: 12/13/15 - 2:17 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class FilterTest { + + private static final Order PERFECT_ORDER = new Order("name", "12345678901", "addr", "dep", "order"); + private static final Order WRONG_ORDER = new Order("name", "12345678901", "addr", "dep", ""); + private static final Order WRONG_DEPOSIT = new Order("name", "12345678901", "addr", "", "order"); + private static final Order WRONG_ADDRESS = new Order("name", "12345678901", "", "dep", "order"); + private static final Order WRONG_CONTACT = new Order("name", "", "addr", "dep", "order"); + private static final Order WRONG_NAME = new Order("", "12345678901", "addr", "dep", "order"); + + @Parameters + public static List getTestData() { + final List testData = new ArrayList<>(); + testData.add(new Object[]{new NameFilter(), PERFECT_ORDER, ""}); + testData.add(new Object[]{new NameFilter(), WRONG_NAME, "Invalid name!"}); + testData.add(new Object[]{new NameFilter(), WRONG_CONTACT, ""}); + testData.add(new Object[]{new NameFilter(), WRONG_ADDRESS, ""}); + testData.add(new Object[]{new NameFilter(), WRONG_DEPOSIT, ""}); + testData.add(new Object[]{new NameFilter(), WRONG_ORDER, ""}); + + testData.add(new Object[]{new ContactFilter(), PERFECT_ORDER, ""}); + testData.add(new Object[]{new ContactFilter(), WRONG_NAME, ""}); + testData.add(new Object[]{new ContactFilter(), WRONG_CONTACT, "Invalid contact number!"}); + testData.add(new Object[]{new ContactFilter(), WRONG_ADDRESS, ""}); + testData.add(new Object[]{new ContactFilter(), WRONG_DEPOSIT, ""}); + testData.add(new Object[]{new ContactFilter(), WRONG_ORDER, ""}); + + testData.add(new Object[]{new AddressFilter(), PERFECT_ORDER, ""}); + testData.add(new Object[]{new AddressFilter(), WRONG_NAME, ""}); + testData.add(new Object[]{new AddressFilter(), WRONG_CONTACT, ""}); + testData.add(new Object[]{new AddressFilter(), WRONG_ADDRESS, "Invalid address!"}); + testData.add(new Object[]{new AddressFilter(), WRONG_DEPOSIT, ""}); + testData.add(new Object[]{new AddressFilter(), WRONG_ORDER, ""}); + + testData.add(new Object[]{new DepositFilter(), PERFECT_ORDER, ""}); + testData.add(new Object[]{new DepositFilter(), WRONG_NAME, ""}); + testData.add(new Object[]{new DepositFilter(), WRONG_CONTACT, ""}); + testData.add(new Object[]{new DepositFilter(), WRONG_ADDRESS, ""}); + testData.add(new Object[]{new DepositFilter(), WRONG_DEPOSIT, "Invalid deposit number!"}); + testData.add(new Object[]{new DepositFilter(), WRONG_ORDER, ""}); + + testData.add(new Object[]{new OrderFilter(), PERFECT_ORDER, ""}); + testData.add(new Object[]{new OrderFilter(), WRONG_NAME, ""}); + testData.add(new Object[]{new OrderFilter(), WRONG_CONTACT, ""}); + testData.add(new Object[]{new OrderFilter(), WRONG_ADDRESS, ""}); + testData.add(new Object[]{new OrderFilter(), WRONG_DEPOSIT, ""}); + testData.add(new Object[]{new OrderFilter(), WRONG_ORDER, "Invalid order!"}); + + return testData; + } + + private final Filter filter; + private final Order order; + private final String result; + + /** + * Constructor + */ + public FilterTest(Filter filter, Order order, String result) { + this.filter = filter; + this.order = order; + this.result = result; + } + + @Test + public void testExecute() throws Exception { + final String result = this.filter.execute(this.order); + assertNotNull(result); + assertEquals(this.result, result.trim()); + } + + @Test + public void testNext() throws Exception { + assertNull(this.filter.getNext()); + assertSame(this.filter, this.filter.getLast()); + } + +} diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/OrderTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/OrderTest.java new file mode 100644 index 000000000..70862ed0f --- /dev/null +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/OrderTest.java @@ -0,0 +1,51 @@ +package com.iluwatar.intercepting.filter; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Date: 12/13/15 - 2:57 PM + * + * @author Jeroen Meulemeester + */ +public class OrderTest { + + private static final String EXPECTED_VALUE = "test"; + + @Test + public void testSetName() throws Exception { + final Order order = new Order(); + order.setName(EXPECTED_VALUE); + assertEquals(EXPECTED_VALUE, order.getName()); + } + + @Test + public void testSetContactNumber() throws Exception { + final Order order = new Order(); + order.setContactNumber(EXPECTED_VALUE); + assertEquals(EXPECTED_VALUE, order.getContactNumber()); + } + + @Test + public void testSetAddress() throws Exception { + final Order order = new Order(); + order.setAddress(EXPECTED_VALUE); + assertEquals(EXPECTED_VALUE, order.getAddress()); + } + + @Test + public void testSetDepositNumber() throws Exception { + final Order order = new Order(); + order.setDepositNumber(EXPECTED_VALUE); + assertEquals(EXPECTED_VALUE, order.getDepositNumber()); + } + + @Test + public void testSetOrder() throws Exception { + final Order order = new Order(); + order.setOrder(EXPECTED_VALUE); + assertEquals(EXPECTED_VALUE, order.getOrder()); + } + +} diff --git a/interpreter/index.md b/interpreter/index.md index 57b117e06..dd6a7eda2 100644 --- a/interpreter/index.md +++ b/interpreter/index.md @@ -7,6 +7,7 @@ categories: Behavioral tags: - Java - Gang Of Four + - Difficulty-Intermediate --- **Intent:** Given a language, define a representation for its grammar along diff --git a/interpreter/pom.xml b/interpreter/pom.xml index 863a00199..72fd0d51b 100644 --- a/interpreter/pom.xml +++ b/interpreter/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT interpreter diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/App.java b/interpreter/src/main/java/com/iluwatar/interpreter/App.java index 2f88951f1..e4e238c15 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/App.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/App.java @@ -55,6 +55,9 @@ public class App { return s.equals("+") || s.equals("-") || s.equals("*"); } + /** + * Get expression for string + */ public static Expression getOperatorInstance(String s, Expression left, Expression right) { switch (s) { case "+": @@ -63,7 +66,8 @@ public class App { return new MinusExpression(left, right); case "*": return new MultiplyExpression(left, right); + default: + return new MultiplyExpression(left, right); } - return null; } } diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java index cb7e957c9..be696f072 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.interpreter; import org.junit.Test; -import com.iluwatar.interpreter.App; - /** * * Application test diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/ExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/ExpressionTest.java new file mode 100644 index 000000000..150596cd8 --- /dev/null +++ b/interpreter/src/test/java/com/iluwatar/interpreter/ExpressionTest.java @@ -0,0 +1,111 @@ +package com.iluwatar.interpreter; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/14/15 - 11:48 AM + * + * @author Jeroen Meulemeester + */ +public abstract class ExpressionTest { + + /** + * Generate inputs ranging from -10 to 10 for both input params and calculate the expected result + * + * @param resultCalc The function used to calculate the expected result + * @return A data set with test entries + */ + static List prepareParameters(final BiFunction resultCalc) { + final List testData = new ArrayList<>(); + for (int i = -10; i < 10; i++) { + for (int j = -10; j < 10; j++) { + testData.add(new Object[]{ + new NumberExpression(i), + new NumberExpression(j), + resultCalc.apply(i, j) + }); + } + } + return testData; + } + + /** + * The input used as first parameter during the test + */ + private final NumberExpression first; + + /** + * The input used as second parameter during the test + */ + private final NumberExpression second; + + /** + * The expected result of the calculation, taking the first and second parameter in account + */ + private final int result; + + /** + * The expected {@link E#toString()} response + */ + private final String expectedToString; + + /** + * Factory, used to create a new test object instance with the correct first and second parameter + */ + private final BiFunction factory; + + /** + * Create a new test instance with the given parameters and expected results + * + * @param first The input used as first parameter during the test + * @param second The input used as second parameter during the test + * @param result The expected result of the tested expression + * @param expectedToString The expected {@link E#toString()} response + * @param factory Factory, used to create a new test object instance + */ + ExpressionTest(final NumberExpression first, final NumberExpression second, final int result, + final String expectedToString, final BiFunction factory) { + + this.first = first; + this.second = second; + this.result = result; + this.expectedToString = expectedToString; + this.factory = factory; + } + + /** + * Get the first parameter + * + * @return The first parameter + */ + final NumberExpression getFirst() { + return this.first; + } + + /** + * Verify if the expression calculates the correct result when calling {@link E#interpret()} + */ + @Test + public void testInterpret() { + final E expression = this.factory.apply(this.first, this.second); + assertNotNull(expression); + assertEquals(this.result, expression.interpret()); + } + + /** + * Verify if the expression has the expected {@link E#toString()} value + */ + @Test + public void testToString() { + final E expression = this.factory.apply(this.first, this.second); + assertNotNull(expression); + assertEquals(expectedToString, expression.toString()); + } +} diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/MinusExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/MinusExpressionTest.java new file mode 100644 index 000000000..3b6d322fe --- /dev/null +++ b/interpreter/src/test/java/com/iluwatar/interpreter/MinusExpressionTest.java @@ -0,0 +1,38 @@ +package com.iluwatar.interpreter; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.List; + +/** + * Date: 12/14/15 - 12:08 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class MinusExpressionTest extends ExpressionTest { + + /** + * Create a new set of test entries with the expected result + * + * @return The list of parameters used during this test + */ + @Parameters + public static List data() { + return prepareParameters((f, s) -> f - s); + } + + /** + * Create a new test instance using the given test parameters and expected result + * + * @param first The first expression parameter + * @param second The second expression parameter + * @param result The expected result + */ + public MinusExpressionTest(final NumberExpression first, final NumberExpression second, final int result) { + super(first, second, result, "-", MinusExpression::new); + } + +} \ No newline at end of file diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/MultiplyExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/MultiplyExpressionTest.java new file mode 100644 index 000000000..91ecdb008 --- /dev/null +++ b/interpreter/src/test/java/com/iluwatar/interpreter/MultiplyExpressionTest.java @@ -0,0 +1,38 @@ +package com.iluwatar.interpreter; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.List; + +/** + * Date: 12/14/15 - 12:08 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class MultiplyExpressionTest extends ExpressionTest { + + /** + * Create a new set of test entries with the expected result + * + * @return The list of parameters used during this test + */ + @Parameters + public static List data() { + return prepareParameters((f, s) -> f * s); + } + + /** + * Create a new test instance using the given test parameters and expected result + * + * @param first The first expression parameter + * @param second The second expression parameter + * @param result The expected result + */ + public MultiplyExpressionTest(final NumberExpression first, final NumberExpression second, final int result) { + super(first, second, result, "*", MultiplyExpression::new); + } + +} \ No newline at end of file diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/NumberExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/NumberExpressionTest.java new file mode 100644 index 000000000..2c4a35be7 --- /dev/null +++ b/interpreter/src/test/java/com/iluwatar/interpreter/NumberExpressionTest.java @@ -0,0 +1,52 @@ +package com.iluwatar.interpreter; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Date: 12/14/15 - 12:08 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class NumberExpressionTest extends ExpressionTest { + + /** + * Create a new set of test entries with the expected result + * + * @return The list of parameters used during this test + */ + @Parameters + public static List data() { + return prepareParameters((f, s) -> f); + } + + /** + * Create a new test instance using the given test parameters and expected result + * + * @param first The first expression parameter + * @param second The second expression parameter + * @param result The expected result + */ + public NumberExpressionTest(final NumberExpression first, final NumberExpression second, final int result) { + super(first, second, result, "number", (f, s) -> f); + } + + /** + * Verify if the {@link NumberExpression#NumberExpression(String)} constructor works as expected + */ + @Test + public void testFromString() throws Exception { + final int expectedValue = getFirst().interpret(); + final String testStingValue = String.valueOf(expectedValue); + final NumberExpression numberExpression = new NumberExpression(testStingValue); + assertEquals(expectedValue, numberExpression.interpret()); + } + +} \ No newline at end of file diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/PlusExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/PlusExpressionTest.java new file mode 100644 index 000000000..065213631 --- /dev/null +++ b/interpreter/src/test/java/com/iluwatar/interpreter/PlusExpressionTest.java @@ -0,0 +1,38 @@ +package com.iluwatar.interpreter; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.List; + +/** + * Date: 12/14/15 - 12:08 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class PlusExpressionTest extends ExpressionTest { + + /** + * Create a new set of test entries with the expected result + * + * @return The list of parameters used during this test + */ + @Parameters + public static List data() { + return prepareParameters((f, s) -> f + s); + } + + /** + * Create a new test instance using the given test parameters and expected result + * + * @param first The first expression parameter + * @param second The second expression parameter + * @param result The expected result + */ + public PlusExpressionTest(final NumberExpression first, final NumberExpression second, final int result) { + super(first, second, result, "+", PlusExpression::new); + } + +} \ No newline at end of file diff --git a/iterator/pom.xml b/iterator/pom.xml index 258c1e3ee..9f57dedb6 100644 --- a/iterator/pom.xml +++ b/iterator/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT iterator diff --git a/iterator/src/main/java/com/iluwatar/iterator/App.java b/iterator/src/main/java/com/iluwatar/iterator/App.java index c9c5fa521..467040ca6 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/App.java +++ b/iterator/src/main/java/com/iluwatar/iterator/App.java @@ -20,28 +20,28 @@ public class App { public static void main(String[] args) { TreasureChest chest = new TreasureChest(); - ItemIterator ringIterator = chest.Iterator(ItemType.RING); + ItemIterator ringIterator = chest.iterator(ItemType.RING); while (ringIterator.hasNext()) { System.out.println(ringIterator.next()); } System.out.println("----------"); - ItemIterator potionIterator = chest.Iterator(ItemType.POTION); + ItemIterator potionIterator = chest.iterator(ItemType.POTION); while (potionIterator.hasNext()) { System.out.println(potionIterator.next()); } System.out.println("----------"); - ItemIterator weaponIterator = chest.Iterator(ItemType.WEAPON); + ItemIterator weaponIterator = chest.iterator(ItemType.WEAPON); while (weaponIterator.hasNext()) { System.out.println(weaponIterator.next()); } System.out.println("----------"); - ItemIterator it = chest.Iterator(ItemType.ANY); + ItemIterator it = chest.iterator(ItemType.ANY); while (it.hasNext()) { System.out.println(it.next()); } diff --git a/iterator/src/main/java/com/iluwatar/iterator/TreasureChest.java b/iterator/src/main/java/com/iluwatar/iterator/TreasureChest.java index 02496e33c..6b5c54a5a 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/TreasureChest.java +++ b/iterator/src/main/java/com/iluwatar/iterator/TreasureChest.java @@ -12,6 +12,9 @@ public class TreasureChest { private List items; + /** + * Constructor + */ public TreasureChest() { items = new ArrayList<>(); items.add(new Item(ItemType.POTION, "Potion of courage")); @@ -26,10 +29,13 @@ public class TreasureChest { items.add(new Item(ItemType.WEAPON, "Dagger of poison")); } - ItemIterator Iterator(ItemType type) { - return new TreasureChestItemIterator(this, type); + ItemIterator iterator(ItemType itemType) { + return new TreasureChestItemIterator(this, itemType); } + /** + * Get all items + */ public List getItems() { ArrayList list = new ArrayList<>(); list.addAll(items); diff --git a/iterator/src/main/java/com/iluwatar/iterator/TreasureChestItemIterator.java b/iterator/src/main/java/com/iluwatar/iterator/TreasureChestItemIterator.java index 39c12ab44..a8303f308 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/TreasureChestItemIterator.java +++ b/iterator/src/main/java/com/iluwatar/iterator/TreasureChestItemIterator.java @@ -13,6 +13,9 @@ public class TreasureChestItemIterator implements ItemIterator { private int idx; private ItemType type; + /** + * Constructor + */ public TreasureChestItemIterator(TreasureChest chest, ItemType type) { this.chest = chest; this.type = type; diff --git a/iterator/src/test/java/com/iluwatar/iterator/AppTest.java b/iterator/src/test/java/com/iluwatar/iterator/AppTest.java index b6198f5c5..5ec59ec74 100644 --- a/iterator/src/test/java/com/iluwatar/iterator/AppTest.java +++ b/iterator/src/test/java/com/iluwatar/iterator/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.iterator; import org.junit.Test; -import com.iluwatar.iterator.App; - /** * * Application test diff --git a/iterator/src/test/java/com/iluwatar/iterator/TreasureChestTest.java b/iterator/src/test/java/com/iluwatar/iterator/TreasureChestTest.java new file mode 100644 index 000000000..a2102a2e2 --- /dev/null +++ b/iterator/src/test/java/com/iluwatar/iterator/TreasureChestTest.java @@ -0,0 +1,108 @@ +package com.iluwatar.iterator; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +/** + * Date: 12/14/15 - 2:58 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class TreasureChestTest { + + /** + * Create a list of all expected items in the chest. + * + * @return The set of all expected items in the chest + */ + @Parameterized.Parameters + public static List data() { + final List parameters = new ArrayList<>(); + parameters.add(new Object[]{new Item(ItemType.POTION, "Potion of courage")}); + parameters.add(new Object[]{new Item(ItemType.RING, "Ring of shadows")}); + parameters.add(new Object[]{new Item(ItemType.POTION, "Potion of wisdom")}); + parameters.add(new Object[]{new Item(ItemType.POTION, "Potion of blood")}); + parameters.add(new Object[]{new Item(ItemType.WEAPON, "Sword of silver +1")}); + parameters.add(new Object[]{new Item(ItemType.POTION, "Potion of rust")}); + parameters.add(new Object[]{new Item(ItemType.POTION, "Potion of healing")}); + parameters.add(new Object[]{new Item(ItemType.RING, "Ring of armor")}); + parameters.add(new Object[]{new Item(ItemType.WEAPON, "Steel halberd")}); + parameters.add(new Object[]{new Item(ItemType.WEAPON, "Dagger of poison")}); + return parameters; + } + + /** + * One of the expected items in the chest + */ + private final Item expectedItem; + + /** + * Create a new test instance, test if the given expected item can be retrieved from the chest + * + * @param expectedItem One of the items that should be in the chest + */ + public TreasureChestTest(final Item expectedItem) { + this.expectedItem = expectedItem; + } + + /** + * Test if the expected item can be retrieved from the chest using the {@link ItemIterator} + */ + @Test + public void testIterator() { + final TreasureChest chest = new TreasureChest(); + final ItemIterator iterator = chest.iterator(expectedItem.getType()); + assertNotNull(iterator); + + while (iterator.hasNext()) { + final Item item = iterator.next(); + assertNotNull(item); + assertEquals(this.expectedItem.getType(), item.getType()); + + final String name = item.toString(); + assertNotNull(name); + if (this.expectedItem.toString().equals(name)) { + return; + } + } + + fail("Expected to find item [" + this.expectedItem + "] using iterator, but we didn't."); + + } + + /** + * Test if the expected item can be retrieved from the chest using the {@link + * TreasureChest#getItems()} method + */ + @Test + public void testGetItems() throws Exception { + final TreasureChest chest = new TreasureChest(); + final List items = chest.getItems(); + assertNotNull(items); + + for (final Item item : items) { + assertNotNull(item); + assertNotNull(item.getType()); + assertNotNull(item.toString()); + + final boolean sameType = this.expectedItem.getType() == item.getType(); + final boolean sameName = this.expectedItem.toString().equals(item.toString()); + if (sameType && sameName) { + return; + } + } + + fail("Expected to find item [" + this.expectedItem + "] in the item list, but we didn't."); + + } + +} \ No newline at end of file diff --git a/layers/index.md b/layers/index.md index 37089a19c..5f746d4d2 100644 --- a/layers/index.md +++ b/layers/index.md @@ -4,7 +4,10 @@ title: Layers folder: layers permalink: /patterns/layers/ categories: Architectural -tags: Java +tags: + - Java + - Difficulty-Intermediate + - Spring --- **Intent:** Layers is an architectural style where software responsibilities are diff --git a/layers/pom.xml b/layers/pom.xml index e338a557f..685f8b65c 100644 --- a/layers/pom.xml +++ b/layers/pom.xml @@ -6,7 +6,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT com.iluwatar.layers layers @@ -32,5 +32,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/layers/src/main/java/com/iluwatar/layers/App.java b/layers/src/main/java/com/iluwatar/layers/App.java index d175553f7..ecb532510 100644 --- a/layers/src/main/java/com/iluwatar/layers/App.java +++ b/layers/src/main/java/com/iluwatar/layers/App.java @@ -4,31 +4,31 @@ import java.util.Arrays; /** * - * Layers is an architectural style where software responsibilities are divided among the different - * layers of the application. + * Layers is an architectural style where software responsibilities are divided among the different layers of the + * application. *

- * This example demonstrates a traditional 3-layer architecture consisting of data access layer, - * business layer and presentation layer. + * This example demonstrates a traditional 3-layer architecture consisting of data access layer, business layer and + * presentation layer. *

- * The data access layer is formed of Spring Data repositories CakeDao, - * CakeToppingDao and CakeLayerDao. The repositories can be used for CRUD - * operations on cakes, cake toppings and cake layers respectively. + * The data access layer is formed of Spring Data repositories CakeDao, CakeToppingDao and + * CakeLayerDao. The repositories can be used for CRUD operations on cakes, cake toppings and cake layers + * respectively. *

- * The business layer is built on top of the data access layer. CakeBakingService - * offers methods to retrieve available cake toppings and cake layers and baked cakes. Also the - * service is used to create new cakes out of cake toppings and cake layers. + * The business layer is built on top of the data access layer. CakeBakingService offers methods to + * retrieve available cake toppings and cake layers and baked cakes. Also the service is used to create new cakes out of + * cake toppings and cake layers. *

- * The presentation layer is built on the business layer and in this example it simply lists the - * cakes that have been baked. + * The presentation layer is built on the business layer and in this example it simply lists the cakes that have been + * baked. *

- * We have applied so called strict layering which means that the layers can only access the classes - * directly beneath them. This leads the solution to create an additional set of DTOs ( - * CakeInfo, CakeToppingInfo, CakeLayerInfo) to translate - * data between layers. In other words, CakeBakingService cannot return entities ( - * Cake, CakeTopping, CakeLayer) directly since these reside - * on data access layer but instead translates these into business layer DTOs (CakeInfo, CakeToppingInfo, CakeLayerInfo) and returns them instead. This way - * the presentation layer does not have any knowledge of other layers than the business layer and - * thus is not affected by changes to them. + * We have applied so called strict layering which means that the layers can only access the classes directly beneath + * them. This leads the solution to create an additional set of DTOs ( CakeInfo, + * CakeToppingInfo, CakeLayerInfo) to translate data between layers. In other words, + * CakeBakingService cannot return entities ( Cake, CakeTopping, + * CakeLayer) directly since these reside on data access layer but instead translates these into business + * layer DTOs (CakeInfo, CakeToppingInfo, CakeLayerInfo) and returns them + * instead. This way the presentation layer does not have any knowledge of other layers than the business layer and thus + * is not affected by changes to them. * * @see Cake * @see CakeTopping @@ -63,8 +63,6 @@ public class App { /** * Initializes the example data - * - * @param cakeBakingService */ private static void initializeData(CakeBakingService cakeBakingService) { cakeBakingService.saveNewLayer(new CakeLayerInfo("chocolate", 1200)); diff --git a/layers/src/main/java/com/iluwatar/layers/CakeBakingService.java b/layers/src/main/java/com/iluwatar/layers/CakeBakingService.java index 80bd3438b..adfa585d6 100644 --- a/layers/src/main/java/com/iluwatar/layers/CakeBakingService.java +++ b/layers/src/main/java/com/iluwatar/layers/CakeBakingService.java @@ -11,44 +11,31 @@ public interface CakeBakingService { /** * Bakes new cake according to parameters - * - * @param cakeInfo - * @throws CakeBakingException */ void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException; /** * Get all cakes - * - * @return */ List getAllCakes(); /** * Store new cake topping - * - * @param toppingInfo */ void saveNewTopping(CakeToppingInfo toppingInfo); /** * Get available cake toppings - * - * @return */ List getAvailableToppings(); /** * Add new cake layer - * - * @param layerInfo */ void saveNewLayer(CakeLayerInfo layerInfo); /** * Get available cake layers - * - * @return */ List getAvailableLayers(); } diff --git a/layers/src/main/java/com/iluwatar/layers/CakeBakingServiceImpl.java b/layers/src/main/java/com/iluwatar/layers/CakeBakingServiceImpl.java index a519ec2ce..79917842d 100644 --- a/layers/src/main/java/com/iluwatar/layers/CakeBakingServiceImpl.java +++ b/layers/src/main/java/com/iluwatar/layers/CakeBakingServiceImpl.java @@ -30,9 +30,9 @@ public class CakeBakingServiceImpl implements CakeBakingService { @Override public void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException { - List allToppings = getAvailableToppings(); - List matchingToppings = - allToppings.stream().filter((t) -> t.name.equals(cakeInfo.cakeToppingInfo.name)) + List allToppings = getAvailableToppingEntities(); + List matchingToppings = + allToppings.stream().filter((t) -> t.getName().equals(cakeInfo.cakeToppingInfo.name)) .collect(Collectors.toList()); if (matchingToppings.isEmpty()) { throw new CakeBakingException(String.format("Topping %s is not available", @@ -50,7 +50,7 @@ public class CakeBakingServiceImpl implements CakeBakingService { } } CakeToppingDao toppingBean = context.getBean(CakeToppingDao.class); - CakeTopping topping = toppingBean.findOne(matchingToppings.iterator().next().id.get()); + CakeTopping topping = toppingBean.findOne(matchingToppings.iterator().next().getId()); CakeDao cakeBean = context.getBean(CakeDao.class); Cake cake = new Cake(); cake.setTopping(topping); diff --git a/layers/src/main/java/com/iluwatar/layers/CakeInfo.java b/layers/src/main/java/com/iluwatar/layers/CakeInfo.java index f60ee9a14..dc374bf60 100644 --- a/layers/src/main/java/com/iluwatar/layers/CakeInfo.java +++ b/layers/src/main/java/com/iluwatar/layers/CakeInfo.java @@ -14,18 +14,27 @@ public class CakeInfo { public final CakeToppingInfo cakeToppingInfo; public final List cakeLayerInfos; + /** + * Constructor + */ public CakeInfo(Long id, CakeToppingInfo cakeToppingInfo, List cakeLayerInfos) { this.id = Optional.of(id); this.cakeToppingInfo = cakeToppingInfo; this.cakeLayerInfos = cakeLayerInfos; } + /** + * Constructor + */ public CakeInfo(CakeToppingInfo cakeToppingInfo, List cakeLayerInfos) { this.id = Optional.empty(); this.cakeToppingInfo = cakeToppingInfo; this.cakeLayerInfos = cakeLayerInfos; } + /** + * Calculate calories + */ public int calculateTotalCalories() { int total = cakeToppingInfo != null ? cakeToppingInfo.calories : 0; total += cakeLayerInfos.stream().mapToInt(c -> c.calories).sum(); @@ -34,7 +43,7 @@ public class CakeInfo { @Override public String toString() { - return String.format("CakeInfo id=%d topping=%s layers=%s totalCalories=%d", id.get(), + return String.format("CakeInfo id=%d topping=%s layers=%s totalCalories=%d", id.orElse(-1L), cakeToppingInfo, cakeLayerInfos, calculateTotalCalories()); } } diff --git a/layers/src/main/java/com/iluwatar/layers/CakeLayerInfo.java b/layers/src/main/java/com/iluwatar/layers/CakeLayerInfo.java index 3dff379da..5bc38b109 100644 --- a/layers/src/main/java/com/iluwatar/layers/CakeLayerInfo.java +++ b/layers/src/main/java/com/iluwatar/layers/CakeLayerInfo.java @@ -13,12 +13,18 @@ public class CakeLayerInfo { public final String name; public final int calories; + /** + * Constructor + */ public CakeLayerInfo(Long id, String name, int calories) { this.id = Optional.of(id); this.name = name; this.calories = calories; } + /** + * Constructor + */ public CakeLayerInfo(String name, int calories) { this.id = Optional.empty(); this.name = name; @@ -27,6 +33,6 @@ public class CakeLayerInfo { @Override public String toString() { - return String.format("CakeLayerInfo id=%d name=%s calories=%d", id.get(), name, calories); + return String.format("CakeLayerInfo id=%d name=%s calories=%d", id.orElse(-1L), name, calories); } } diff --git a/layers/src/main/java/com/iluwatar/layers/CakeTopping.java b/layers/src/main/java/com/iluwatar/layers/CakeTopping.java index 6dc9c45fc..9f2107f1e 100644 --- a/layers/src/main/java/com/iluwatar/layers/CakeTopping.java +++ b/layers/src/main/java/com/iluwatar/layers/CakeTopping.java @@ -58,7 +58,7 @@ public class CakeTopping { @Override public String toString() { - return String.format("id=%s name=%s calories=%d", name, calories); + return String.format("id=%s name=%s calories=%d", id, name, calories); } public Cake getCake() { diff --git a/layers/src/main/java/com/iluwatar/layers/CakeToppingInfo.java b/layers/src/main/java/com/iluwatar/layers/CakeToppingInfo.java index 4e432ec44..4c9be6a3e 100644 --- a/layers/src/main/java/com/iluwatar/layers/CakeToppingInfo.java +++ b/layers/src/main/java/com/iluwatar/layers/CakeToppingInfo.java @@ -13,12 +13,18 @@ public class CakeToppingInfo { public final String name; public final int calories; + /** + * Constructor + */ public CakeToppingInfo(Long id, String name, int calories) { this.id = Optional.of(id); this.name = name; this.calories = calories; } + /** + * Constructor + */ public CakeToppingInfo(String name, int calories) { this.id = Optional.empty(); this.name = name; @@ -27,6 +33,6 @@ public class CakeToppingInfo { @Override public String toString() { - return String.format("CakeToppingInfo id=%d name=%s calories=%d", id.get(), name, calories); + return String.format("CakeToppingInfo id=%d name=%s calories=%d", id.orElse(-1L), name, calories); } } diff --git a/layers/src/main/resources/META-INF/persistence.xml b/layers/src/main/resources/META-INF/persistence.xml index d94d8582b..96856e1b9 100644 --- a/layers/src/main/resources/META-INF/persistence.xml +++ b/layers/src/main/resources/META-INF/persistence.xml @@ -1,8 +1,8 @@ + xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> - + \ No newline at end of file diff --git a/layers/src/main/resources/applicationContext.xml b/layers/src/main/resources/applicationContext.xml index 0c908ad2e..6b3bc466d 100644 --- a/layers/src/main/resources/applicationContext.xml +++ b/layers/src/main/resources/applicationContext.xml @@ -1,42 +1,39 @@ - + - + - + - - - + + + - - - - - - + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/layers/src/test/java/com/iluwatar/layers/AppTest.java b/layers/src/test/java/com/iluwatar/layers/AppTest.java index 7db3f6ecd..cd03ae815 100644 --- a/layers/src/test/java/com/iluwatar/layers/AppTest.java +++ b/layers/src/test/java/com/iluwatar/layers/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.layers; import org.junit.Test; -import com.iluwatar.layers.App; - /** * * Application test diff --git a/layers/src/test/java/com/iluwatar/layers/CakeBakingExceptionTest.java b/layers/src/test/java/com/iluwatar/layers/CakeBakingExceptionTest.java new file mode 100644 index 000000000..87381a309 --- /dev/null +++ b/layers/src/test/java/com/iluwatar/layers/CakeBakingExceptionTest.java @@ -0,0 +1,30 @@ +package com.iluwatar.layers; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * Date: 12/15/15 - 7:57 PM + * + * @author Jeroen Meulemeester + */ +public class CakeBakingExceptionTest { + + @Test + public void testConstructor() throws Exception { + final CakeBakingException exception = new CakeBakingException(); + assertNull(exception.getMessage()); + assertNull(exception.getCause()); + } + + @Test + public void testConstructorWithMessage() throws Exception { + final String expectedMessage = "message"; + final CakeBakingException exception = new CakeBakingException(expectedMessage); + assertEquals(expectedMessage, exception.getMessage()); + assertNull(exception.getCause()); + } + +} diff --git a/layers/src/test/java/com/iluwatar/layers/CakeBakingServiceImplTest.java b/layers/src/test/java/com/iluwatar/layers/CakeBakingServiceImplTest.java new file mode 100644 index 000000000..11caab9ea --- /dev/null +++ b/layers/src/test/java/com/iluwatar/layers/CakeBakingServiceImplTest.java @@ -0,0 +1,159 @@ +package com.iluwatar.layers; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/15/15 - 9:55 PM + * + * @author Jeroen Meulemeester + */ +public class CakeBakingServiceImplTest { + + @Test + public void testLayers() throws CakeBakingException { + final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); + + final List initialLayers = service.getAvailableLayers(); + assertNotNull(initialLayers); + assertTrue(initialLayers.isEmpty()); + + service.saveNewLayer(new CakeLayerInfo("Layer1", 1000)); + service.saveNewLayer(new CakeLayerInfo("Layer2", 2000)); + + final List availableLayers = service.getAvailableLayers(); + assertNotNull(availableLayers); + assertEquals(2, availableLayers.size()); + for (final CakeLayerInfo layer : availableLayers) { + assertNotNull(layer.id); + assertNotNull(layer.name); + assertNotNull(layer.toString()); + assertTrue(layer.calories > 0); + } + + } + + @Test + public void testToppings() throws CakeBakingException { + final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); + + final List initialToppings = service.getAvailableToppings(); + assertNotNull(initialToppings); + assertTrue(initialToppings.isEmpty()); + + service.saveNewTopping(new CakeToppingInfo("Topping1", 1000)); + service.saveNewTopping(new CakeToppingInfo("Topping2", 2000)); + + final List availableToppings = service.getAvailableToppings(); + assertNotNull(availableToppings); + assertEquals(2, availableToppings.size()); + for (final CakeToppingInfo topping : availableToppings) { + assertNotNull(topping.id); + assertNotNull(topping.name); + assertNotNull(topping.toString()); + assertTrue(topping.calories > 0); + } + + } + + @Test + public void testBakeCakes() throws CakeBakingException { + final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); + + final List initialCakes = service.getAllCakes(); + assertNotNull(initialCakes); + assertTrue(initialCakes.isEmpty()); + + final CakeToppingInfo topping1 = new CakeToppingInfo("Topping1", 1000); + final CakeToppingInfo topping2 = new CakeToppingInfo("Topping2", 2000); + service.saveNewTopping(topping1); + service.saveNewTopping(topping2); + + final CakeLayerInfo layer1 = new CakeLayerInfo("Layer1", 1000); + final CakeLayerInfo layer2 = new CakeLayerInfo("Layer2", 2000); + final CakeLayerInfo layer3 = new CakeLayerInfo("Layer3", 2000); + service.saveNewLayer(layer1); + service.saveNewLayer(layer2); + service.saveNewLayer(layer3); + + service.bakeNewCake(new CakeInfo(topping1, Arrays.asList(layer1, layer2))); + service.bakeNewCake(new CakeInfo(topping2, Collections.singletonList(layer3))); + + final List allCakes = service.getAllCakes(); + assertNotNull(allCakes); + assertEquals(2, allCakes.size()); + for (final CakeInfo cakeInfo : allCakes) { + assertNotNull(cakeInfo.id); + assertNotNull(cakeInfo.cakeToppingInfo); + assertNotNull(cakeInfo.cakeLayerInfos); + assertNotNull(cakeInfo.toString()); + assertFalse(cakeInfo.cakeLayerInfos.isEmpty()); + assertTrue(cakeInfo.calculateTotalCalories() > 0); + } + + } + + @Test(expected = CakeBakingException.class) + public void testBakeCakeMissingTopping() throws CakeBakingException { + final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); + + final CakeLayerInfo layer1 = new CakeLayerInfo("Layer1", 1000); + final CakeLayerInfo layer2 = new CakeLayerInfo("Layer2", 2000); + service.saveNewLayer(layer1); + service.saveNewLayer(layer2); + + final CakeToppingInfo missingTopping = new CakeToppingInfo("Topping1", 1000); + service.bakeNewCake(new CakeInfo(missingTopping, Arrays.asList(layer1, layer2))); + } + + @Test(expected = CakeBakingException.class) + public void testBakeCakeMissingLayer() throws CakeBakingException { + final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); + + final List initialCakes = service.getAllCakes(); + assertNotNull(initialCakes); + assertTrue(initialCakes.isEmpty()); + + final CakeToppingInfo topping1 = new CakeToppingInfo("Topping1", 1000); + service.saveNewTopping(topping1); + + final CakeLayerInfo layer1 = new CakeLayerInfo("Layer1", 1000); + service.saveNewLayer(layer1); + + final CakeLayerInfo missingLayer = new CakeLayerInfo("Layer2", 2000); + service.bakeNewCake(new CakeInfo(topping1, Arrays.asList(layer1, missingLayer))); + + } + + @Test(expected = CakeBakingException.class) + public void testBakeCakesUsedLayer() throws CakeBakingException { + final CakeBakingServiceImpl service = new CakeBakingServiceImpl(); + + final List initialCakes = service.getAllCakes(); + assertNotNull(initialCakes); + assertTrue(initialCakes.isEmpty()); + + final CakeToppingInfo topping1 = new CakeToppingInfo("Topping1", 1000); + final CakeToppingInfo topping2 = new CakeToppingInfo("Topping2", 2000); + service.saveNewTopping(topping1); + service.saveNewTopping(topping2); + + final CakeLayerInfo layer1 = new CakeLayerInfo("Layer1", 1000); + final CakeLayerInfo layer2 = new CakeLayerInfo("Layer2", 2000); + service.saveNewLayer(layer1); + service.saveNewLayer(layer2); + + service.bakeNewCake(new CakeInfo(topping1, Arrays.asList(layer1, layer2))); + service.bakeNewCake(new CakeInfo(topping2, Collections.singletonList(layer2))); + + } + +} diff --git a/layers/src/test/java/com/iluwatar/layers/CakeTest.java b/layers/src/test/java/com/iluwatar/layers/CakeTest.java new file mode 100644 index 000000000..8c2bd4c15 --- /dev/null +++ b/layers/src/test/java/com/iluwatar/layers/CakeTest.java @@ -0,0 +1,97 @@ +package com.iluwatar.layers; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/15/15 - 8:02 PM + * + * @author Jeroen Meulemeester + */ +public class CakeTest { + + @Test + public void testSetId() { + final Cake cake = new Cake(); + assertNull(cake.getId()); + + final Long expectedId = Long.valueOf(1234L); + cake.setId(expectedId); + assertEquals(expectedId, cake.getId()); + } + + @Test + public void testSetTopping() { + final Cake cake = new Cake(); + assertNull(cake.getTopping()); + + final CakeTopping expectedTopping = new CakeTopping("DummyTopping", 1000); + cake.setTopping(expectedTopping); + assertEquals(expectedTopping, cake.getTopping()); + } + + @Test + public void testSetLayers() { + final Cake cake = new Cake(); + assertNotNull(cake.getLayers()); + assertTrue(cake.getLayers().isEmpty()); + + final Set expectedLayers = new HashSet<>(); + expectedLayers.add(new CakeLayer("layer1", 1000)); + expectedLayers.add(new CakeLayer("layer2", 2000)); + expectedLayers.add(new CakeLayer("layer3", 3000)); + + cake.setLayers(expectedLayers); + assertEquals(expectedLayers, cake.getLayers()); + } + + @Test + public void testAddLayer() { + final Cake cake = new Cake(); + assertNotNull(cake.getLayers()); + assertTrue(cake.getLayers().isEmpty()); + + final Set initialLayers = new HashSet<>(); + initialLayers.add(new CakeLayer("layer1", 1000)); + initialLayers.add(new CakeLayer("layer2", 2000)); + + cake.setLayers(initialLayers); + assertEquals(initialLayers, cake.getLayers()); + + final CakeLayer newLayer = new CakeLayer("layer3", 3000); + cake.addLayer(newLayer); + + final Set expectedLayers = new HashSet<>(); + expectedLayers.addAll(initialLayers); + expectedLayers.addAll(initialLayers); + expectedLayers.add(newLayer); + assertEquals(expectedLayers, cake.getLayers()); + } + + @Test + public void testToString() { + final CakeTopping topping = new CakeTopping("topping", 20); + topping.setId(2345L); + + final CakeLayer layer = new CakeLayer("layer", 100); + layer.setId(3456L); + + final Cake cake = new Cake(); + cake.setId(1234L); + cake.setTopping(topping); + cake.addLayer(layer); + + final String expected = "id=1234 topping=id=2345 name=topping calories=20 " + + "layers=[id=3456 name=layer calories=100]"; + assertEquals(expected, cake.toString()); + + } + +} diff --git a/layers/src/test/java/com/iluwatar/layers/CakeViewImplTest.java b/layers/src/test/java/com/iluwatar/layers/CakeViewImplTest.java new file mode 100644 index 000000000..ce8299170 --- /dev/null +++ b/layers/src/test/java/com/iluwatar/layers/CakeViewImplTest.java @@ -0,0 +1,44 @@ +package com.iluwatar.layers; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Mockito.*; + +/** + * Date: 12/15/15 - 10:04 PM + * + * @author Jeroen Meulemeester + */ +public class CakeViewImplTest extends StdOutTest { + + /** + * Verify if the cake view renders the expected result + */ + @Test + public void testRender() { + + final List layers = new ArrayList<>(); + layers.add(new CakeLayerInfo("layer1", 1000)); + layers.add(new CakeLayerInfo("layer2", 2000)); + layers.add(new CakeLayerInfo("layer3", 3000)); + + final List cakes = new ArrayList<>(); + final CakeInfo cake = new CakeInfo(new CakeToppingInfo("topping", 1000), layers); + cakes.add(cake); + + final CakeBakingService bakingService = mock(CakeBakingService.class); + when(bakingService.getAllCakes()).thenReturn(cakes); + + final CakeViewImpl cakeView = new CakeViewImpl(bakingService); + + verifyZeroInteractions(getStdOutMock()); + + cakeView.render(); + verify(getStdOutMock(), times(1)).println(cake); + + } + +} diff --git a/layers/src/test/java/com/iluwatar/layers/StdOutTest.java b/layers/src/test/java/com/iluwatar/layers/StdOutTest.java new file mode 100644 index 000000000..fe72bbb8a --- /dev/null +++ b/layers/src/test/java/com/iluwatar/layers/StdOutTest.java @@ -0,0 +1,54 @@ +package com.iluwatar.layers; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since the actions of the views don't have + * any influence on any other accessible objects, except for writing to std-out using {@link + * System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/lazy-loading/index.md b/lazy-loading/index.md index 700892af0..6f2501b7e 100644 --- a/lazy-loading/index.md +++ b/lazy-loading/index.md @@ -4,7 +4,11 @@ title: Lazy Loading folder: lazy-loading permalink: /patterns/lazy-loading/ categories: Other -tags: Java +tags: + - Java + - Difficulty-Beginner + - Idiom + - Performance --- **Intent:** Lazy loading is a design pattern commonly used to defer diff --git a/lazy-loading/pom.xml b/lazy-loading/pom.xml index eafc0f559..44c950f4a 100644 --- a/lazy-loading/pom.xml +++ b/lazy-loading/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT lazy-loading diff --git a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java index 25e46d8b9..dabd8c313 100644 --- a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java +++ b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java @@ -7,6 +7,9 @@ package com.iluwatar.lazy.loading; */ public class Heavy { + /** + * Constructor + */ public Heavy() { System.out.println("Creating Heavy ..."); try { diff --git a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java index 132ebaa5f..f78005c73 100644 --- a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java +++ b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java @@ -9,10 +9,16 @@ public class HolderNaive { private Heavy heavy; + /** + * Constructor + */ public HolderNaive() { System.out.println("HolderNaive created"); } + /** + * Get heavy object + */ public Heavy getHeavy() { if (heavy == null) { heavy = new Heavy(); diff --git a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java index d2b15a3af..56074846e 100644 --- a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java +++ b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java @@ -10,10 +10,16 @@ public class HolderThreadSafe { private Heavy heavy; + /** + * Constructor + */ public HolderThreadSafe() { System.out.println("HolderThreadSafe created"); } + /** + * Get heavy object + */ public synchronized Heavy getHeavy() { if (heavy == null) { heavy = new Heavy(); diff --git a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Java8Holder.java b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Java8Holder.java index da021e014..aa86e3b34 100644 --- a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Java8Holder.java +++ b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Java8Holder.java @@ -5,7 +5,7 @@ import java.util.function.Supplier; /** * * This lazy loader is thread safe and more efficient than {@link HolderThreadSafe}. It utilizes - * Java 8 functional interface {@link Supplier} as {@link Heavy} factory. + * Java 8 functional interface {@link Supplier} as {@link Heavy} factory. * */ public class Java8Holder { diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AbstractHolderTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AbstractHolderTest.java new file mode 100644 index 000000000..99523cd0a --- /dev/null +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AbstractHolderTest.java @@ -0,0 +1,41 @@ +package com.iluwatar.lazy.loading; + +import org.junit.Test; + +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertSame; +import static junit.framework.TestCase.assertNull; + +/** + * Date: 12/19/15 - 11:58 AM + * + * @author Jeroen Meulemeester + */ +public abstract class AbstractHolderTest { + + /** + * Get the internal state of the holder value + * + * @return The internal value + */ + abstract Heavy getInternalHeavyValue() throws Exception; + + /** + * Request a lazy loaded {@link Heavy} object from the holder. + * + * @return The lazy loaded {@link Heavy} object + */ + abstract Heavy getHeavy() throws Exception; + + /** + * This test shows that the heavy field is not instantiated until the method getHeavy is called + */ + @Test(timeout = 3000) + public void testGetHeavy() throws Exception { + assertNull(getInternalHeavyValue()); + assertNotNull(getHeavy()); + assertNotNull(getInternalHeavyValue()); + assertSame(getHeavy(), getInternalHeavyValue()); + } + +} diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java index 591b1282d..29176a6b5 100644 --- a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.lazy.loading; import org.junit.Test; -import com.iluwatar.lazy.loading.App; - /** * * Application test diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderNaiveTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderNaiveTest.java new file mode 100644 index 000000000..2c539e8ca --- /dev/null +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderNaiveTest.java @@ -0,0 +1,26 @@ +package com.iluwatar.lazy.loading; + +import java.lang.reflect.Field; + +/** + * Date: 12/19/15 - 12:05 PM + * + * @author Jeroen Meulemeester + */ +public class HolderNaiveTest extends AbstractHolderTest { + + private final HolderNaive holder = new HolderNaive(); + + @Override + Heavy getInternalHeavyValue() throws Exception { + final Field holderField = HolderNaive.class.getDeclaredField("heavy"); + holderField.setAccessible(true); + return (Heavy) holderField.get(this.holder); + } + + @Override + Heavy getHeavy() { + return holder.getHeavy(); + } + +} \ No newline at end of file diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java index d827f186b..f6aed73b7 100644 --- a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java @@ -1,45 +1,26 @@ package com.iluwatar.lazy.loading; -import org.junit.Test; - import java.lang.reflect.Field; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - /** - * Using reflection this test shows that the heavy field is not instantiated until the method - * getHeavy is called + * Date: 12/19/15 - 12:19 PM * - * Created by jones on 11/10/2015. + * @author Jeroen Meulemeester */ -public class HolderThreadSafeTest { +public class HolderThreadSafeTest extends AbstractHolderTest { - @Test - public void test() throws IllegalAccessException { - HolderThreadSafe hts = new HolderThreadSafe(); + private final HolderThreadSafe holder = new HolderThreadSafe(); - { - // first call is null - Field[] ff = HolderThreadSafe.class.getDeclaredFields(); - for (Field f : ff) { - f.setAccessible(true); - } - - assertNull(ff[0].get(hts)); - } - - // now it is lazily loaded - hts.getHeavy(); - - { - // now it is not null - call via reflection so that the test is the same before and after - Field[] ff = HolderThreadSafe.class.getDeclaredFields(); - for (Field f : ff) { - f.setAccessible(true); - } - - assertNotNull(ff[0].get(hts)); - } + @Override + Heavy getInternalHeavyValue() throws Exception { + final Field holderField = HolderThreadSafe.class.getDeclaredField("heavy"); + holderField.setAccessible(true); + return (Heavy) holderField.get(this.holder); } -} + + @Override + Heavy getHeavy() throws Exception { + return this.holder.getHeavy(); + } + +} \ No newline at end of file diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/Java8HolderTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/Java8HolderTest.java new file mode 100644 index 000000000..aed9a054e --- /dev/null +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/Java8HolderTest.java @@ -0,0 +1,40 @@ +package com.iluwatar.lazy.loading; + +import java.lang.reflect.Field; +import java.util.function.Supplier; + +/** + * Date: 12/19/15 - 12:27 PM + * + * @author Jeroen Meulemeester + */ +public class Java8HolderTest extends AbstractHolderTest { + + private final Java8Holder holder = new Java8Holder(); + + + @Override + Heavy getInternalHeavyValue() throws Exception { + final Field holderField = Java8Holder.class.getDeclaredField("heavy"); + holderField.setAccessible(true); + + final Supplier supplier = (Supplier) holderField.get(this.holder); + final Class supplierClass = supplier.getClass(); + + // This is a little fishy, but I don't know another way to test this: + // The lazy holder is at first a lambda, but gets replaced with a new supplier after loading ... + if (supplierClass.isLocalClass()) { + final Field instanceField = supplierClass.getDeclaredField("heavyInstance"); + instanceField.setAccessible(true); + return (Heavy) instanceField.get(supplier); + } else { + return null; + } + } + + @Override + Heavy getHeavy() throws Exception { + return holder.getHeavy(); + } + +} \ No newline at end of file diff --git a/mediator/pom.xml b/mediator/pom.xml index 60999c5aa..449ce6e35 100644 --- a/mediator/pom.xml +++ b/mediator/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT mediator @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java b/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java index acc70a0e6..ab15c26da 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java +++ b/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java @@ -19,7 +19,7 @@ public class PartyImpl implements Party { @Override public void act(PartyMember actor, Action action) { for (PartyMember member : members) { - if (member != actor) { + if (!member.equals(actor)) { member.partyAction(action); } } diff --git a/mediator/src/test/java/com/iluwatar/mediator/AppTest.java b/mediator/src/test/java/com/iluwatar/mediator/AppTest.java index 4b3269244..3e10dd846 100644 --- a/mediator/src/test/java/com/iluwatar/mediator/AppTest.java +++ b/mediator/src/test/java/com/iluwatar/mediator/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.mediator; import org.junit.Test; -import com.iluwatar.mediator.App; - /** * * Application test diff --git a/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java b/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java new file mode 100644 index 000000000..992662fb2 --- /dev/null +++ b/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java @@ -0,0 +1,41 @@ +package com.iluwatar.mediator; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/19/15 - 10:00 PM + * + * @author Jeroen Meulemeester + */ +public class PartyImplTest { + + /** + * Verify if a member is notified when it's joining a party. Generate an action and see if the + * other member gets it. Also check members don't get their own actions. + */ + @Test + public void testPartyAction() { + final PartyMember partyMember1 = mock(PartyMember.class); + final PartyMember partyMember2 = mock(PartyMember.class); + + final PartyImpl party = new PartyImpl(); + party.addMember(partyMember1); + party.addMember(partyMember2); + + verify(partyMember1).joinedParty(party); + verify(partyMember2).joinedParty(party); + + party.act(partyMember1, Action.GOLD); + verifyZeroInteractions(partyMember1); + verify(partyMember2).partyAction(Action.GOLD); + + verifyNoMoreInteractions(partyMember1, partyMember2); + + } + +} diff --git a/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java b/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java new file mode 100644 index 000000000..31b7222e9 --- /dev/null +++ b/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java @@ -0,0 +1,128 @@ +package com.iluwatar.mediator; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.PrintStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Supplier; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/19/15 - 10:13 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class PartyMemberTest { + + @Parameterized.Parameters + public static Collection[]> data() { + return Arrays.asList( + new Supplier[]{Hobbit::new}, + new Supplier[]{Hunter::new}, + new Supplier[]{Rogue::new}, + new Supplier[]{Wizard::new} + ); + } + + /** + * The mocked standard out {@link PrintStream}, required since some actions on a {@link + * PartyMember} have any influence on any other accessible objects, except for writing to std-out + * using {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * The factory, used to create a new instance of the tested party member + */ + private final Supplier memberSupplier; + + /** + * Create a new test instance, using the given {@link PartyMember} factory + * + * @param memberSupplier The party member factory + */ + public PartyMemberTest(final Supplier memberSupplier) { + this.memberSupplier = memberSupplier; + } + + /** + * Verify if a party action triggers the correct output to the std-Out + */ + @Test + public void testPartyAction() { + final PartyMember member = this.memberSupplier.get(); + + for (final Action action : Action.values()) { + member.partyAction(action); + verify(this.stdOutMock).println(member.toString() + " " + action.getDescription()); + } + + verifyNoMoreInteractions(this.stdOutMock); + } + + /** + * Verify if a member action triggers the expected interactions with the party class + */ + @Test + public void testAct() { + final PartyMember member = this.memberSupplier.get(); + + member.act(Action.GOLD); + verifyZeroInteractions(this.stdOutMock); + + final Party party = mock(Party.class); + member.joinedParty(party); + verify(this.stdOutMock).println(member.toString() + " joins the party"); + + for (final Action action : Action.values()) { + member.act(action); + verify(this.stdOutMock).println(member.toString() + " " + action.toString()); + verify(party).act(member, action); + } + + verifyNoMoreInteractions(party, this.stdOutMock); + } + + /** + * Verify if {@link PartyMember#toString()} generate the expected output + */ + @Test + public void testToString() throws Exception { + final PartyMember member = this.memberSupplier.get(); + final Class memberClass = member.getClass(); + assertEquals(memberClass.getSimpleName(), member.toString()); + } + +} diff --git a/memento/index.md b/memento/index.md index f299506e0..7322aef50 100644 --- a/memento/index.md +++ b/memento/index.md @@ -7,6 +7,7 @@ categories: Behavioral tags: - Java - Gang Of Four + - Difficulty-Intermediate --- **Also known as:** Token diff --git a/memento/pom.xml b/memento/pom.xml index 08d5c3a02..258cd270a 100644 --- a/memento/pom.xml +++ b/memento/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT memento diff --git a/memento/src/main/java/com/iluwatar/memento/App.java b/memento/src/main/java/com/iluwatar/memento/App.java index c99894680..e08e9a106 100644 --- a/memento/src/main/java/com/iluwatar/memento/App.java +++ b/memento/src/main/java/com/iluwatar/memento/App.java @@ -23,6 +23,9 @@ import java.util.Stack; */ public class App { + /** + * Program entry point + */ public static void main(String[] args) { Stack states = new Stack<>(); diff --git a/memento/src/main/java/com/iluwatar/memento/Star.java b/memento/src/main/java/com/iluwatar/memento/Star.java index b4ec1c669..f67edfd15 100644 --- a/memento/src/main/java/com/iluwatar/memento/Star.java +++ b/memento/src/main/java/com/iluwatar/memento/Star.java @@ -11,12 +11,18 @@ public class Star { private int ageYears; private int massTons; + /** + * Constructor + */ public Star(StarType startType, int startAge, int startMass) { this.type = startType; this.ageYears = startAge; this.massTons = startMass; } + /** + * Makes time pass for the star + */ public void timePasses() { ageYears *= 2; massTons *= 8; diff --git a/memento/src/test/java/com/iluwatar/memento/AppTest.java b/memento/src/test/java/com/iluwatar/memento/AppTest.java index 4eda4a6f9..79ffea00f 100644 --- a/memento/src/test/java/com/iluwatar/memento/AppTest.java +++ b/memento/src/test/java/com/iluwatar/memento/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.memento; import org.junit.Test; -import com.iluwatar.memento.App; - /** * * Application test diff --git a/memento/src/test/java/com/iluwatar/memento/StarTest.java b/memento/src/test/java/com/iluwatar/memento/StarTest.java new file mode 100644 index 000000000..b5c7d9be0 --- /dev/null +++ b/memento/src/test/java/com/iluwatar/memento/StarTest.java @@ -0,0 +1,75 @@ +package com.iluwatar.memento; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Date: 12/20/15 - 10:08 AM + * + * @author Jeroen Meulemeester + */ +public class StarTest { + + /** + * Verify the stages of a dying sun, without going back in time + */ + @Test + public void testTimePasses() { + final Star star = new Star(StarType.SUN, 1, 2); + assertEquals("sun age: 1 years mass: 2 tons", star.toString()); + + star.timePasses(); + assertEquals("red giant age: 2 years mass: 16 tons", star.toString()); + + star.timePasses(); + assertEquals("white dwarf age: 4 years mass: 128 tons", star.toString()); + + star.timePasses(); + assertEquals("supernova age: 8 years mass: 1024 tons", star.toString()); + + star.timePasses(); + assertEquals("dead star age: 16 years mass: 8192 tons", star.toString()); + + star.timePasses(); + assertEquals("dead star age: 64 years mass: 0 tons", star.toString()); + + star.timePasses(); + assertEquals("dead star age: 256 years mass: 0 tons", star.toString()); + } + + /** + * Verify some stage of a dying sun, but go back in time to test the memento + */ + @Test + public void testSetMemento() { + final Star star = new Star(StarType.SUN, 1, 2); + final StarMemento firstMemento = star.getMemento(); + assertEquals("sun age: 1 years mass: 2 tons", star.toString()); + + star.timePasses(); + final StarMemento secondMemento = star.getMemento(); + assertEquals("red giant age: 2 years mass: 16 tons", star.toString()); + + star.timePasses(); + final StarMemento thirdMemento = star.getMemento(); + assertEquals("white dwarf age: 4 years mass: 128 tons", star.toString()); + + star.timePasses(); + assertEquals("supernova age: 8 years mass: 1024 tons", star.toString()); + + star.setMemento(thirdMemento); + assertEquals("white dwarf age: 4 years mass: 128 tons", star.toString()); + + star.timePasses(); + assertEquals("supernova age: 8 years mass: 1024 tons", star.toString()); + + star.setMemento(secondMemento); + assertEquals("red giant age: 2 years mass: 16 tons", star.toString()); + + star.setMemento(firstMemento); + assertEquals("sun age: 1 years mass: 2 tons", star.toString()); + + } + +} diff --git a/message-channel/index.md b/message-channel/index.md index 06cf93488..3b742a983 100644 --- a/message-channel/index.md +++ b/message-channel/index.md @@ -7,6 +7,7 @@ categories: Integration tags: - Java - EIP + - Camel --- **Intent:** When two applications communicate using a messaging system they do it by using logical addresses diff --git a/message-channel/pom.xml b/message-channel/pom.xml index 0c34678a6..4f5f90339 100644 --- a/message-channel/pom.xml +++ b/message-channel/pom.xml @@ -6,7 +6,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT message-channel diff --git a/message-channel/src/main/java/com/iluwatar/message/channel/App.java b/message-channel/src/main/java/com/iluwatar/message/channel/App.java index a41dd74dc..b0aeb690f 100644 --- a/message-channel/src/main/java/com/iluwatar/message/channel/App.java +++ b/message-channel/src/main/java/com/iluwatar/message/channel/App.java @@ -30,9 +30,6 @@ public class App { /** * Program entry point - * - * @param args command line args - * @throws Exception */ public static void main(String[] args) throws Exception { CamelContext context = new DefaultCamelContext(); diff --git a/model-view-controller/index.md b/model-view-controller/index.md index 1ba1089c0..3d1d3e929 100644 --- a/model-view-controller/index.md +++ b/model-view-controller/index.md @@ -4,7 +4,9 @@ title: Model-View-Controller folder: model-view-controller permalink: /patterns/model-view-controller/ categories: Presentation Tier -tags: Java +tags: + - Java + - Difficulty-Intermediate --- **Intent:** Separate the user interface into three interconnected components: diff --git a/model-view-controller/pom.xml b/model-view-controller/pom.xml index 0f4539747..6db4d556f 100644 --- a/model-view-controller/pom.xml +++ b/model-view-controller/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT model-view-controller @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java index 7142c2979..286ab9119 100644 --- a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.model.view.controller; import org.junit.Test; -import com.iluwatar.model.view.controller.App; - /** * * Application test diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantControllerTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantControllerTest.java new file mode 100644 index 000000000..0090f2d1d --- /dev/null +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantControllerTest.java @@ -0,0 +1,100 @@ +package com.iluwatar.model.view.controller; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/20/15 - 2:19 PM + * + * @author Jeroen Meulemeester + */ +public class GiantControllerTest { + + /** + * Verify if the controller passes the health level through to the model and vice versa + */ + @Test + public void testSetHealth() { + final GiantModel model = mock(GiantModel.class); + final GiantView view = mock(GiantView.class); + final GiantController controller = new GiantController(model, view); + + verifyZeroInteractions(model, view); + + for (final Health health : Health.values()) { + controller.setHealth(health); + verify(model).setHealth(health); + verifyZeroInteractions(view); + } + + controller.getHealth(); + verify(model).getHealth(); + + verifyNoMoreInteractions(model, view); + } + + /** + * Verify if the controller passes the fatigue level through to the model and vice versa + */ + @Test + public void testSetFatigue() { + final GiantModel model = mock(GiantModel.class); + final GiantView view = mock(GiantView.class); + final GiantController controller = new GiantController(model, view); + + verifyZeroInteractions(model, view); + + for (final Fatigue fatigue : Fatigue.values()) { + controller.setFatigue(fatigue); + verify(model).setFatigue(fatigue); + verifyZeroInteractions(view); + } + + controller.getFatigue(); + verify(model).getFatigue(); + + verifyNoMoreInteractions(model, view); + } + + /** + * Verify if the controller passes the nourishment level through to the model and vice versa + */ + @Test + public void testSetNourishment() { + final GiantModel model = mock(GiantModel.class); + final GiantView view = mock(GiantView.class); + final GiantController controller = new GiantController(model, view); + + verifyZeroInteractions(model, view); + + for (final Nourishment nourishment : Nourishment.values()) { + controller.setNourishment(nourishment); + verify(model).setNourishment(nourishment); + verifyZeroInteractions(view); + } + + controller.getNourishment(); + verify(model).getNourishment(); + + verifyNoMoreInteractions(model, view); + } + + @Test + public void testUpdateView() { + final GiantModel model = mock(GiantModel.class); + final GiantView view = mock(GiantView.class); + final GiantController controller = new GiantController(model, view); + + verifyZeroInteractions(model, view); + + controller.updateView(); + verify(view).displayGiant(model); + + verifyNoMoreInteractions(model, view); + } + +} \ No newline at end of file diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantModelTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantModelTest.java new file mode 100644 index 000000000..9513a62ec --- /dev/null +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantModelTest.java @@ -0,0 +1,56 @@ +package com.iluwatar.model.view.controller; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Date: 12/20/15 - 2:10 PM + * + * @author Jeroen Meulemeester + */ +public class GiantModelTest { + + /** + * Verify if the health value is set properly though the constructor and setter + */ + @Test + public void testSetHealth() { + final GiantModel model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); + assertEquals(Health.HEALTHY, model.getHealth()); + for (final Health health : Health.values()) { + model.setHealth(health); + assertEquals(health, model.getHealth()); + assertEquals("The giant looks " + health.toString() + ", alert and saturated.", model.toString()); + } + } + + /** + * Verify if the fatigue level is set properly though the constructor and setter + */ + @Test + public void testSetFatigue() { + final GiantModel model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); + assertEquals(Fatigue.ALERT, model.getFatigue()); + for (final Fatigue fatigue : Fatigue.values()) { + model.setFatigue(fatigue); + assertEquals(fatigue, model.getFatigue()); + assertEquals("The giant looks healthy, " + fatigue.toString() + " and saturated.", model.toString()); + } + } + + /** + * Verify if the nourishment level is set properly though the constructor and setter + */ + @Test + public void testSetNourishment() { + final GiantModel model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); + assertEquals(Nourishment.SATURATED, model.getNourishment()); + for (final Nourishment nourishment : Nourishment.values()) { + model.setNourishment(nourishment); + assertEquals(nourishment, model.getNourishment()); + assertEquals("The giant looks healthy, alert and " + nourishment.toString() + ".", model.toString()); + } + } + +} diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantViewTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantViewTest.java new file mode 100644 index 000000000..8d7a7dfbf --- /dev/null +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantViewTest.java @@ -0,0 +1,64 @@ +package com.iluwatar.model.view.controller; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/20/15 - 2:04 PM + * + * @author Jeroen Meulemeester + */ +public class GiantViewTest { + + /** + * The mocked standard out {@link PrintStream}, required since the actions of the views don't have + * any influence on any other accessible objects, except for writing to std-out using {@link + * System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Verify if the {@link GiantView} does what it has to do: Print the {@link GiantModel} to the + * standard out stream, nothing more, nothing less. + */ + @Test + public void testDisplayGiant() { + final GiantView view = new GiantView(); + + final GiantModel model = mock(GiantModel.class); + view.displayGiant(model); + + verify(this.stdOutMock).println(model); + verifyNoMoreInteractions(model, this.stdOutMock); + + } + +} \ No newline at end of file diff --git a/model-view-presenter/index.md b/model-view-presenter/index.md index b51268013..a65a9a651 100644 --- a/model-view-presenter/index.md +++ b/model-view-presenter/index.md @@ -4,7 +4,9 @@ title: Model-View-Presenter folder: model-view-presenter permalink: /patterns/model-view-presenter/ categories: Presentation Tier -tags: Java +tags: + - Java + - Difficulty-Intermediate --- **Intent:** Apply a "Separation of Concerns" principle in a way that allows diff --git a/model-view-presenter/pom.xml b/model-view-presenter/pom.xml index 09410afc6..cea55b47f 100644 --- a/model-view-presenter/pom.xml +++ b/model-view-presenter/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT model-view-presenter model-view-presenter diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java index d04f284ac..0cf4f8c34 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java @@ -39,9 +39,7 @@ public class FileLoader { br.close(); return sb.toString(); - } - - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); } diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJFrame.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJFrame.java index 02cb2703a..db08d525b 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJFrame.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJFrame.java @@ -26,7 +26,7 @@ public class FileSelectorJFrame extends JFrame implements FileSelectorView, Acti /** * The "OK" button for loading the file. */ - private JButton OK; + private JButton ok; /** * The cancel button. @@ -121,10 +121,10 @@ public class FileSelectorJFrame extends JFrame implements FileSelectorView, Acti /* * Add the OK button. */ - this.OK = new JButton("OK"); - this.panel.add(OK); - this.OK.setBounds(250, 50, 100, 25); - this.OK.addActionListener(this); + this.ok = new JButton("OK"); + this.panel.add(ok); + this.ok.setBounds(250, 50, 100, 25); + this.ok.addActionListener(this); /* * Add the cancel button. @@ -140,13 +140,11 @@ public class FileSelectorJFrame extends JFrame implements FileSelectorView, Acti @Override public void actionPerformed(ActionEvent e) { - if (e.getSource() == this.OK) { + if (this.ok.equals(e.getSource())) { this.fileName = this.input.getText(); presenter.fileNameChanged(); presenter.confirmed(); - } - - else if (e.getSource() == this.cancel) { + } else if (this.cancel.equals(e.getSource())) { presenter.cancelled(); } } diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java index 133d8555d..f38dc2655 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java @@ -51,6 +51,9 @@ public class FileSelectorPresenter { loader.setFileName(view.getFileName()); } + /** + * Ok button handler + */ public void confirmed() { if (loader.getFileName() == null || loader.getFileName().equals("")) { view.showMessage("Please give the name of the file first!"); @@ -60,9 +63,7 @@ public class FileSelectorPresenter { if (loader.fileExists()) { String data = loader.loadData(); view.displayData(data); - } - - else { + } else { view.showMessage("The file specified does not exist."); } } diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java index 80cfadd28..f124c0054 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java @@ -9,53 +9,53 @@ public interface FileSelectorView { /** * Opens the view. */ - public void open(); + void open(); /** * Closes the view. */ - public void close(); + void close(); /** * @return True, if the view is opened, false otherwise. */ - public boolean isOpened(); + boolean isOpened(); /** * Sets the presenter component, to the one given as parameter. * * @param presenter The new presenter component. */ - public void setPresenter(FileSelectorPresenter presenter); + void setPresenter(FileSelectorPresenter presenter); /** * @return The presenter Component. */ - public FileSelectorPresenter getPresenter(); + FileSelectorPresenter getPresenter(); /** * Sets the file's name, to the value given as parameter. * * @param name The new name of the file. */ - public void setFileName(String name); + void setFileName(String name); /** * @return The name of the file. */ - public String getFileName(); + String getFileName(); /** * Displays a message to the users. * * @param message The message to be displayed. */ - public void showMessage(String message); + void showMessage(String message); /** * Displays the data to the view. * * @param data The data to be written. */ - public void displayData(String data); + void displayData(String data); } diff --git a/repository/src/test/java/com/iluwatar/repository/AppTest.java b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/AppTest.java similarity index 70% rename from repository/src/test/java/com/iluwatar/repository/AppTest.java rename to model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/AppTest.java index 929f61941..9b4aabc4d 100644 --- a/repository/src/test/java/com/iluwatar/repository/AppTest.java +++ b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/AppTest.java @@ -1,9 +1,7 @@ -package com.iluwatar.repository; +package com.iluwatar.model.view.presenter; import org.junit.Test; -import com.iluwatar.repository.App; - /** * * Application test @@ -16,4 +14,5 @@ public class AppTest { String[] args = {}; App.main(args); } + } diff --git a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileLoaderTest.java b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileLoaderTest.java new file mode 100644 index 000000000..ed1fc0e9e --- /dev/null +++ b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileLoaderTest.java @@ -0,0 +1,21 @@ +package com.iluwatar.model.view.presenter; + +import org.junit.Test; + +import static org.junit.Assert.assertNull; + +/** + * Date: 12/21/15 - 12:12 PM + * + * @author Jeroen Meulemeester + */ +public class FileLoaderTest { + + @Test + public void testLoadData() throws Exception { + final FileLoader fileLoader = new FileLoader(); + fileLoader.setFileName("non-existing-file"); + assertNull(fileLoader.loadData()); + } + +} \ No newline at end of file diff --git a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java index dfdcba31b..ba371525a 100644 --- a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java +++ b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java @@ -1,14 +1,13 @@ package com.iluwatar.model.view.presenter; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; -import com.iluwatar.model.view.presenter.FileLoader; -import com.iluwatar.model.view.presenter.FileSelectorPresenter; -import com.iluwatar.model.view.presenter.FileSelectorStub; - /** * This test case is responsible for testing our application by taking advantage of the * Model-View-Controller architectural pattern. @@ -57,13 +56,13 @@ public class FileSelectorPresenterTest { */ @Test public void updateFileNameToLoader() { - String EXPECTED_FILE = "Stamatis"; - stub.setFileName(EXPECTED_FILE); + String expectedFile = "Stamatis"; + stub.setFileName(expectedFile); presenter.start(); presenter.fileNameChanged(); - assertEquals(EXPECTED_FILE, loader.getFileName()); + assertEquals(expectedFile, loader.getFileName()); } /** diff --git a/monostate/index.md b/monostate/index.md index 2b88f131e..4176af3ce 100644 --- a/monostate/index.md +++ b/monostate/index.md @@ -4,7 +4,9 @@ title: MonoState folder: monostate permalink: /patterns/monostate/ categories: Creational -tags: Java +tags: + - Java + - Difficulty-Beginner --- **Intent:** Enforces a behaviour like sharing the same state amongst all instances. diff --git a/monostate/pom.xml b/monostate/pom.xml index 7531263f0..f18c03e5c 100644 --- a/monostate/pom.xml +++ b/monostate/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT monostate @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/monostate/src/main/java/com/iluwatar/monostate/App.java b/monostate/src/main/java/com/iluwatar/monostate/App.java index 0daad5b67..5c61371fa 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/App.java +++ b/monostate/src/main/java/com/iluwatar/monostate/App.java @@ -28,8 +28,8 @@ public class App { public static void main(String[] args) { LoadBalancer loadBalancer1 = new LoadBalancer(); LoadBalancer loadBalancer2 = new LoadBalancer(); - loadBalancer1.serverequest(new Request("Hello")); - loadBalancer2.serverequest(new Request("Hello World")); + loadBalancer1.serverRequest(new Request("Hello")); + loadBalancer2.serverRequest(new Request("Hello World")); } } diff --git a/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java b/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java index b81e44251..697c48bb4 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java +++ b/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java @@ -24,6 +24,9 @@ public class LoadBalancer { servers.add(new Server("localhost", 8084, ++id)); } + /** + * Add new server + */ public final void addServer(Server server) { synchronized (servers) { servers.add(server); @@ -39,7 +42,10 @@ public class LoadBalancer { return lastServedId; } - public void serverequest(Request request) { + /** + * Handle request + */ + public void serverRequest(Request request) { if (lastServedId >= servers.size()) { lastServedId = 0; } diff --git a/monostate/src/main/java/com/iluwatar/monostate/Server.java b/monostate/src/main/java/com/iluwatar/monostate/Server.java index f48f4ad0f..0cf9ac41f 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/Server.java +++ b/monostate/src/main/java/com/iluwatar/monostate/Server.java @@ -11,6 +11,9 @@ public class Server { public final int port; public final int id; + /** + * Constructor + */ public Server(String host, int port, int id) { this.host = host; this.port = port; @@ -25,7 +28,7 @@ public class Server { return port; } - public final void serve(Request request) { + public void serve(Request request) { System.out.println("Server ID " + id + " associated to host : " + getHost() + " and Port " + getPort() + " Processed request with value " + request.value); } diff --git a/monostate/src/test/java/com/iluwatar/monostate/AppTest.java b/monostate/src/test/java/com/iluwatar/monostate/AppTest.java index c502dd14a..053cd6649 100644 --- a/monostate/src/test/java/com/iluwatar/monostate/AppTest.java +++ b/monostate/src/test/java/com/iluwatar/monostate/AppTest.java @@ -1,26 +1,13 @@ package com.iluwatar.monostate; -import org.junit.Assert; import org.junit.Test; public class AppTest { - @Test - public void testSameStateAmonstAllInstances() { - LoadBalancer balancer = new LoadBalancer(); - LoadBalancer balancer2 = new LoadBalancer(); - balancer.addServer(new Server("localhost", 8085, 6)); - // Both should have the same number of servers. - Assert.assertTrue(balancer.getNoOfServers() == balancer2.getNoOfServers()); - // Both Should have the same LastServedId - Assert.assertTrue(balancer.getLastServedId() == balancer2.getLastServedId()); - } - @Test public void testMain() { String[] args = {}; App.main(args); - Assert.assertTrue(LoadBalancer.getLastServedId() == 2); } } diff --git a/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java b/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java new file mode 100644 index 000000000..5488f12f3 --- /dev/null +++ b/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java @@ -0,0 +1,49 @@ +package com.iluwatar.monostate; + +import org.junit.Assert; +import org.junit.Test; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + +/** + * Date: 12/21/15 - 12:26 PM + * + * @author Jeroen Meulemeester + */ +public class LoadBalancerTest { + + @Test + public void testSameStateAmongstAllInstances() { + final LoadBalancer firstBalancer = new LoadBalancer(); + final LoadBalancer secondBalancer = new LoadBalancer(); + firstBalancer.addServer(new Server("localhost", 8085, 6)); + // Both should have the same number of servers. + Assert.assertTrue(firstBalancer.getNoOfServers() == secondBalancer.getNoOfServers()); + // Both Should have the same LastServedId + Assert.assertTrue(firstBalancer.getLastServedId() == secondBalancer.getLastServedId()); + } + + @Test + public void testServe() { + final Server server = mock(Server.class); + when(server.getHost()).thenReturn("testhost"); + when(server.getPort()).thenReturn(1234); + doNothing().when(server).serve(any(Request.class)); + + final LoadBalancer loadBalancer = new LoadBalancer(); + loadBalancer.addServer(server); + + verifyZeroInteractions(server); + + final Request request = new Request("test"); + for (int i = 0; i < loadBalancer.getNoOfServers() * 2; i++) { + loadBalancer.serverRequest(request); + } + + verify(server, times(2)).serve(request); + verifyNoMoreInteractions(server); + + } + +} diff --git a/multiton/index.md b/multiton/index.md index 617bedb6a..6491de442 100644 --- a/multiton/index.md +++ b/multiton/index.md @@ -4,7 +4,9 @@ title: Multiton folder: multiton permalink: /patterns/multiton/ categories: Creational -tags: Java +tags: + - Java + - Difficulty-Beginner --- **Intent:** Ensure a class only has limited number of instances, and provide a diff --git a/multiton/pom.xml b/multiton/pom.xml index 4cbb326e8..5240ba6be 100644 --- a/multiton/pom.xml +++ b/multiton/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT multiton diff --git a/multiton/src/test/java/com/iluwatar/multiton/AppTest.java b/multiton/src/test/java/com/iluwatar/multiton/AppTest.java index 41b1387a6..6901e6086 100644 --- a/multiton/src/test/java/com/iluwatar/multiton/AppTest.java +++ b/multiton/src/test/java/com/iluwatar/multiton/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.multiton; import org.junit.Test; -import com.iluwatar.multiton.App; - /** * * Application test diff --git a/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java b/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java new file mode 100644 index 000000000..923f76b1e --- /dev/null +++ b/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java @@ -0,0 +1,29 @@ +package com.iluwatar.multiton; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +/** + * Date: 12/22/15 - 22:28 AM + * + * @author Jeroen Meulemeester + */ +public class NazgulTest { + + /** + * Verify if {@link Nazgul#getInstance(NazgulName)} returns the correct Nazgul multiton instance + */ + @Test + public void testGetInstance() { + for (final NazgulName name : NazgulName.values()) { + final Nazgul nazgul = Nazgul.getInstance(name); + assertNotNull(nazgul); + assertSame(nazgul, Nazgul.getInstance(name)); + assertEquals(name, nazgul.getName()); + } + } + +} diff --git a/naked-objects/dom/pom.xml b/naked-objects/dom/pom.xml index bd71db272..24a054c67 100644 --- a/naked-objects/dom/pom.xml +++ b/naked-objects/dom/pom.xml @@ -16,7 +16,7 @@ com.iluwatar naked-objects - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT naked-objects-dom diff --git a/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageService.java b/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageService.java index 6769f95dd..fa1e74048 100644 --- a/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageService.java +++ b/naked-objects/dom/src/main/java/domainapp/dom/app/homepage/HomePageService.java @@ -21,9 +21,7 @@ import org.apache.isis.applib.annotation.HomePage; import org.apache.isis.applib.annotation.NatureOfService; import org.apache.isis.applib.annotation.SemanticsOf; -@DomainService(nature = NatureOfService.VIEW_CONTRIBUTIONS_ONLY // trick to suppress the actions - // from the top-level menu -) +@DomainService(nature = NatureOfService.VIEW_CONTRIBUTIONS_ONLY) public class HomePageService { // region > homePage (action) diff --git a/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObjects.java b/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObjects.java index 5e4642455..849f01c5d 100644 --- a/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObjects.java +++ b/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObjects.java @@ -69,9 +69,12 @@ public class SimpleObjects { } } + /** + * Create simple object + */ @Action(domainEvent = CreateDomainEvent.class) @MemberOrder(sequence = "3") - public SimpleObject create(final @ParameterLayout(named = "Name") String name) { + public SimpleObject create(@ParameterLayout(named = "Name") final String name) { final SimpleObject obj = container.newTransientInstance(SimpleObject.class); obj.setName(name); container.persistIfNotAlready(obj); diff --git a/naked-objects/fixture/pom.xml b/naked-objects/fixture/pom.xml index 057bbb9ff..2cc097e92 100644 --- a/naked-objects/fixture/pom.xml +++ b/naked-objects/fixture/pom.xml @@ -16,7 +16,7 @@ com.iluwatar naked-objects - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT naked-objects-fixture diff --git a/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectCreate.java b/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectCreate.java index 2918fe7f6..9a922a7be 100644 --- a/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectCreate.java +++ b/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectCreate.java @@ -45,8 +45,6 @@ public class SimpleObjectCreate extends FixtureScript { /** * The created simple object (output). - * - * @return */ public SimpleObject getSimpleObject() { return simpleObject; @@ -57,9 +55,9 @@ public class SimpleObjectCreate extends FixtureScript { @Override protected void execute(final ExecutionContext ec) { - String name = checkParam("name", ec, String.class); + String paramName = checkParam("name", ec, String.class); - this.simpleObject = wrap(simpleObjects).create(name); + this.simpleObject = wrap(simpleObjects).create(paramName); // also make available to UI ec.addResult(this, simpleObject); diff --git a/naked-objects/fixture/src/main/java/domainapp/fixture/scenarios/RecreateSimpleObjects.java b/naked-objects/fixture/src/main/java/domainapp/fixture/scenarios/RecreateSimpleObjects.java index c978e0b82..6d17d9b63 100644 --- a/naked-objects/fixture/src/main/java/domainapp/fixture/scenarios/RecreateSimpleObjects.java +++ b/naked-objects/fixture/src/main/java/domainapp/fixture/scenarios/RecreateSimpleObjects.java @@ -29,7 +29,7 @@ import domainapp.fixture.modules.simple.SimpleObjectsTearDown; public class RecreateSimpleObjects extends FixtureScript { - public final List NAMES = Collections.unmodifiableList(Arrays.asList("Foo", "Bar", "Baz", + public final List names = Collections.unmodifiableList(Arrays.asList("Foo", "Bar", "Baz", "Frodo", "Froyo", "Fizz", "Bip", "Bop", "Bang", "Boo")); public RecreateSimpleObjects() { @@ -69,12 +69,12 @@ public class RecreateSimpleObjects extends FixtureScript { protected void execute(final ExecutionContext ec) { // defaults - final int number = defaultParam("number", ec, 3); + final int paramNumber = defaultParam("number", ec, 3); // validate - if (number < 0 || number > NAMES.size()) { + if (paramNumber < 0 || paramNumber > names.size()) { throw new IllegalArgumentException(String.format("number must be in range [0,%d)", - NAMES.size())); + names.size())); } // @@ -82,8 +82,8 @@ public class RecreateSimpleObjects extends FixtureScript { // ec.executeChild(this, new SimpleObjectsTearDown()); - for (int i = 0; i < number; i++) { - final SimpleObjectCreate fs = new SimpleObjectCreate().setName(NAMES.get(i)); + for (int i = 0; i < paramNumber; i++) { + final SimpleObjectCreate fs = new SimpleObjectCreate().setName(names.get(i)); ec.executeChild(this, fs.getName(), fs); simpleObjects.add(fs.getSimpleObject()); } diff --git a/naked-objects/index.md b/naked-objects/index.md index 805cea810..e3dd09a81 100644 --- a/naked-objects/index.md +++ b/naked-objects/index.md @@ -4,7 +4,9 @@ title: Naked Objects folder: naked-objects permalink: /patterns/naked-objects/ categories: Architectural -tags: Java +tags: + - Java + - Difficulty-Expert --- **Intent:** The Naked Objects architectural pattern is well suited for rapid diff --git a/naked-objects/integtests/pom.xml b/naked-objects/integtests/pom.xml index 78819358a..d5fb3c581 100644 --- a/naked-objects/integtests/pom.xml +++ b/naked-objects/integtests/pom.xml @@ -16,7 +16,7 @@ com.iluwatar naked-objects - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT naked-objects-integtests diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/bootstrap/SimpleAppSystemInitializer.java b/naked-objects/integtests/src/test/java/domainapp/integtests/bootstrap/SimpleAppSystemInitializer.java index c617915f1..90ae45d95 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/bootstrap/SimpleAppSystemInitializer.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/bootstrap/SimpleAppSystemInitializer.java @@ -21,6 +21,12 @@ import org.apache.isis.objectstore.jdo.datanucleus.IsisConfigurationForJdoIntegT public class SimpleAppSystemInitializer { + private SimpleAppSystemInitializer() { + } + + /** + * Init test system + */ public static void initIsft() { IsisSystemForTest isft = IsisSystemForTest.getElseNull(); if (isft == null) { diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/modules/simple/SimpleObjectGlue.java b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/modules/simple/SimpleObjectGlue.java index ef6012919..2ea375b4a 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/modules/simple/SimpleObjectGlue.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/modules/simple/SimpleObjectGlue.java @@ -29,7 +29,7 @@ import static org.junit.Assert.assertThat; public class SimpleObjectGlue extends CukeGlueAbstract { @Given("^there are.* (\\d+) simple objects$") - public void there_are_N_simple_objects(int n) throws Throwable { + public void thereAreNumSimpleObjects(int n) throws Throwable { try { final List findAll = service(SimpleObjects.class).listAll(); assertThat(findAll.size(), is(n)); @@ -41,7 +41,7 @@ public class SimpleObjectGlue extends CukeGlueAbstract { } @When("^I create a new simple object$") - public void I_create_a_new_simple_object() throws Throwable { + public void createNewSimpleObject() throws Throwable { service(SimpleObjects.class).create(UUID.randomUUID().toString()); } diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/SimpleAppIntegTest.java b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/SimpleAppIntegTest.java index 3ceef4e63..7a7ad91b2 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/SimpleAppIntegTest.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/SimpleAppIntegTest.java @@ -27,13 +27,13 @@ import domainapp.integtests.bootstrap.SimpleAppSystemInitializer; public abstract class SimpleAppIntegTest extends IntegrationTestAbstract { - @BeforeClass - public static void initClass() { - org.apache.log4j.PropertyConfigurator.configure("logging.properties"); - SimpleAppSystemInitializer.initIsft(); + @BeforeClass + public static void initClass() { + org.apache.log4j.PropertyConfigurator.configure("logging.properties"); + SimpleAppSystemInitializer.initIsft(); - // instantiating will install onto ThreadLocal - new ScenarioExecutionForIntegration(); - } + // instantiating will install onto ThreadLocal + new ScenarioExecutionForIntegration(); + } } diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectIntegTest.java b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectIntegTest.java index 610136bb8..872aff7a3 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectIntegTest.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectIntegTest.java @@ -35,87 +35,86 @@ import static org.assertj.core.api.Assertions.assertThat; public class SimpleObjectIntegTest extends SimpleAppIntegTest { + @Inject + FixtureScripts fixtureScripts; + + RecreateSimpleObjects fs; + SimpleObject simpleObjectPojo; + SimpleObject simpleObjectWrapped; + + @Before + public void setUp() throws Exception { + // given + fs = new RecreateSimpleObjects().setNumber(1); + fixtureScripts.runFixtureScript(fs, null); + + simpleObjectPojo = fs.getSimpleObjects().get(0); + + assertThat(simpleObjectPojo).isNotNull(); + simpleObjectWrapped = wrap(simpleObjectPojo); + } + + public static class Name extends SimpleObjectIntegTest { + + @Test + public void accessible() throws Exception { + // when + final String name = simpleObjectWrapped.getName(); + // then + assertThat(name).isEqualTo(fs.names.get(0)); + } + + @Test + public void cannotBeUpdatedDirectly() throws Exception { + + // expect + expectedExceptions.expect(DisabledException.class); + + // when + simpleObjectWrapped.setName("new name"); + } + } + + public static class UpdateName extends SimpleObjectIntegTest { + + @Test + public void happyCase() throws Exception { + + // when + simpleObjectWrapped.updateName("new name"); + + // then + assertThat(simpleObjectWrapped.getName()).isEqualTo("new name"); + } + + @Test + public void failsValidation() throws Exception { + + // expect + expectedExceptions.expect(InvalidException.class); + expectedExceptions.expectMessage("Exclamation mark is not allowed"); + + // when + simpleObjectWrapped.updateName("new name!"); + } + } + + public static class Title extends SimpleObjectIntegTest { + @Inject - FixtureScripts fixtureScripts; + DomainObjectContainer container; - RecreateSimpleObjects fs; - SimpleObject simpleObjectPojo; - SimpleObject simpleObjectWrapped; + @Test + public void interpolatesName() throws Exception { - @Before - public void setUp() throws Exception { - // given - fs = new RecreateSimpleObjects().setNumber(1); - fixtureScripts.runFixtureScript(fs, null); + // given + final String name = simpleObjectWrapped.getName(); - simpleObjectPojo = fs.getSimpleObjects().get(0); + // when + final String title = container.titleOf(simpleObjectWrapped); - assertThat(simpleObjectPojo).isNotNull(); - simpleObjectWrapped = wrap(simpleObjectPojo); - } - - public static class Name extends SimpleObjectIntegTest { - - @Test - public void accessible() throws Exception { - // when - final String name = simpleObjectWrapped.getName(); - // then - assertThat(name).isEqualTo(fs.NAMES.get(0)); - } - - @Test - public void cannotBeUpdatedDirectly() throws Exception { - - // expect - expectedExceptions.expect(DisabledException.class); - - // when - simpleObjectWrapped.setName("new name"); - } - } - - public static class UpdateName extends SimpleObjectIntegTest { - - @Test - public void happyCase() throws Exception { - - // when - simpleObjectWrapped.updateName("new name"); - - // then - assertThat(simpleObjectWrapped.getName()).isEqualTo("new name"); - } - - @Test - public void failsValidation() throws Exception { - - // expect - expectedExceptions.expect(InvalidException.class); - expectedExceptions.expectMessage("Exclamation mark is not allowed"); - - // when - simpleObjectWrapped.updateName("new name!"); - } - } - - - public static class Title extends SimpleObjectIntegTest { - - @Inject - DomainObjectContainer container; - - @Test - public void interpolatesName() throws Exception { - - // given - final String name = simpleObjectWrapped.getName(); - - // when - final String title = container.titleOf(simpleObjectWrapped); - - // then - assertThat(title).isEqualTo("Object: " + name); - } + // then + assertThat(title).isEqualTo("Object: " + name); } + } } \ No newline at end of file diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectsIntegTest.java b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectsIntegTest.java index fd3b0ff46..332213542 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectsIntegTest.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/tests/modules/simple/SimpleObjectsIntegTest.java @@ -42,102 +42,102 @@ import static org.assertj.core.api.Assertions.assertThat; public class SimpleObjectsIntegTest extends SimpleAppIntegTest { - @Inject - FixtureScripts fixtureScripts; - @Inject - SimpleObjects simpleObjects; + @Inject + FixtureScripts fixtureScripts; + @Inject + SimpleObjects simpleObjects; - public static class ListAll extends SimpleObjectsIntegTest { + public static class ListAll extends SimpleObjectsIntegTest { - @Test - public void happyCase() throws Exception { + @Test + public void happyCase() throws Exception { - // given - RecreateSimpleObjects fs = new RecreateSimpleObjects(); - fixtureScripts.runFixtureScript(fs, null); - nextTransaction(); + // given + RecreateSimpleObjects fs = new RecreateSimpleObjects(); + fixtureScripts.runFixtureScript(fs, null); + nextTransaction(); - // when - final List all = wrap(simpleObjects).listAll(); + // when + final List all = wrap(simpleObjects).listAll(); - // then - assertThat(all).hasSize(fs.getSimpleObjects().size()); + // then + assertThat(all).hasSize(fs.getSimpleObjects().size()); - SimpleObject simpleObject = wrap(all.get(0)); - assertThat(simpleObject.getName()).isEqualTo(fs.getSimpleObjects().get(0).getName()); - } - - @Test - public void whenNone() throws Exception { - - // given - FixtureScript fs = new SimpleObjectsTearDown(); - fixtureScripts.runFixtureScript(fs, null); - nextTransaction(); - - // when - final List all = wrap(simpleObjects).listAll(); - - // then - assertThat(all).hasSize(0); - } + SimpleObject simpleObject = wrap(all.get(0)); + assertThat(simpleObject.getName()).isEqualTo(fs.getSimpleObjects().get(0).getName()); } - public static class Create extends SimpleObjectsIntegTest { + @Test + public void whenNone() throws Exception { - @Test - public void happyCase() throws Exception { + // given + FixtureScript fs = new SimpleObjectsTearDown(); + fixtureScripts.runFixtureScript(fs, null); + nextTransaction(); - // given - FixtureScript fs = new SimpleObjectsTearDown(); - fixtureScripts.runFixtureScript(fs, null); - nextTransaction(); + // when + final List all = wrap(simpleObjects).listAll(); - // when - wrap(simpleObjects).create("Faz"); - - // then - final List all = wrap(simpleObjects).listAll(); - assertThat(all).hasSize(1); - } - - @Test - public void whenAlreadyExists() throws Exception { - - // given - FixtureScript fs = new SimpleObjectsTearDown(); - fixtureScripts.runFixtureScript(fs, null); - nextTransaction(); - wrap(simpleObjects).create("Faz"); - nextTransaction(); - - // then - expectedExceptions.expectCause(causalChainContains(SQLIntegrityConstraintViolationException.class)); - - // when - wrap(simpleObjects).create("Faz"); - nextTransaction(); - } - - private static Matcher causalChainContains(final Class cls) { - return new TypeSafeMatcher() { - @Override - protected boolean matchesSafely(Throwable item) { - final List causalChain = Throwables.getCausalChain(item); - for (Throwable throwable : causalChain) { - if(cls.isAssignableFrom(throwable.getClass())){ - return true; - } - } - return false; - } - - @Override - public void describeTo(Description description) { - description.appendText("exception with causal chain containing " + cls.getSimpleName()); - } - }; - } + // then + assertThat(all).hasSize(0); } + } + + public static class Create extends SimpleObjectsIntegTest { + + @Test + public void happyCase() throws Exception { + + // given + FixtureScript fs = new SimpleObjectsTearDown(); + fixtureScripts.runFixtureScript(fs, null); + nextTransaction(); + + // when + wrap(simpleObjects).create("Faz"); + + // then + final List all = wrap(simpleObjects).listAll(); + assertThat(all).hasSize(1); + } + + @Test + public void whenAlreadyExists() throws Exception { + + // given + FixtureScript fs = new SimpleObjectsTearDown(); + fixtureScripts.runFixtureScript(fs, null); + nextTransaction(); + wrap(simpleObjects).create("Faz"); + nextTransaction(); + + // then + expectedExceptions.expectCause(causalChainContains(SQLIntegrityConstraintViolationException.class)); + + // when + wrap(simpleObjects).create("Faz"); + nextTransaction(); + } + + private static Matcher causalChainContains(final Class cls) { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(Throwable item) { + final List causalChain = Throwables.getCausalChain(item); + for (Throwable throwable : causalChain) { + if (cls.isAssignableFrom(throwable.getClass())) { + return true; + } + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("exception with causal chain containing " + cls.getSimpleName()); + } + }; + } + } } \ No newline at end of file diff --git a/naked-objects/pom.xml b/naked-objects/pom.xml index aad8a360d..c5b188098 100644 --- a/naked-objects/pom.xml +++ b/naked-objects/pom.xml @@ -15,7 +15,7 @@ java-design-patterns com.iluwatar - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT naked-objects @@ -350,17 +350,17 @@ ${project.groupId} naked-objects-dom - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT ${project.groupId} naked-objects-fixture - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT ${project.groupId} naked-objects-webapp - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT diff --git a/naked-objects/webapp/pom.xml b/naked-objects/webapp/pom.xml index 4935bd3ef..ad43bf91f 100644 --- a/naked-objects/webapp/pom.xml +++ b/naked-objects/webapp/pom.xml @@ -16,7 +16,7 @@ com.iluwatar naked-objects - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT naked-objects-webapp diff --git a/naked-objects/webapp/src/main/java/domainapp/webapp/SimpleApplication.java b/naked-objects/webapp/src/main/java/domainapp/webapp/SimpleApplication.java index c7bbd8c80..a292a7779 100644 --- a/naked-objects/webapp/src/main/java/domainapp/webapp/SimpleApplication.java +++ b/naked-objects/webapp/src/main/java/domainapp/webapp/SimpleApplication.java @@ -74,7 +74,7 @@ public class SimpleApplication extends IsisWicketApplication { *

* for demos only, obvious. */ - private final static boolean DEMO_MODE_USING_CREDENTIALS_AS_QUERYARGS = false; + private static final boolean DEMO_MODE_USING_CREDENTIALS_AS_QUERYARGS = false; @Override @@ -116,6 +116,7 @@ public class SimpleApplication extends IsisWicketApplication { servletRequest.getSession().invalidate(); } } catch (Exception e) { + System.out.println(e); } WebRequest request = super.newWebRequest(servletRequest, filterPath); return request; diff --git a/null-object/index.md b/null-object/index.md index 5127e8565..6b659b5cc 100644 --- a/null-object/index.md +++ b/null-object/index.md @@ -4,7 +4,9 @@ title: Null Object folder: null-object permalink: /patterns/null-object/ categories: Behavioral -tags: Java +tags: + - Java + - Difficulty-Beginner --- **Intent:** In most object-oriented languages, such as Java or C#, references diff --git a/null-object/pom.xml b/null-object/pom.xml index 597dad233..c7312f227 100644 --- a/null-object/pom.xml +++ b/null-object/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT null-object @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java b/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java index 5de258890..4478b9bfa 100644 --- a/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java +++ b/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java @@ -11,6 +11,9 @@ public class NodeImpl implements Node { private final Node left; private final Node right; + /** + * Constructor + */ public NodeImpl(String name, Node left, Node right) { this.name = name; this.left = left; diff --git a/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java b/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java index 58f03da28..0231c7b1a 100644 --- a/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java +++ b/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.nullobject; import org.junit.Test; -import com.iluwatar.nullobject.App; - /** * * Application test diff --git a/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java b/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java new file mode 100644 index 000000000..2bb9a1b4a --- /dev/null +++ b/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java @@ -0,0 +1,43 @@ +package com.iluwatar.nullobject; + +import org.junit.Test; +import org.mockito.Mockito; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +/** + * Date: 12/26/15 - 11:47 PM + * + * @author Jeroen Meulemeester + */ +public class NullNodeTest extends StdOutTest { + + /** + * Verify if {@link NullNode#getInstance()} actually returns the same object instance + */ + @Test + public void testGetInstance() { + final NullNode instance = NullNode.getInstance(); + assertNotNull(instance); + assertSame(instance, NullNode.getInstance()); + } + + @Test + public void testFields() { + final NullNode node = NullNode.getInstance(); + assertEquals(0, node.getTreeSize()); + assertNull(node.getName()); + assertNull(node.getLeft()); + assertNull(node.getRight()); + } + + @Test + public void testWalk() throws Exception { + NullNode.getInstance().walk(); + Mockito.verifyZeroInteractions(getStdOutMock()); + } + +} \ No newline at end of file diff --git a/null-object/src/test/java/com/iluwatar/nullobject/StdOutTest.java b/null-object/src/test/java/com/iluwatar/nullobject/StdOutTest.java new file mode 100644 index 000000000..5a9bae163 --- /dev/null +++ b/null-object/src/test/java/com/iluwatar/nullobject/StdOutTest.java @@ -0,0 +1,54 @@ +package com.iluwatar.nullobject; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since walking through the tree has no + * influence on any other accessible object, except for writing to std-out using {@link + * System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java b/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java new file mode 100644 index 000000000..5d7968584 --- /dev/null +++ b/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java @@ -0,0 +1,101 @@ +package com.iluwatar.nullobject; + +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +/** + * Date: 12/26/15 - 11:44 PM + * + * @author Jeroen Meulemeester + */ +public class TreeTest extends StdOutTest { + + /** + * During the tests, the same tree structure will be used, shown below. End points will be + * terminated with the {@link NullNode} instance. + * + *

+   * root
+   * ├── level1_a
+   * │   ├── level2_a
+   * │   │   ├── level3_a
+   * │   │   └── level3_b
+   * │   └── level2_b
+   * └── level1_b
+   * 
+ */ + private static final Node TREE_ROOT; + + static { + final NodeImpl level1B = new NodeImpl("level1_b", NullNode.getInstance(), NullNode.getInstance()); + final NodeImpl level2B = new NodeImpl("level2_b", NullNode.getInstance(), NullNode.getInstance()); + final NodeImpl level3A = new NodeImpl("level3_a", NullNode.getInstance(), NullNode.getInstance()); + final NodeImpl level3B = new NodeImpl("level3_b", NullNode.getInstance(), NullNode.getInstance()); + final NodeImpl level2A = new NodeImpl("level2_a", level3A, level3B); + final NodeImpl level1A = new NodeImpl("level1_a", level2A, level2B); + TREE_ROOT = new NodeImpl("root", level1A, level1B); + } + + /** + * Verify the number of items in the tree. The root has 6 children so we expect a {@link + * Node#getTreeSize()} of 7 {@link Node}s in total. + */ + @Test + public void testTreeSize() { + assertEquals(7, TREE_ROOT.getTreeSize()); + } + + /** + * Walk through the tree and verify if every item is handled + */ + @Test + public void testWalk() { + TREE_ROOT.walk(); + + final InOrder inOrder = Mockito.inOrder(getStdOutMock()); + inOrder.verify(getStdOutMock()).println("root"); + inOrder.verify(getStdOutMock()).println("level1_a"); + inOrder.verify(getStdOutMock()).println("level2_a"); + inOrder.verify(getStdOutMock()).println("level3_a"); + inOrder.verify(getStdOutMock()).println("level3_b"); + inOrder.verify(getStdOutMock()).println("level2_b"); + inOrder.verify(getStdOutMock()).println("level1_b"); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testGetLeft() throws Exception { + final Node level1 = TREE_ROOT.getLeft(); + assertNotNull(level1); + assertEquals("level1_a", level1.getName()); + assertEquals(5, level1.getTreeSize()); + + final Node level2 = level1.getLeft(); + assertNotNull(level2); + assertEquals("level2_a", level2.getName()); + assertEquals(3, level2.getTreeSize()); + + final Node level3 = level2.getLeft(); + assertNotNull(level3); + assertEquals("level3_a", level3.getName()); + assertEquals(1, level3.getTreeSize()); + assertSame(NullNode.getInstance(), level3.getRight()); + assertSame(NullNode.getInstance(), level3.getLeft()); + } + + @Test + public void testGetRight() throws Exception { + final Node level1 = TREE_ROOT.getRight(); + assertNotNull(level1); + assertEquals("level1_b", level1.getName()); + assertEquals(1, level1.getTreeSize()); + assertSame(NullNode.getInstance(), level1.getRight()); + assertSame(NullNode.getInstance(), level1.getLeft()); + } + +} diff --git a/object-pool/index.md b/object-pool/index.md index 276d9a1f6..0d37041c3 100644 --- a/object-pool/index.md +++ b/object-pool/index.md @@ -4,7 +4,10 @@ title: Object Pool folder: object-pool permalink: /patterns/object-pool/ categories: Creational -tags: Java +tags: + - Java + - Difficulty-Beginner + - Performance --- **Intent:** When objects are expensive to create and they are needed only for diff --git a/object-pool/pom.xml b/object-pool/pom.xml index 75bc894e8..c03d02568 100644 --- a/object-pool/pom.xml +++ b/object-pool/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT object-pool diff --git a/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java b/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java index 79d7d6345..d85955f0a 100644 --- a/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java +++ b/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java @@ -5,8 +5,6 @@ import java.util.HashSet; /** * * Generic object pool - * - * @param */ public abstract class ObjectPool { @@ -15,6 +13,9 @@ public abstract class ObjectPool { protected abstract T create(); + /** + * Checkout object from pool + */ public synchronized T checkOut() { if (available.size() <= 0) { available.add(create()); diff --git a/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java b/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java index aeefd6b3a..f3923fff5 100644 --- a/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java +++ b/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java @@ -11,6 +11,9 @@ public class Oliphaunt { private final int id; + /** + * Constructor + */ public Oliphaunt() { id = counter++; try { diff --git a/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java b/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java index fd2920d88..b36a7e4a1 100644 --- a/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java +++ b/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.object.pool; import org.junit.Test; -import com.iluwatar.object.pool.App; - /** * * Application test diff --git a/object-pool/src/test/java/com/iluwatar/object/pool/OliphauntPoolTest.java b/object-pool/src/test/java/com/iluwatar/object/pool/OliphauntPoolTest.java new file mode 100644 index 000000000..347e0b4c9 --- /dev/null +++ b/object-pool/src/test/java/com/iluwatar/object/pool/OliphauntPoolTest.java @@ -0,0 +1,99 @@ +package com.iluwatar.object.pool; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/27/15 - 1:05 AM + * + * @author Jeroen Meulemeester + */ +public class OliphauntPoolTest { + + /** + * Use the same object 100 times subsequently. This should not take much time since the heavy + * object instantiation is done only once. Verify if we get the same object each time. + */ + @Test(timeout = 5000) + public void testSubsequentCheckinCheckout() { + final OliphauntPool pool = new OliphauntPool(); + assertEquals(pool.toString(), "Pool available=0 inUse=0"); + + final Oliphaunt expectedOliphaunt = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=1"); + + pool.checkIn(expectedOliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=0"); + + for (int i = 0; i < 100; i++) { + final Oliphaunt oliphaunt = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=1"); + assertSame(expectedOliphaunt, oliphaunt); + assertEquals(expectedOliphaunt.getId(), oliphaunt.getId()); + assertEquals(expectedOliphaunt.toString(), oliphaunt.toString()); + + pool.checkIn(oliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=0"); + } + + } + + /** + * Use the same object 100 times subsequently. This should not take much time since the heavy + * object instantiation is done only once. Verify if we get the same object each time. + */ + @Test(timeout = 5000) + public void testConcurrentCheckinCheckout() { + final OliphauntPool pool = new OliphauntPool(); + assertEquals(pool.toString(), "Pool available=0 inUse=0"); + + final Oliphaunt firstOliphaunt = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=1"); + + final Oliphaunt secondOliphaunt = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + + assertNotSame(firstOliphaunt, secondOliphaunt); + assertEquals(firstOliphaunt.getId() + 1, secondOliphaunt.getId()); + + // After checking in the second, we should get the same when checking out a new oliphaunt ... + pool.checkIn(secondOliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=1"); + + final Oliphaunt oliphaunt3 = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + assertSame(secondOliphaunt, oliphaunt3); + + // ... and the same applies for the first one + pool.checkIn(firstOliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=1"); + + final Oliphaunt oliphaunt4 = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + assertSame(firstOliphaunt, oliphaunt4); + + // When both oliphaunt return to the pool, we should still get the same instances + pool.checkIn(firstOliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=1"); + + pool.checkIn(secondOliphaunt); + assertEquals(pool.toString(), "Pool available=2 inUse=0"); + + // The order of the returned instances is not determined, so just put them in a list + // and verify if both expected instances are in there. + final List oliphaunts = Arrays.asList(pool.checkOut(), pool.checkOut()); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + assertTrue(oliphaunts.contains(firstOliphaunt)); + assertTrue(oliphaunts.contains(secondOliphaunt)); + + } + + +} \ No newline at end of file diff --git a/observer/pom.xml b/observer/pom.xml index a3dd25d85..a824a551e 100644 --- a/observer/pom.xml +++ b/observer/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT observer @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/observer/src/main/java/com/iluwatar/observer/Weather.java b/observer/src/main/java/com/iluwatar/observer/Weather.java index 634953945..4e04143a2 100644 --- a/observer/src/main/java/com/iluwatar/observer/Weather.java +++ b/observer/src/main/java/com/iluwatar/observer/Weather.java @@ -27,6 +27,9 @@ public class Weather { observers.remove(obs); } + /** + * Makes time pass for weather + */ public void timePasses() { WeatherType[] enumValues = WeatherType.values(); currentWeather = enumValues[(currentWeather.ordinal() + 1) % enumValues.length]; diff --git a/observer/src/main/java/com/iluwatar/observer/generic/GWeather.java b/observer/src/main/java/com/iluwatar/observer/generic/GWeather.java index 9d1c6ed07..d503c8421 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/GWeather.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/GWeather.java @@ -15,6 +15,9 @@ public class GWeather extends Observable { currentWeather = WeatherType.SUNNY; } + /** + * Makes time pass for weather + */ public void timePasses() { WeatherType[] enumValues = WeatherType.values(); currentWeather = enumValues[(currentWeather.ordinal() + 1) % enumValues.length]; diff --git a/observer/src/main/java/com/iluwatar/observer/generic/Observable.java b/observer/src/main/java/com/iluwatar/observer/generic/Observable.java index f1ad2dca6..e764245b7 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/Observable.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/Observable.java @@ -22,6 +22,13 @@ public abstract class Observable, O extends Observ this.observers.add(observer); } + public void removeObserver(O observer) { + this.observers.remove(observer); + } + + /** + * Notify observers + */ @SuppressWarnings("unchecked") public void notifyObservers(A argument) { for (O observer : observers) { diff --git a/observer/src/main/java/com/iluwatar/observer/generic/Observer.java b/observer/src/main/java/com/iluwatar/observer/generic/Observer.java index b01955419..34b9ac359 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/Observer.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/Observer.java @@ -3,10 +3,6 @@ package com.iluwatar.observer.generic; /** * * Observer - * - * @param - * @param - * @param */ public interface Observer, O extends Observer, A> { diff --git a/observer/src/test/java/com/iluwatar/observer/AppTest.java b/observer/src/test/java/com/iluwatar/observer/AppTest.java index 65976626d..d41acad33 100644 --- a/observer/src/test/java/com/iluwatar/observer/AppTest.java +++ b/observer/src/test/java/com/iluwatar/observer/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.observer; import org.junit.Test; -import com.iluwatar.observer.App; - /** * * Application test diff --git a/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java b/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java new file mode 100644 index 000000000..3571ced63 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java @@ -0,0 +1,37 @@ +package com.iluwatar.observer; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Date: 12/27/15 - 12:07 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class HobbitsTest extends WeatherObserverTest { + + @Parameterized.Parameters + public static Collection data() { + final ArrayList testData = new ArrayList<>(); + testData.add(new Object[]{WeatherType.SUNNY, "The happy hobbits bade in the warm sun."}); + testData.add(new Object[]{WeatherType.RAINY, "The hobbits look for cover from the rain."}); + testData.add(new Object[]{WeatherType.WINDY, "The hobbits hold their hats tightly in the windy weather."}); + testData.add(new Object[]{WeatherType.COLD, "The hobbits are shivering in the cold weather."}); + return testData; + } + + /** + * Create a new test with the given weather and expected response + * + * @param weather The weather that should be unleashed on the observer + * @param response The expected response from the observer + */ + public HobbitsTest(final WeatherType weather, final String response) { + super(weather, response, Hobbits::new); + } + +} diff --git a/observer/src/test/java/com/iluwatar/observer/OrcsTest.java b/observer/src/test/java/com/iluwatar/observer/OrcsTest.java new file mode 100644 index 000000000..a59288ab2 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/OrcsTest.java @@ -0,0 +1,37 @@ +package com.iluwatar.observer; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Date: 12/27/15 - 12:07 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class OrcsTest extends WeatherObserverTest { + + @Parameterized.Parameters + public static Collection data() { + final ArrayList testData = new ArrayList<>(); + testData.add(new Object[]{WeatherType.SUNNY, "The sun hurts the orcs' eyes."}); + testData.add(new Object[]{WeatherType.RAINY, "The orcs are dripping wet."}); + testData.add(new Object[]{WeatherType.WINDY, "The orc smell almost vanishes in the wind."}); + testData.add(new Object[]{WeatherType.COLD, "The orcs are freezing cold."}); + return testData; + } + + /** + * Create a new test with the given weather and expected response + * + * @param weather The weather that should be unleashed on the observer + * @param response The expected response from the observer + */ + public OrcsTest(final WeatherType weather, final String response) { + super(weather, response, Orcs::new); + } + +} diff --git a/observer/src/test/java/com/iluwatar/observer/StdOutTest.java b/observer/src/test/java/com/iluwatar/observer/StdOutTest.java new file mode 100644 index 000000000..3ea0bb119 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/StdOutTest.java @@ -0,0 +1,54 @@ +package com.iluwatar.observer; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/27/15 - 12:16 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since changes in the weather doesn't has + * any influence on any other accessible objects, except for writing to std-out using {@link + * System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + protected final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/observer/src/test/java/com/iluwatar/observer/WeatherObserverTest.java b/observer/src/test/java/com/iluwatar/observer/WeatherObserverTest.java new file mode 100644 index 000000000..e4d6a4430 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/WeatherObserverTest.java @@ -0,0 +1,59 @@ +package com.iluwatar.observer; + +import org.junit.Test; + +import java.util.function.Supplier; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/27/15 - 11:44 AM + * + * @author Jeroen Meulemeester + */ +public abstract class WeatherObserverTest extends StdOutTest { + + /** + * The observer instance factory + */ + private final Supplier factory; + + /** + * The weather type currently tested + */ + private final WeatherType weather; + + /** + * The expected response from the observer + */ + private final String response; + + /** + * Create a new test instance using the given parameters + * + * @param weather The weather currently being tested + * @param response The expected response from the observer + * @param factory The factory, used to create an instance of the tested observer + */ + WeatherObserverTest(final WeatherType weather, final String response, final Supplier factory) { + this.weather = weather; + this.response = response; + this.factory = factory; + } + + /** + * Verify if the weather has the expected influence on the observer + */ + @Test + public void testObserver() { + final O observer = this.factory.get(); + verifyZeroInteractions(getStdOutMock()); + + observer.update(this.weather); + verify(getStdOutMock()).println(this.response); + verifyNoMoreInteractions(getStdOutMock()); + } + +} \ No newline at end of file diff --git a/observer/src/test/java/com/iluwatar/observer/WeatherTest.java b/observer/src/test/java/com/iluwatar/observer/WeatherTest.java new file mode 100644 index 000000000..a195be526 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/WeatherTest.java @@ -0,0 +1,61 @@ +package com.iluwatar.observer; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/27/15 - 11:08 AM + * + * @author Jeroen Meulemeester + */ +public class WeatherTest extends StdOutTest { + + /** + * Add a {@link WeatherObserver}, verify if it gets notified of a weather change, remove the + * observer again and verify that there are no more notifications. + */ + @Test + public void testAddRemoveObserver() { + final WeatherObserver observer = mock(WeatherObserver.class); + + final Weather weather = new Weather(); + weather.addObserver(observer); + verifyZeroInteractions(observer); + + weather.timePasses(); + verify(getStdOutMock()).println("The weather changed to rainy."); + verify(observer).update(WeatherType.RAINY); + + weather.removeObserver(observer); + weather.timePasses(); + verify(getStdOutMock()).println("The weather changed to windy."); + + verifyNoMoreInteractions(observer, getStdOutMock()); + } + + /** + * Verify if the weather passes in the order of the {@link WeatherType}s + */ + @Test + public void testTimePasses() { + final WeatherObserver observer = mock(WeatherObserver.class); + final Weather weather = new Weather(); + weather.addObserver(observer); + + final InOrder inOrder = inOrder(observer, getStdOutMock()); + final WeatherType[] weatherTypes = WeatherType.values(); + for (int i = 1; i < 20; i++) { + weather.timePasses(); + inOrder.verify(observer).update(weatherTypes[i % weatherTypes.length]); + } + + verifyNoMoreInteractions(observer); + } + +} \ No newline at end of file diff --git a/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java b/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java new file mode 100644 index 000000000..6e955cf54 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java @@ -0,0 +1,41 @@ +package com.iluwatar.observer.generic; + +import com.iluwatar.observer.Hobbits; +import com.iluwatar.observer.WeatherObserverTest; +import com.iluwatar.observer.WeatherType; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Date: 12/27/15 - 12:07 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class GHobbitsTest extends ObserverTest { + + @Parameterized.Parameters + public static Collection data() { + final ArrayList testData = new ArrayList<>(); + testData.add(new Object[]{WeatherType.SUNNY, "The happy hobbits bade in the warm sun."}); + testData.add(new Object[]{WeatherType.RAINY, "The hobbits look for cover from the rain."}); + testData.add(new Object[]{WeatherType.WINDY, "The hobbits hold their hats tightly in the windy weather."}); + testData.add(new Object[]{WeatherType.COLD, "The hobbits are shivering in the cold weather."}); + return testData; + } + + /** + * Create a new test with the given weather and expected response + * + * @param weather The weather that should be unleashed on the observer + * @param response The expected response from the observer + */ + public GHobbitsTest(final WeatherType weather, final String response) { + super(weather, response, GHobbits::new); + } + +} diff --git a/observer/src/test/java/com/iluwatar/observer/generic/GWeatherTest.java b/observer/src/test/java/com/iluwatar/observer/generic/GWeatherTest.java new file mode 100644 index 000000000..b7a538167 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/generic/GWeatherTest.java @@ -0,0 +1,65 @@ +package com.iluwatar.observer.generic; + +import com.iluwatar.observer.StdOutTest; +import com.iluwatar.observer.WeatherObserver; +import com.iluwatar.observer.WeatherType; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/27/15 - 11:08 AM + * + * @author Jeroen Meulemeester + */ +public class GWeatherTest extends StdOutTest { + + /** + * Add a {@link WeatherObserver}, verify if it gets notified of a weather change, remove the + * observer again and verify that there are no more notifications. + */ + @Test + public void testAddRemoveObserver() { + final Race observer = mock(Race.class); + + final GWeather weather = new GWeather(); + weather.addObserver(observer); + verifyZeroInteractions(observer); + + weather.timePasses(); + verify(getStdOutMock()).println("The weather changed to rainy."); + verify(observer).update(weather, WeatherType.RAINY); + + weather.removeObserver(observer); + weather.timePasses(); + verify(getStdOutMock()).println("The weather changed to windy."); + + verifyNoMoreInteractions(observer, getStdOutMock()); + } + + /** + * Verify if the weather passes in the order of the {@link WeatherType}s + */ + @Test + public void testTimePasses() { + final Race observer = mock(Race.class); + final GWeather weather = new GWeather(); + weather.addObserver(observer); + + final InOrder inOrder = inOrder(observer, getStdOutMock()); + final WeatherType[] weatherTypes = WeatherType.values(); + for (int i = 1; i < 20; i++) { + weather.timePasses(); + inOrder.verify(observer).update(weather, weatherTypes[i % weatherTypes.length]); + } + + verifyNoMoreInteractions(observer); + } + +} \ No newline at end of file diff --git a/observer/src/test/java/com/iluwatar/observer/generic/ObserverTest.java b/observer/src/test/java/com/iluwatar/observer/generic/ObserverTest.java new file mode 100644 index 000000000..2e664bd58 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/generic/ObserverTest.java @@ -0,0 +1,62 @@ +package com.iluwatar.observer.generic; + +import com.iluwatar.observer.StdOutTest; +import com.iluwatar.observer.WeatherType; + +import org.junit.Test; + +import java.util.function.Supplier; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/27/15 - 11:44 AM + * + * @author Jeroen Meulemeester + */ +public abstract class ObserverTest extends StdOutTest { + + /** + * The observer instance factory + */ + private final Supplier factory; + + /** + * The weather type currently tested + */ + private final WeatherType weather; + + /** + * The expected response from the observer + */ + private final String response; + + /** + * Create a new test instance using the given parameters + * + * @param weather The weather currently being tested + * @param response The expected response from the observer + * @param factory The factory, used to create an instance of the tested observer + */ + ObserverTest(final WeatherType weather, final String response, final Supplier factory) { + this.weather = weather; + this.response = response; + this.factory = factory; + } + + /** + * Verify if the weather has the expected influence on the observer + */ + @Test + public void testObserver() { + final O observer = this.factory.get(); + verifyZeroInteractions(getStdOutMock()); + + observer.update(null, this.weather); + verify(getStdOutMock()).println(this.response); + verifyNoMoreInteractions(getStdOutMock()); + } + +} \ No newline at end of file diff --git a/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java b/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java new file mode 100644 index 000000000..508380970 --- /dev/null +++ b/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java @@ -0,0 +1,39 @@ +package com.iluwatar.observer.generic; + +import com.iluwatar.observer.WeatherType; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Date: 12/27/15 - 12:07 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class OrcsTest extends ObserverTest { + + @Parameterized.Parameters + public static Collection data() { + final ArrayList testData = new ArrayList<>(); + testData.add(new Object[]{WeatherType.SUNNY, "The sun hurts the orcs' eyes."}); + testData.add(new Object[]{WeatherType.RAINY, "The orcs are dripping wet."}); + testData.add(new Object[]{WeatherType.WINDY, "The orc smell almost vanishes in the wind."}); + testData.add(new Object[]{WeatherType.COLD, "The orcs are freezing cold."}); + return testData; + } + + /** + * Create a new test with the given weather and expected response + * + * @param weather The weather that should be unleashed on the observer + * @param response The expected response from the observer + */ + public OrcsTest(final WeatherType weather, final String response) { + super(weather, response, GOrcs::new); + } + +} diff --git a/poison-pill/index.md b/poison-pill/index.md index cd60e1a68..90dff3c36 100644 --- a/poison-pill/index.md +++ b/poison-pill/index.md @@ -4,7 +4,9 @@ title: Poison Pill folder: poison-pill permalink: /patterns/poison-pill/ categories: Other -tags: Java +tags: + - Java + - Difficulty-Intermediate --- **Intent:** Poison Pill is known predefined data item that allows to provide diff --git a/poison-pill/pom.xml b/poison-pill/pom.xml index 340445414..c416fe0be 100644 --- a/poison-pill/pom.xml +++ b/poison-pill/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT poison-pill @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java index ff06d7d5b..4530ef953 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java @@ -7,20 +7,23 @@ import com.iluwatar.poison.pill.Message.Headers; */ public class Consumer { - private final MQSubscribePoint queue; + private final MqSubscribePoint queue; private final String name; - public Consumer(String name, MQSubscribePoint queue) { + public Consumer(String name, MqSubscribePoint queue) { this.name = name; this.queue = queue; } + /** + * Consume message + */ public void consume() { while (true) { Message msg; try { msg = queue.take(); - if (msg == Message.POISON_PILL) { + if (Message.POISON_PILL.equals(msg)) { System.out.println(String.format("Consumer %s receive request to terminate.", name)); break; } diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java index b0fc6d6dc..4f253376e 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java @@ -8,7 +8,7 @@ import java.util.Map; */ public interface Message { - public static final Message POISON_PILL = new Message() { + Message POISON_PILL = new Message() { @Override public void addHeader(Headers header, String value) { @@ -45,13 +45,13 @@ public interface Message { DATE, SENDER } - public void addHeader(Headers header, String value); + void addHeader(Headers header, String value); - public String getHeader(Headers header); + String getHeader(Headers header); - public Map getHeaders(); + Map getHeaders(); - public void setBody(String body); + void setBody(String body); - public String getBody(); + String getBody(); } diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java index 99231ab51..aa0d3699d 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java @@ -3,6 +3,6 @@ package com.iluwatar.poison.pill; /** * Represents abstraction of channel (or pipe) that bounds {@link Producer} and {@link Consumer} */ -public interface MessageQueue extends MQPublishPoint, MQSubscribePoint { +public interface MessageQueue extends MqPublishPoint, MqSubscribePoint { } diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/MQPublishPoint.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqPublishPoint.java similarity index 50% rename from poison-pill/src/main/java/com/iluwatar/poison/pill/MQPublishPoint.java rename to poison-pill/src/main/java/com/iluwatar/poison/pill/MqPublishPoint.java index a266d9f4d..9a9558e4c 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/MQPublishPoint.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqPublishPoint.java @@ -3,7 +3,7 @@ package com.iluwatar.poison.pill; /** * Endpoint to publish {@link Message} to queue */ -public interface MQPublishPoint { +public interface MqPublishPoint { - public void put(Message msg) throws InterruptedException; + void put(Message msg) throws InterruptedException; } diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/MQSubscribePoint.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqSubscribePoint.java similarity index 52% rename from poison-pill/src/main/java/com/iluwatar/poison/pill/MQSubscribePoint.java rename to poison-pill/src/main/java/com/iluwatar/poison/pill/MqSubscribePoint.java index c093b1412..03623edbc 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/MQSubscribePoint.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqSubscribePoint.java @@ -3,7 +3,7 @@ package com.iluwatar.poison.pill; /** * Endpoint to retrieve {@link Message} from queue */ -public interface MQSubscribePoint { +public interface MqSubscribePoint { - public Message take() throws InterruptedException; + Message take() throws InterruptedException; } diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java index ecde39e35..5405de869 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java @@ -10,16 +10,22 @@ import com.iluwatar.poison.pill.Message.Headers; */ public class Producer { - private final MQPublishPoint queue; + private final MqPublishPoint queue; private final String name; private boolean isStopped; - public Producer(String name, MQPublishPoint queue) { + /** + * Constructor + */ + public Producer(String name, MqPublishPoint queue) { this.name = name; this.queue = queue; this.isStopped = false; } + /** + * Send message to queue + */ public void send(String body) { if (isStopped) { throw new IllegalStateException(String.format( @@ -38,6 +44,9 @@ public class Producer { } } + /** + * Stop system by sending poison pill + */ public void stop() { isStopped = true; try { diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java index c9b619016..20861ded1 100644 --- a/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.poison.pill; import org.junit.Test; -import com.iluwatar.poison.pill.App; - /** * * Application test diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java new file mode 100644 index 000000000..c152fbbd2 --- /dev/null +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java @@ -0,0 +1,56 @@ +package com.iluwatar.poison.pill; + +import org.junit.Test; +import org.mockito.InOrder; + +import java.time.LocalDateTime; + +import static org.mockito.Mockito.inOrder; + +/** + * Date: 12/27/15 - 9:45 PM + * + * @author Jeroen Meulemeester + */ +public class ConsumerTest extends StdOutTest { + + @Test + public void testConsume() throws Exception { + final Message[] messages = new Message[]{ + createMessage("you", "Hello!"), + createMessage("me", "Hi!"), + Message.POISON_PILL, + createMessage("late_for_the_party", "Hello? Anyone here?"), + }; + + final MessageQueue queue = new SimpleMessageQueue(messages.length); + for (final Message message : messages) { + queue.put(message); + } + + new Consumer("NSA", queue).consume(); + + final InOrder inOrder = inOrder(getStdOutMock()); + inOrder.verify(getStdOutMock()).println("Message [Hello!] from [you] received by [NSA]"); + inOrder.verify(getStdOutMock()).println("Message [Hi!] from [me] received by [NSA]"); + inOrder.verify(getStdOutMock()).println("Consumer NSA receive request to terminate."); + inOrder.verifyNoMoreInteractions(); + + } + + /** + * Create a new message from the given sender with the given message body + * + * @param sender The sender's name + * @param message The message body + * @return The message instance + */ + private static Message createMessage(final String sender, final String message) { + final SimpleMessage msg = new SimpleMessage(); + msg.addHeader(Message.Headers.SENDER, sender); + msg.addHeader(Message.Headers.DATE, LocalDateTime.now().toString()); + msg.setBody(message); + return msg; + } + +} \ No newline at end of file diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/PoisonMessageTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/PoisonMessageTest.java new file mode 100644 index 000000000..9fb733aad --- /dev/null +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/PoisonMessageTest.java @@ -0,0 +1,40 @@ +package com.iluwatar.poison.pill; + +import org.junit.Test; + +import static com.iluwatar.poison.pill.Message.Headers; +import static com.iluwatar.poison.pill.Message.POISON_PILL; + +/** + * Date: 12/27/15 - 10:30 PM + * + * @author Jeroen Meulemeester + */ +public class PoisonMessageTest { + + @Test(expected = UnsupportedOperationException.class) + public void testAddHeader() throws Exception { + POISON_PILL.addHeader(Headers.SENDER, "sender"); + } + + @Test(expected = UnsupportedOperationException.class) + public void testGetHeader() throws Exception { + POISON_PILL.getHeader(Headers.SENDER); + } + + @Test(expected = UnsupportedOperationException.class) + public void testGetHeaders() throws Exception { + POISON_PILL.getHeaders(); + } + + @Test(expected = UnsupportedOperationException.class) + public void testSetBody() throws Exception { + POISON_PILL.setBody("Test message."); + } + + @Test(expected = UnsupportedOperationException.class) + public void testGetBody() throws Exception { + POISON_PILL.getBody(); + } + +} diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/ProducerTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/ProducerTest.java new file mode 100644 index 000000000..103020e4a --- /dev/null +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/ProducerTest.java @@ -0,0 +1,64 @@ +package com.iluwatar.poison.pill; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/27/15 - 10:32 PM + * + * @author Jeroen Meulemeester + */ +public class ProducerTest { + + @Test + public void testSend() throws Exception { + final MqPublishPoint publishPoint = mock(MqPublishPoint.class); + final Producer producer = new Producer("producer", publishPoint); + verifyZeroInteractions(publishPoint); + + producer.send("Hello!"); + + final ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + verify(publishPoint).put(messageCaptor.capture()); + + final Message message = messageCaptor.getValue(); + assertNotNull(message); + assertEquals("producer", message.getHeader(Message.Headers.SENDER)); + assertNotNull(message.getHeader(Message.Headers.DATE)); + assertEquals("Hello!", message.getBody()); + + verifyNoMoreInteractions(publishPoint); + } + + @Test + public void testStop() throws Exception { + final MqPublishPoint publishPoint = mock(MqPublishPoint.class); + final Producer producer = new Producer("producer", publishPoint); + verifyZeroInteractions(publishPoint); + + producer.stop(); + verify(publishPoint).put(eq(Message.POISON_PILL)); + + try { + producer.send("Hello!"); + fail("Expected 'IllegalStateException' at this point, since the producer has stopped!"); + } catch (IllegalStateException e) { + assertNotNull(e); + assertNotNull(e.getMessage()); + assertEquals("Producer Hello! was stopped and fail to deliver requested message [producer].", + e.getMessage()); + } + + verifyNoMoreInteractions(publishPoint); + } + +} diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/SimpleMessageTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/SimpleMessageTest.java new file mode 100644 index 000000000..f5c348e2b --- /dev/null +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/SimpleMessageTest.java @@ -0,0 +1,40 @@ +package com.iluwatar.poison.pill; + +import org.junit.Test; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/27/15 - 10:25 PM + * + * @author Jeroen Meulemeester + */ +public class SimpleMessageTest { + + @Test + public void testGetHeaders() { + final SimpleMessage message = new SimpleMessage(); + assertNotNull(message.getHeaders()); + assertTrue(message.getHeaders().isEmpty()); + + final String senderName = "test"; + message.addHeader(Message.Headers.SENDER, senderName); + assertNotNull(message.getHeaders()); + assertFalse(message.getHeaders().isEmpty()); + assertEquals(senderName, message.getHeaders().get(Message.Headers.SENDER)); + } + + @Test(expected = UnsupportedOperationException.class) + public void testUnModifiableHeaders() { + final SimpleMessage message = new SimpleMessage(); + final Map headers = message.getHeaders(); + headers.put(Message.Headers.SENDER, "test"); + } + + +} \ No newline at end of file diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/StdOutTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/StdOutTest.java new file mode 100644 index 000000000..9c533b5c2 --- /dev/null +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.poison.pill; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to std-out using {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/pom.xml b/pom.xml index 9e4c39bf9..c929f2945 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,13 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT pom UTF-8 5.0.1.Final + 4.1.7.RELEASE 1.9.0.RELEASE 1.4.188 4.12 @@ -20,6 +21,8 @@ 1.4 2.15.3 1.2.17 + 18.0 + 1.14.0 abstract-factory @@ -86,6 +89,7 @@ reactor caching publish-subscribe + delegation event-driven-architecture @@ -102,6 +106,11 @@ ${hibernate.version} + org.springframework + spring-test + ${spring.version} + + org.springframework.data spring-data-jpa ${spring-data.version} @@ -143,6 +152,17 @@ log4j ${log4j.version} + + com.google.guava + guava + ${guava.version} + + + com.github.stefanbirkner + system-rules + ${systemrules.version} + test + @@ -233,7 +253,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 2.15 + 2.17 validate @@ -243,9 +263,11 @@ validate checkstyle.xml + checkstyle-suppressions.xml UTF-8 true true + true @@ -285,7 +307,37 @@ -Xmx1024M ${argLine} + + org.apache.maven.plugins + maven-pmd-plugin + 3.6 + + true + 5 + true + + + + + check + + + exclude-pmd.properties + + + + + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.6 + + + + diff --git a/private-class-data/index.md b/private-class-data/index.md index 83c95d308..4c09df0d0 100644 --- a/private-class-data/index.md +++ b/private-class-data/index.md @@ -4,7 +4,10 @@ title: Private Class Data folder: private-class-data permalink: /patterns/private-class-data/ categories: Other -tags: Java +tags: + - Java + - Difficulty-Beginner + - Idiom --- **Intent:** Private Class Data design pattern seeks to reduce exposure of diff --git a/private-class-data/pom.xml b/private-class-data/pom.xml index 362e061f1..e783ac315 100644 --- a/private-class-data/pom.xml +++ b/private-class-data/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT private-class-data @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java b/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java index 599a80407..849c2413c 100644 --- a/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java +++ b/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java @@ -13,6 +13,9 @@ public class ImmutableStew { data = new StewData(numPotatoes, numCarrots, numMeat, numPeppers); } + /** + * Mix the stew + */ public void mix() { System.out.println(String.format( "Mixing the immutable stew we find: %d potatoes, %d carrots, %d meat and %d peppers", diff --git a/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java b/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java index 9deb32e16..2efd0b4ee 100644 --- a/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java +++ b/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java @@ -12,6 +12,9 @@ public class Stew { private int numMeat; private int numPeppers; + /** + * Constructor + */ public Stew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { this.numPotatoes = numPotatoes; this.numCarrots = numCarrots; @@ -19,12 +22,18 @@ public class Stew { this.numPeppers = numPeppers; } + /** + * Mix the stew + */ public void mix() { System.out.println(String.format( "Mixing the stew we find: %d potatoes, %d carrots, %d meat and %d peppers", numPotatoes, numCarrots, numMeat, numPeppers)); } + /** + * Taste the stew + */ public void taste() { System.out.println("Tasting the stew"); if (numPotatoes > 0) { diff --git a/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java b/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java index 23d54ccf4..0bd62ada3 100644 --- a/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java +++ b/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java @@ -12,6 +12,9 @@ public class StewData { private int numMeat; private int numPeppers; + /** + * Constructor + */ public StewData(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { this.numPotatoes = numPotatoes; this.numCarrots = numCarrots; diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java index 6623a43ad..5fd53d99f 100644 --- a/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.privateclassdata; import org.junit.Test; -import com.iluwatar.privateclassdata.App; - /** * * Application test diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/ImmutableStewTest.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/ImmutableStewTest.java new file mode 100644 index 000000000..da5335b0f --- /dev/null +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/ImmutableStewTest.java @@ -0,0 +1,52 @@ +package com.iluwatar.privateclassdata; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.verify; + +/** + * Date: 12/27/15 - 10:46 PM + * + * @author Jeroen Meulemeester + */ +public class ImmutableStewTest extends StdOutTest { + + /** + * Verify if mixing the stew doesn't change the internal state + */ + @Test + public void testMix() { + final Stew stew = new Stew(1, 2, 3, 4); + final String message = "Mixing the stew we find: 1 potatoes, 2 carrots, 3 meat and 4 peppers"; + + final InOrder inOrder = inOrder(getStdOutMock()); + for (int i = 0; i < 20; i++) { + stew.mix(); + inOrder.verify(getStdOutMock()).println(message); + } + + inOrder.verifyNoMoreInteractions(); + } + + /** + * Verify if tasting the stew actually removes one of each ingredient + */ + @Test + public void testDrink() { + final Stew stew = new Stew(1, 2, 3, 4); + stew.mix(); + + verify(getStdOutMock()) + .println("Mixing the stew we find: 1 potatoes, 2 carrots, 3 meat and 4 peppers"); + + stew.taste(); + verify(getStdOutMock()).println("Tasting the stew"); + + stew.mix(); + verify(getStdOutMock()) + .println("Mixing the stew we find: 0 potatoes, 1 carrots, 2 meat and 3 peppers"); + + } +} \ No newline at end of file diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/StdOutTest.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/StdOutTest.java new file mode 100644 index 000000000..91904c31c --- /dev/null +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.privateclassdata; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to std-out using {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/StewTest.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/StewTest.java new file mode 100644 index 000000000..8e0452fab --- /dev/null +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/StewTest.java @@ -0,0 +1,33 @@ +package com.iluwatar.privateclassdata; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; + +/** + * Date: 12/27/15 - 10:46 PM + * + * @author Jeroen Meulemeester + */ +public class StewTest extends StdOutTest { + + /** + * Verify if mixing the stew doesn't change the internal state + */ + @Test + public void testMix() { + final ImmutableStew stew = new ImmutableStew(1, 2, 3, 4); + final String expectedMessage = "Mixing the immutable stew we find: 1 potatoes, " + + "2 carrots, 3 meat and 4 peppers"; + + final InOrder inOrder = inOrder(getStdOutMock()); + for (int i = 0; i < 20; i++) { + stew.mix(); + inOrder.verify(getStdOutMock()).println(expectedMessage); + } + + inOrder.verifyNoMoreInteractions(); + } + +} \ No newline at end of file diff --git a/producer-consumer/index.md b/producer-consumer/index.md index 58dc45e0d..124c98a2a 100644 --- a/producer-consumer/index.md +++ b/producer-consumer/index.md @@ -3,15 +3,16 @@ layout: pattern title: Producer Consumer folder: producer-consumer permalink: /patterns/producer-consumer/ -categories: Other -tags: Java +categories: Concurrency +tags: + - Java + - Difficulty-Intermediate + - I/O --- -**Intent:** Producer Consumer Design pattern is a classic concurrency or threading pattern which reduces +**Intent:** Producer Consumer Design pattern is a classic concurrency pattern which reduces coupling between Producer and Consumer by separating Identification of work with Execution of - Work.. - - + Work. ![alt text](./etc/producer-consumer.png "Producer Consumer") @@ -19,4 +20,3 @@ tags: Java * decouple system by separate work in two process produce and consume. * addresses the issue of different timing require to produce work or consuming work - diff --git a/producer-consumer/pom.xml b/producer-consumer/pom.xml index 571272102..fae5e36d9 100644 --- a/producer-consumer/pom.xml +++ b/producer-consumer/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT producer-consumer @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java index 50d94d92f..63cae9413 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java @@ -5,14 +5,12 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** - * Producer Consumer Design pattern is a classic concurrency or threading pattern which reduces - * coupling between Producer and Consumer by separating Identification of work with Execution of - * Work. + * Producer Consumer Design pattern is a classic concurrency or threading pattern which reduces coupling between + * Producer and Consumer by separating Identification of work with Execution of Work. *

- * In producer consumer design pattern a shared queue is used to control the flow and this - * separation allows you to code producer and consumer separately. It also addresses the issue of - * different timing require to produce item or consuming item. by using producer consumer pattern - * both Producer and Consumer Thread can work with different speed. + * In producer consumer design pattern a shared queue is used to control the flow and this separation allows you to code + * producer and consumer separately. It also addresses the issue of different timing require to produce item or + * consuming item. by using producer consumer pattern both Producer and Consumer Thread can work with different speed. * */ public class App { @@ -20,7 +18,8 @@ public class App { /** * Program entry point * - * @param args command line args + * @param args + * command line args */ public static void main(String[] args) { @@ -35,7 +34,7 @@ public class App { producer.produce(); } }); - }; + } for (int i = 0; i < 3; i++) { final Consumer consumer = new Consumer("Consumer_" + i, queue); diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java index 8bb3b75b6..ff63ab41b 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java @@ -14,6 +14,9 @@ public class Consumer { this.queue = queue; } + /** + * Consume item from the queue + */ public void consume() throws InterruptedException { Item item = queue.take(); diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java index 40e71c607..8b122a5fc 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java @@ -19,6 +19,9 @@ public class Producer { this.queue = queue; } + /** + * Put item in the queue + */ public void produce() throws InterruptedException { Item item = new Item(name, itemId++); diff --git a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java index cdd9ad046..e82e36da1 100644 --- a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java +++ b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.producer.consumer; import org.junit.Test; -import com.iluwatar.producer.consumer.App; - /** * * Application test diff --git a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ConsumerTest.java b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ConsumerTest.java new file mode 100644 index 000000000..4ff203d42 --- /dev/null +++ b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ConsumerTest.java @@ -0,0 +1,39 @@ +package com.iluwatar.producer.consumer; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; + +/** + * Date: 12/27/15 - 11:01 PM + * + * @author Jeroen Meulemeester + */ +public class ConsumerTest extends StdOutTest { + + private static final int ITEM_COUNT = 5; + + @Test + public void testConsume() throws Exception { + final ItemQueue queue = spy(new ItemQueue()); + for (int id = 0; id < ITEM_COUNT; id++) { + queue.put(new Item("producer", id)); + } + + reset(queue); // Don't count the preparation above as interactions with the queue + final Consumer consumer = new Consumer("consumer", queue); + + final InOrder inOrder = inOrder(getStdOutMock()); + for (int id = 0; id < ITEM_COUNT; id++) { + consumer.consume(); + inOrder.verify(getStdOutMock()) + .println("Consumer [consumer] consume item [" + id + "] produced by [producer]"); + } + + inOrder.verifyNoMoreInteractions(); + } + +} diff --git a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ProducerTest.java b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ProducerTest.java new file mode 100644 index 000000000..0605879dd --- /dev/null +++ b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ProducerTest.java @@ -0,0 +1,28 @@ +package com.iluwatar.producer.consumer; + +import org.junit.Test; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/28/15 - 12:12 AM + * + * @author Jeroen Meulemeester + */ +public class ProducerTest { + + @Test(timeout = 6000) + public void testProduce() throws Exception { + final ItemQueue queue = mock(ItemQueue.class); + final Producer producer = new Producer("producer", queue); + + producer.produce(); + verify(queue).put(any(Item.class)); + + verifyNoMoreInteractions(queue); + } + +} \ No newline at end of file diff --git a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/StdOutTest.java b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/StdOutTest.java new file mode 100644 index 000000000..85d8fe6c0 --- /dev/null +++ b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.producer.consumer; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to std-out using {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/property/index.md b/property/index.md index 1c5b28db6..9949239c1 100644 --- a/property/index.md +++ b/property/index.md @@ -4,7 +4,9 @@ title: Property folder: property permalink: /patterns/property/ categories: Creational -tags: Java +tags: + - Java + - Difficulty-Beginner --- **Intent:** Create hierarchy of objects and new objects using already existing diff --git a/property/pom.xml b/property/pom.xml index b6d10bdb0..0d1ff2016 100644 --- a/property/pom.xml +++ b/property/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT property diff --git a/property/src/main/java/com/iluwatar/property/Character.java b/property/src/main/java/com/iluwatar/property/Character.java index 10b8f495d..50e564623 100644 --- a/property/src/main/java/com/iluwatar/property/Character.java +++ b/property/src/main/java/com/iluwatar/property/Character.java @@ -18,6 +18,9 @@ public class Character implements Prototype { private String name; private Type type; + /** + * Constructor + */ public Character() { this.prototype = new Prototype() { // Null-value object @Override @@ -43,6 +46,9 @@ public class Character implements Prototype { this.prototype = prototype; } + /** + * Constructor + */ public Character(String name, Character prototype) { this.name = name; this.type = prototype.type; diff --git a/property/src/main/java/com/iluwatar/property/Prototype.java b/property/src/main/java/com/iluwatar/property/Prototype.java index 13b4c8608..33e2d66d6 100644 --- a/property/src/main/java/com/iluwatar/property/Prototype.java +++ b/property/src/main/java/com/iluwatar/property/Prototype.java @@ -5,11 +5,11 @@ package com.iluwatar.property; */ public interface Prototype { - public Integer get(Stats stat); + Integer get(Stats stat); - public boolean has(Stats stat); + boolean has(Stats stat); - public void set(Stats stat, Integer val); + void set(Stats stat, Integer val); - public void remove(Stats stat); + void remove(Stats stat); } diff --git a/property/src/test/java/com/iluwatar/property/AppTest.java b/property/src/test/java/com/iluwatar/property/AppTest.java index 75be2f649..bfa48ffab 100644 --- a/property/src/test/java/com/iluwatar/property/AppTest.java +++ b/property/src/test/java/com/iluwatar/property/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.property; import org.junit.Test; -import com.iluwatar.property.App; - /** * * Application test diff --git a/property/src/test/java/com/iluwatar/property/CharacterTest.java b/property/src/test/java/com/iluwatar/property/CharacterTest.java new file mode 100644 index 000000000..6d9a7a14b --- /dev/null +++ b/property/src/test/java/com/iluwatar/property/CharacterTest.java @@ -0,0 +1,103 @@ +package com.iluwatar.property; + +import org.junit.Test; + +import static com.iluwatar.property.Character.Type; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/28/15 - 7:46 PM + * + * @author Jeroen Meulemeester + */ +public class CharacterTest { + + @Test + public void testPrototypeStats() throws Exception { + final Character prototype = new Character(); + + for (final Stats stat : Stats.values()) { + assertFalse(prototype.has(stat)); + assertNull(prototype.get(stat)); + + final Integer expectedValue = stat.ordinal(); + prototype.set(stat, expectedValue); + assertTrue(prototype.has(stat)); + assertEquals(expectedValue, prototype.get(stat)); + + prototype.remove(stat); + assertFalse(prototype.has(stat)); + assertNull(prototype.get(stat)); + } + + } + + @Test + public void testCharacterStats() throws Exception { + final Character prototype = new Character(); + for (final Stats stat : Stats.values()) { + prototype.set(stat, stat.ordinal()); + } + + final Character mage = new Character(Type.MAGE, prototype); + for (final Stats stat : Stats.values()) { + final Integer expectedValue = stat.ordinal(); + assertTrue(mage.has(stat)); + assertEquals(expectedValue, mage.get(stat)); + } + } + + @Test + public void testToString() throws Exception { + final Character prototype = new Character(); + prototype.set(Stats.ARMOR, 1); + prototype.set(Stats.AGILITY, 2); + prototype.set(Stats.INTELLECT, 3); + assertEquals("Stats:\n - AGILITY:2\n - ARMOR:1\n - INTELLECT:3\n", prototype.toString()); + + final Character stupid = new Character(Type.ROGUE, prototype); + stupid.remove(Stats.INTELLECT); + assertEquals("Character type: ROGUE\nStats:\n - AGILITY:2\n - ARMOR:1\n", stupid.toString()); + + final Character weak = new Character("weak", prototype); + weak.remove(Stats.ARMOR); + assertEquals("Player: weak\nStats:\n - AGILITY:2\n - INTELLECT:3\n", weak.toString()); + + } + + @Test + public void testName() throws Exception { + final Character prototype = new Character(); + prototype.set(Stats.ARMOR, 1); + prototype.set(Stats.INTELLECT, 2); + assertNull(prototype.name()); + + final Character stupid = new Character(Type.ROGUE, prototype); + stupid.remove(Stats.INTELLECT); + assertNull(stupid.name()); + + final Character weak = new Character("weak", prototype); + weak.remove(Stats.ARMOR); + assertEquals("weak", weak.name()); + } + + @Test + public void testType() throws Exception { + final Character prototype = new Character(); + prototype.set(Stats.ARMOR, 1); + prototype.set(Stats.INTELLECT, 2); + assertNull(prototype.type()); + + final Character stupid = new Character(Type.ROGUE, prototype); + stupid.remove(Stats.INTELLECT); + assertEquals(Type.ROGUE, stupid.type()); + + final Character weak = new Character("weak", prototype); + weak.remove(Stats.ARMOR); + assertNull(weak.type()); + } + +} \ No newline at end of file diff --git a/prototype/index.md b/prototype/index.md index 9d108ff06..457c2f46a 100644 --- a/prototype/index.md +++ b/prototype/index.md @@ -7,6 +7,7 @@ categories: Creational tags: - Java - Gang Of Four + - Difficulty-Beginner --- **Intent:** Specify the kinds of objects to create using a prototypical diff --git a/prototype/pom.xml b/prototype/pom.xml index c08e79558..411517d2e 100644 --- a/prototype/pom.xml +++ b/prototype/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT prototype @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java b/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java index f5cb8bdaf..679882097 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java @@ -9,11 +9,9 @@ public class ElfBeast extends Beast { public ElfBeast() {} - public ElfBeast(ElfBeast beast) {} - @Override public Beast clone() throws CloneNotSupportedException { - return new ElfBeast(this); + return new ElfBeast(); } @Override diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java b/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java index c801e4007..42ce9d530 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java @@ -9,11 +9,9 @@ public class ElfMage extends Mage { public ElfMage() {} - public ElfMage(ElfMage mage) {} - @Override public Mage clone() throws CloneNotSupportedException { - return new ElfMage(this); + return new ElfMage(); } @Override diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java b/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java index 8b5167b0e..1cba6943c 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java @@ -9,11 +9,9 @@ public class ElfWarlord extends Warlord { public ElfWarlord() {} - public ElfWarlord(ElfWarlord warlord) {} - @Override public Warlord clone() throws CloneNotSupportedException { - return new ElfWarlord(this); + return new ElfWarlord(); } @Override diff --git a/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java b/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java index 4c5a60bcd..85792104d 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java +++ b/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java @@ -11,12 +11,18 @@ public class HeroFactoryImpl implements HeroFactory { private Warlord warlord; private Beast beast; + /** + * Constructor + */ public HeroFactoryImpl(Mage mage, Warlord warlord, Beast beast) { this.mage = mage; this.warlord = warlord; this.beast = beast; } + /** + * Create mage + */ public Mage createMage() { try { return mage.clone(); @@ -25,6 +31,9 @@ public class HeroFactoryImpl implements HeroFactory { } } + /** + * Create warlord + */ public Warlord createWarlord() { try { return warlord.clone(); @@ -33,6 +42,9 @@ public class HeroFactoryImpl implements HeroFactory { } } + /** + * Create beast + */ public Beast createBeast() { try { return beast.clone(); diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java b/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java index 50a6b5ae2..a45afb767 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java @@ -9,11 +9,9 @@ public class OrcBeast extends Beast { public OrcBeast() {} - public OrcBeast(OrcBeast beast) {} - @Override public Beast clone() throws CloneNotSupportedException { - return new OrcBeast(this); + return new OrcBeast(); } @Override diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java b/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java index f27d12519..47a33379b 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java @@ -9,11 +9,9 @@ public class OrcMage extends Mage { public OrcMage() {} - public OrcMage(OrcMage mage) {} - @Override public Mage clone() throws CloneNotSupportedException { - return new OrcMage(this); + return new OrcMage(); } @Override diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java b/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java index d21816d8e..40ab91113 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java @@ -9,11 +9,9 @@ public class OrcWarlord extends Warlord { public OrcWarlord() {} - public OrcWarlord(OrcWarlord warlord) {} - @Override public Warlord clone() throws CloneNotSupportedException { - return new OrcWarlord(this); + return new OrcWarlord(); } @Override diff --git a/prototype/src/test/java/com/iluwatar/prototype/AppTest.java b/prototype/src/test/java/com/iluwatar/prototype/AppTest.java index c2b8ea4ff..772a88a03 100644 --- a/prototype/src/test/java/com/iluwatar/prototype/AppTest.java +++ b/prototype/src/test/java/com/iluwatar/prototype/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.prototype; import org.junit.Test; -import com.iluwatar.prototype.App; - /** * * Application test diff --git a/prototype/src/test/java/com/iluwatar/prototype/HeroFactoryImplTest.java b/prototype/src/test/java/com/iluwatar/prototype/HeroFactoryImplTest.java new file mode 100644 index 000000000..e237b43b7 --- /dev/null +++ b/prototype/src/test/java/com/iluwatar/prototype/HeroFactoryImplTest.java @@ -0,0 +1,39 @@ +package com.iluwatar.prototype; + +import org.junit.Test; + +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +/** + * Date: 12/28/15 - 8:34 PM + * + * @author Jeroen Meulemeester + */ +public class HeroFactoryImplTest { + + @Test + public void testFactory() throws Exception { + final Mage mage = mock(Mage.class); + final Warlord warlord = mock(Warlord.class); + final Beast beast = mock(Beast.class); + + when(mage.clone()).thenThrow(CloneNotSupportedException.class); + when(warlord.clone()).thenThrow(CloneNotSupportedException.class); + when(beast.clone()).thenThrow(CloneNotSupportedException.class); + + final HeroFactoryImpl factory = new HeroFactoryImpl(mage, warlord, beast); + assertNull(factory.createMage()); + assertNull(factory.createWarlord()); + assertNull(factory.createBeast()); + + verify(mage).clone(); + verify(warlord).clone(); + verify(beast).clone(); + verifyNoMoreInteractions(mage, warlord, beast); + } + +} \ No newline at end of file diff --git a/prototype/src/test/java/com/iluwatar/prototype/PrototypeTest.java b/prototype/src/test/java/com/iluwatar/prototype/PrototypeTest.java new file mode 100644 index 000000000..3e3d8f88b --- /dev/null +++ b/prototype/src/test/java/com/iluwatar/prototype/PrototypeTest.java @@ -0,0 +1,66 @@ +package com.iluwatar.prototype; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; + +/** + * Date: 12/28/15 - 8:45 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class PrototypeTest

{ + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList( + new Object[]{new OrcBeast(), "Orcish wolf"}, + new Object[]{new OrcMage(), "Orcish mage"}, + new Object[]{new OrcWarlord(), "Orcish warlord"}, + new Object[]{new ElfBeast(), "Elven eagle"}, + new Object[]{new ElfMage(), "Elven mage"}, + new Object[]{new ElfWarlord(), "Elven warlord"} + ); + } + + /** + * The tested prototype instance + */ + private final Prototype testedPrototype; + + /** + * The expected {@link Prototype#toString()} value + */ + private final String expectedToString; + + /** + * Create a new test instance, using the given test object and expected value + * + * @param testedPrototype The tested prototype instance + * @param expectedToString The expected {@link Prototype#toString()} value + */ + public PrototypeTest(final Prototype testedPrototype, final String expectedToString) { + this.expectedToString = expectedToString; + this.testedPrototype = testedPrototype; + } + + @Test + public void testPrototype() throws Exception { + assertEquals(this.expectedToString, this.testedPrototype.toString()); + + final Object clone = this.testedPrototype.clone(); + assertNotNull(clone); + assertNotSame(clone, this.testedPrototype); + assertSame(this.testedPrototype.getClass(), clone.getClass()); + } + +} diff --git a/proxy/index.md b/proxy/index.md index baa759600..2f16527a8 100644 --- a/proxy/index.md +++ b/proxy/index.md @@ -7,7 +7,7 @@ categories: Structural tags: - Java - Gang Of Four - - Difficulty-Intermediate + - Difficulty-Beginner --- **Also known as:** Surrogate diff --git a/proxy/pom.xml b/proxy/pom.xml index 0bb88bb79..139934c13 100644 --- a/proxy/pom.xml +++ b/proxy/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT proxy @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/proxy/src/main/java/com/iluwatar/proxy/App.java b/proxy/src/main/java/com/iluwatar/proxy/App.java index 25a903e41..837424f28 100644 --- a/proxy/src/main/java/com/iluwatar/proxy/App.java +++ b/proxy/src/main/java/com/iluwatar/proxy/App.java @@ -18,6 +18,9 @@ package com.iluwatar.proxy; */ public class App { + /** + * Program entry point + */ public static void main(String[] args) { WizardTowerProxy tower = new WizardTowerProxy(); diff --git a/proxy/src/test/java/com/iluwatar/proxy/AppTest.java b/proxy/src/test/java/com/iluwatar/proxy/AppTest.java index a68629646..0485dabb6 100644 --- a/proxy/src/test/java/com/iluwatar/proxy/AppTest.java +++ b/proxy/src/test/java/com/iluwatar/proxy/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.proxy; import org.junit.Test; -import com.iluwatar.proxy.App; - /** * * Application test diff --git a/proxy/src/test/java/com/iluwatar/proxy/StdOutTest.java b/proxy/src/test/java/com/iluwatar/proxy/StdOutTest.java new file mode 100644 index 000000000..a145b7b80 --- /dev/null +++ b/proxy/src/test/java/com/iluwatar/proxy/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.proxy; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to std-out using {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/proxy/src/test/java/com/iluwatar/proxy/WizardTest.java b/proxy/src/test/java/com/iluwatar/proxy/WizardTest.java new file mode 100644 index 000000000..c1b9e6fed --- /dev/null +++ b/proxy/src/test/java/com/iluwatar/proxy/WizardTest.java @@ -0,0 +1,22 @@ +package com.iluwatar.proxy; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Date: 12/28/15 - 9:02 PM + * + * @author Jeroen Meulemeester + */ +public class WizardTest { + + @Test + public void testToString() throws Exception { + final String[] wizardNames = {"Gandalf", "Dumbledore", "Oz", "Merlin"}; + for (final String name : wizardNames) { + assertEquals(name, new Wizard(name).toString()); + } + } + +} \ No newline at end of file diff --git a/proxy/src/test/java/com/iluwatar/proxy/WizardTowerProxyTest.java b/proxy/src/test/java/com/iluwatar/proxy/WizardTowerProxyTest.java new file mode 100644 index 000000000..dcde88f8c --- /dev/null +++ b/proxy/src/test/java/com/iluwatar/proxy/WizardTowerProxyTest.java @@ -0,0 +1,38 @@ +package com.iluwatar.proxy; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; + +/** + * Date: 12/28/15 - 9:18 PM + * + * @author Jeroen Meulemeester + */ +public class WizardTowerProxyTest extends StdOutTest { + + @Test + public void testEnter() throws Exception { + final Wizard[] wizards = new Wizard[]{ + new Wizard("Gandalf"), + new Wizard("Dumbledore"), + new Wizard("Oz"), + new Wizard("Merlin") + }; + + final WizardTowerProxy tower = new WizardTowerProxy(); + for (final Wizard wizard : wizards) { + tower.enter(wizard); + } + + final InOrder inOrder = inOrder(getStdOutMock()); + inOrder.verify(getStdOutMock()).println("Gandalf enters the tower."); + inOrder.verify(getStdOutMock()).println("Dumbledore enters the tower."); + inOrder.verify(getStdOutMock()).println("Oz enters the tower."); + inOrder.verify(getStdOutMock()).println("Merlin is not allowed to enter!"); + inOrder.verifyNoMoreInteractions(); + + } + +} \ No newline at end of file diff --git a/proxy/src/test/java/com/iluwatar/proxy/WizardTowerTest.java b/proxy/src/test/java/com/iluwatar/proxy/WizardTowerTest.java new file mode 100644 index 000000000..007b92a33 --- /dev/null +++ b/proxy/src/test/java/com/iluwatar/proxy/WizardTowerTest.java @@ -0,0 +1,38 @@ +package com.iluwatar.proxy; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; + +/** + * Date: 12/28/15 - 9:18 PM + * + * @author Jeroen Meulemeester + */ +public class WizardTowerTest extends StdOutTest { + + @Test + public void testEnter() throws Exception { + final Wizard[] wizards = new Wizard[]{ + new Wizard("Gandalf"), + new Wizard("Dumbledore"), + new Wizard("Oz"), + new Wizard("Merlin") + }; + + final WizardTower tower = new WizardTower(); + for (final Wizard wizard : wizards) { + tower.enter(wizard); + } + + final InOrder inOrder = inOrder(getStdOutMock()); + inOrder.verify(getStdOutMock()).println("Gandalf enters the tower."); + inOrder.verify(getStdOutMock()).println("Dumbledore enters the tower."); + inOrder.verify(getStdOutMock()).println("Oz enters the tower."); + inOrder.verify(getStdOutMock()).println("Merlin enters the tower."); + inOrder.verifyNoMoreInteractions(); + + } + +} \ No newline at end of file diff --git a/publish-subscribe/index.md b/publish-subscribe/index.md index b91f22e3b..cd1ad3971 100644 --- a/publish-subscribe/index.md +++ b/publish-subscribe/index.md @@ -7,6 +7,7 @@ categories: Integration tags: - Java - EIP + - Camel --- **Intent:** Broadcast messages from sender to all the interested receivers. diff --git a/publish-subscribe/pom.xml b/publish-subscribe/pom.xml index 07d704719..fad968b19 100644 --- a/publish-subscribe/pom.xml +++ b/publish-subscribe/pom.xml @@ -4,7 +4,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT publish-subscribe diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/App.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/App.java index 30f982ed1..f80dd1ad1 100644 --- a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/App.java +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/App.java @@ -28,10 +28,6 @@ public class App { /** * Program entry point - * - * @param args - * command line args - * @throws Exception */ public static void main(String[] args) throws Exception { CamelContext context = new DefaultCamelContext(); diff --git a/reactor/index.md b/reactor/index.md index 6e20598d2..9071413d8 100644 --- a/reactor/index.md +++ b/reactor/index.md @@ -3,10 +3,11 @@ layout: pattern title: Reactor folder: reactor permalink: /patterns/reactor/ -categories: Architectural +categories: Concurrency tags: - Java - Difficulty-Expert + - I/O --- **Intent:** The Reactor design pattern handles service requests that are delivered concurrently to an application by one or more clients. The application can register specific handlers for processing which are called by reactor on specific events. Dispatching of event handlers is performed by an initiation dispatcher, which manages the registered event handlers. Demultiplexing of service requests is performed by a synchronous event demultiplexer. diff --git a/reactor/pom.xml b/reactor/pom.xml index c60e8cf98..c980d3b10 100644 --- a/reactor/pom.xml +++ b/reactor/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT reactor diff --git a/reactor/src/main/java/com/iluwatar/reactor/app/App.java b/reactor/src/main/java/com/iluwatar/reactor/app/App.java index 2c49d9001..d074c9b19 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/app/App.java +++ b/reactor/src/main/java/com/iluwatar/reactor/app/App.java @@ -80,8 +80,6 @@ public class App { /** * App entry. - * - * @throws IOException */ public static void main(String[] args) throws IOException { new App(new ThreadPoolDispatcher(2)).start(); diff --git a/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java b/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java index ee25b0be0..13cdd70e1 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java +++ b/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java @@ -37,10 +37,10 @@ public class AppClient { * @throws IOException if any I/O error occurs. */ public void start() throws IOException { - service.execute(new TCPLoggingClient("Client 1", 6666)); - service.execute(new TCPLoggingClient("Client 2", 6667)); - service.execute(new UDPLoggingClient("Client 3", 6668)); - service.execute(new UDPLoggingClient("Client 4", 6668)); + service.execute(new TcpLoggingClient("Client 1", 6666)); + service.execute(new TcpLoggingClient("Client 2", 6667)); + service.execute(new UdpLoggingClient("Client 3", 6668)); + service.execute(new UdpLoggingClient("Client 4", 6668)); } /** @@ -69,7 +69,7 @@ public class AppClient { /** * A logging client that sends requests to Reactor on TCP socket. */ - static class TCPLoggingClient implements Runnable { + static class TcpLoggingClient implements Runnable { private final int serverPort; private final String clientName; @@ -80,7 +80,7 @@ public class AppClient { * @param clientName the name of the client to be sent in logging requests. * @param port the port on which client will send logging requests. */ - public TCPLoggingClient(String clientName, int serverPort) { + public TcpLoggingClient(String clientName, int serverPort) { this.clientName = clientName; this.serverPort = serverPort; } @@ -118,7 +118,7 @@ public class AppClient { /** * A logging client that sends requests to Reactor on UDP socket. */ - static class UDPLoggingClient implements Runnable { + static class UdpLoggingClient implements Runnable { private final String clientName; private final InetSocketAddress remoteAddress; @@ -129,7 +129,7 @@ public class AppClient { * @param port the port on which client will send logging requests. * @throws UnknownHostException if localhost is unknown */ - public UDPLoggingClient(String clientName, int port) throws UnknownHostException { + public UdpLoggingClient(String clientName, int port) throws UnknownHostException { this.clientName = clientName; this.remoteAddress = new InetSocketAddress(InetAddress.getLocalHost(), port); } diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java b/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java index df08426d0..cd1318c89 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java @@ -131,6 +131,7 @@ public abstract class AbstractNioChannel { * channel.write(buffer, key); * } * + * * * @param data the data to be written on underlying channel. * @param key the key which is writable. diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java b/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java index 16c13e5f9..271a6975d 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java @@ -14,40 +14,41 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** - * This class acts as Synchronous Event De-multiplexer and Initiation Dispatcher of Reactor pattern. - * Multiple handles i.e. {@link AbstractNioChannel}s can be registered to the reactor and it blocks - * for events from all these handles. Whenever an event occurs on any of the registered handles, it - * synchronously de-multiplexes the event which can be any of read, write or accept, and dispatches - * the event to the appropriate {@link ChannelHandler} using the {@link Dispatcher}. + * This class acts as Synchronous Event De-multiplexer and Initiation Dispatcher of Reactor pattern. Multiple handles + * i.e. {@link AbstractNioChannel}s can be registered to the reactor and it blocks for events from all these handles. + * Whenever an event occurs on any of the registered handles, it synchronously de-multiplexes the event which can be any + * of read, write or accept, and dispatches the event to the appropriate {@link ChannelHandler} using the + * {@link Dispatcher}. * *

- * Implementation: A NIO reactor runs in its own thread when it is started using {@link #start()} - * method. {@link NioReactor} uses {@link Selector} for realizing Synchronous Event De-multiplexing. + * Implementation: A NIO reactor runs in its own thread when it is started using {@link #start()} method. + * {@link NioReactor} uses {@link Selector} for realizing Synchronous Event De-multiplexing. * *

- * NOTE: This is one of the ways to implement NIO reactor and it does not take care of all possible - * edge cases which are required in a real application. This implementation is meant to demonstrate - * the fundamental concepts that lie behind Reactor pattern. + * NOTE: This is one of the ways to implement NIO reactor and it does not take care of all possible edge cases which are + * required in a real application. This implementation is meant to demonstrate the fundamental concepts that lie behind + * Reactor pattern. */ public class NioReactor { private final Selector selector; private final Dispatcher dispatcher; /** - * All the work of altering the SelectionKey operations and Selector operations are performed in - * the context of main event loop of reactor. So when any channel needs to change its readability - * or writability, a new command is added in the command queue and then the event loop picks up - * the command and executes it in next iteration. + * All the work of altering the SelectionKey operations and Selector operations are performed in the context of main + * event loop of reactor. So when any channel needs to change its readability or writability, a new command is added + * in the command queue and then the event loop picks up the command and executes it in next iteration. */ private final Queue pendingCommands = new ConcurrentLinkedQueue<>(); private final ExecutorService reactorMain = Executors.newSingleThreadExecutor(); /** - * Creates a reactor which will use provided {@code dispatcher} to dispatch events. The - * application can provide various implementations of dispatcher which suits its needs. + * Creates a reactor which will use provided {@code dispatcher} to dispatch events. The application can provide + * various implementations of dispatcher which suits its needs. * - * @param dispatcher a non-null dispatcher used to dispatch events on registered channels. - * @throws IOException if any I/O error occurs. + * @param dispatcher + * a non-null dispatcher used to dispatch events on registered channels. + * @throws IOException + * if any I/O error occurs. */ public NioReactor(Dispatcher dispatcher) throws IOException { this.dispatcher = dispatcher; @@ -57,7 +58,8 @@ public class NioReactor { /** * Starts the reactor event loop in a new thread. * - * @throws IOException if any I/O error occurs. + * @throws IOException + * if any I/O error occurs. */ public void start() throws IOException { reactorMain.execute(() -> { @@ -73,8 +75,10 @@ public class NioReactor { /** * Stops the reactor and related resources such as dispatcher. * - * @throws InterruptedException if interrupted while stopping the reactor. - * @throws IOException if any I/O error occurs. + * @throws InterruptedException + * if interrupted while stopping the reactor. + * @throws IOException + * if any I/O error occurs. */ public void stop() throws InterruptedException, IOException { reactorMain.shutdownNow(); @@ -84,15 +88,15 @@ public class NioReactor { } /** - * Registers a new channel (handle) with this reactor. Reactor will start waiting for events on - * this channel and notify of any events. While registering the channel the reactor uses - * {@link AbstractNioChannel#getInterestedOps()} to know about the interested operation of this - * channel. + * Registers a new channel (handle) with this reactor. Reactor will start waiting for events on this channel and + * notify of any events. While registering the channel the reactor uses {@link AbstractNioChannel#getInterestedOps()} + * to know about the interested operation of this channel. * - * @param channel a new channel on which reactor will wait for events. The channel must be bound - * prior to being registered. + * @param channel + * a new channel on which reactor will wait for events. The channel must be bound prior to being registered. * @return this - * @throws IOException if any I/O error occurs. + * @throws IOException + * if any I/O error occurs. */ public NioReactor registerChannel(AbstractNioChannel channel) throws IOException { SelectionKey key = channel.getJavaChannel().register(selector, channel.getInterestedOps()); @@ -113,8 +117,8 @@ public class NioReactor { processPendingCommands(); /* - * Synchronous event de-multiplexing happens here, this is blocking call which returns when it - * is possible to initiate non-blocking operation on any of the registered channels. + * Synchronous event de-multiplexing happens here, this is blocking call which returns when it is possible to + * initiate non-blocking operation on any of the registered channels. */ selector.select(); @@ -147,8 +151,8 @@ public class NioReactor { } /* - * Initiation dispatcher logic, it checks the type of event and notifier application specific - * event handler to handle the event. + * Initiation dispatcher logic, it checks the type of event and notifier application specific event handler to handle + * the event. */ private void processKey(SelectionKey key) throws IOException { if (key.isAcceptable()) { @@ -196,14 +200,15 @@ public class NioReactor { } /** - * Queues the change of operations request of a channel, which will change the interested - * operations of the channel sometime in future. + * Queues the change of operations request of a channel, which will change the interested operations of the channel + * sometime in future. *

- * This is a non-blocking method and does not guarantee that the operations have changed when this - * method returns. + * This is a non-blocking method and does not guarantee that the operations have changed when this method returns. * - * @param key the key for which operations have to be changed. - * @param interestedOps the new interest operations. + * @param key + * the key for which operations have to be changed. + * @param interestedOps + * the new interest operations. */ public void changeOps(SelectionKey key, int interestedOps) { pendingCommands.add(new ChangeKeyOpsCommand(key, interestedOps)); diff --git a/repository/etc/repository.png b/repository/etc/repository.png index 1e031f3ae..08d5d571d 100644 Binary files a/repository/etc/repository.png and b/repository/etc/repository.png differ diff --git a/repository/etc/repository.ucls b/repository/etc/repository.ucls index 6c42019e1..894e9434c 100644 --- a/repository/etc/repository.ucls +++ b/repository/etc/repository.ucls @@ -19,10 +19,48 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repository/index.md b/repository/index.md index a65044d29..697c708f9 100644 --- a/repository/index.md +++ b/repository/index.md @@ -3,8 +3,11 @@ layout: pattern title: Repository folder: repository permalink: /patterns/repository/ -categories: Architectural -tags: Java +categories: Persistence Tier +tags: + - Java + - Difficulty-Intermediate + - Spring --- **Intent:** Repository layer is added between the domain and data mapping @@ -25,3 +28,9 @@ querying is utilized. **Real world examples:** * [Spring Data](http://projects.spring.io/spring-data/) + +**Credits:** + +* [Don’t use DAO, use Repository](http://thinkinginobjects.com/2012/08/26/dont-use-dao-use-repository/) +* [Advanced Spring Data JPA - Specifications and Querydsl](https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/) + diff --git a/repository/pom.xml b/repository/pom.xml index 60b51568e..05b468a04 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -1,35 +1,42 @@ - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.8.0-SNAPSHOT - - repository - - - org.springframework.data - spring-data-jpa - - - org.hibernate - hibernate-entitymanager - - - commons-dbcp - commons-dbcp - - - com.h2database - h2 - - - junit - junit - test - - + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.10.0-SNAPSHOT + + repository + + + org.springframework + spring-test + + + org.springframework.data + spring-data-jpa + + + org.hibernate + hibernate-entitymanager + + + commons-dbcp + commons-dbcp + + + com.h2database + h2 + + + junit + junit + test + + + com.google.guava + guava + + diff --git a/repository/src/main/java/com/iluwatar/repository/App.java b/repository/src/main/java/com/iluwatar/repository/App.java index fb9680cb6..9c940b36d 100644 --- a/repository/src/main/java/com/iluwatar/repository/App.java +++ b/repository/src/main/java/com/iluwatar/repository/App.java @@ -5,7 +5,6 @@ import java.util.List; import org.springframework.context.support.ClassPathXmlApplicationContext; /** - * * Repository pattern mediates between the domain and data mapping layers using a collection-like * interface for accessing domain objects. A system with complex domain model often benefits from a * layer that isolates domain objects from the details of the database access code and in such @@ -16,28 +15,34 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; *

* In this example we utilize Spring Data to automatically generate a repository for us from the * {@link Person} domain object. Using the {@link PersonRepository} we perform CRUD operations on - * the entity. Underneath we have configured in-memory H2 database for which schema is created and - * dropped on each run. - * + * the entity, moreover, the query by {@link org.springframework.data.jpa.domain.Specification} are + * also performed. Underneath we have configured in-memory H2 database for which schema is created + * and dropped on each run. */ public class App { /** * Program entry point * - * @param args command line args + * @param args + * command line args */ public static void main(String[] args) { - ClassPathXmlApplicationContext context = - new ClassPathXmlApplicationContext("applicationContext.xml"); + + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( + "applicationContext.xml"); PersonRepository repository = context.getBean(PersonRepository.class); - Person peter = new Person("Peter", "Sagan"); - Person nasta = new Person("Nasta", "Kuzminova"); + Person peter = new Person("Peter", "Sagan", 17); + Person nasta = new Person("Nasta", "Kuzminova", 25); + Person john = new Person("John", "lawrence", 35); + Person terry = new Person("Terry", "Law", 36); // Add new Person records repository.save(peter); repository.save(nasta); + repository.save(john); + repository.save(terry); // Count Person records System.out.println("Count Person records: " + repository.count()); @@ -48,9 +53,6 @@ public class App { System.out.println(person); } - // Find Person by surname - System.out.println("Find by surname 'Sagan': " + repository.findBySurname("Sagan")); - // Update Person nasta.setName("Barbora"); nasta.setSurname("Spotakova"); @@ -61,9 +63,22 @@ public class App { // Remove record from Person repository.delete(2L); - // And finally count records + // count records System.out.println("Count Person records: " + repository.count()); + // find by name + Person p = repository.findOne(new PersonSpecifications.NameEqualSpec("John")); + System.out.println("Find by John is " + p); + + // find by age + persons = repository.findAll(new PersonSpecifications.AgeBetweenSpec(20, 40)); + + System.out.println("Find Person with age between 20,40: "); + for (Person person : persons) { + System.out.println(person); + } + context.close(); + } } diff --git a/repository/src/main/java/com/iluwatar/repository/AppConfig.java b/repository/src/main/java/com/iluwatar/repository/AppConfig.java new file mode 100644 index 000000000..62b9a4c04 --- /dev/null +++ b/repository/src/main/java/com/iluwatar/repository/AppConfig.java @@ -0,0 +1,135 @@ +package com.iluwatar.repository; + +import java.sql.SQLException; +import java.util.List; +import java.util.Properties; + +import javax.sql.DataSource; + +import org.apache.commons.dbcp.BasicDataSource; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; + +/** + * This is the same example as in {@link App} but with annotations based + * configuration for Spring. + * + */ +@EnableJpaRepositories +public class AppConfig { + + /** + * Creation of H2 db + * + * @return A new Instance of DataSource + */ + @Bean(destroyMethod = "close") + public DataSource dataSource() { + BasicDataSource basicDataSource = new BasicDataSource(); + basicDataSource.setDriverClassName("org.h2.Driver"); + basicDataSource.setUrl("jdbc:h2:~/databases/person"); + basicDataSource.setUsername("sa"); + basicDataSource.setPassword("sa"); + return (DataSource) basicDataSource; + } + + /** + * Factory to create a especific instance of Entity Manager + */ + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean(); + entityManager.setDataSource(dataSource()); + entityManager.setPackagesToScan("com.iluwatar"); + entityManager.setPersistenceProvider(new HibernatePersistenceProvider()); + entityManager.setJpaProperties(jpaProperties()); + + return entityManager; + } + + /** + * Properties for Jpa + */ + private Properties jpaProperties() { + Properties properties = new Properties(); + properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); + properties.setProperty("hibernate.hbm2ddl.auto", "create-drop"); + return properties; + } + + /** + * Get transaction manager + */ + @Bean + public JpaTransactionManager transactionManager() throws SQLException { + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); + return transactionManager; + } + + /** + * Program entry point + * + * @param args + * command line args + */ + public static void main(String[] args) { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + AppConfig.class); + PersonRepository repository = context.getBean(PersonRepository.class); + + Person peter = new Person("Peter", "Sagan", 17); + Person nasta = new Person("Nasta", "Kuzminova", 25); + Person john = new Person("John", "lawrence", 35); + Person terry = new Person("Terry", "Law", 36); + + // Add new Person records + repository.save(peter); + repository.save(nasta); + repository.save(john); + repository.save(terry); + + // Count Person records + System.out.println("Count Person records: " + repository.count()); + + // Print all records + List persons = (List) repository.findAll(); + for (Person person : persons) { + System.out.println(person); + } + + // Update Person + nasta.setName("Barbora"); + nasta.setSurname("Spotakova"); + repository.save(nasta); + + System.out.println("Find by id 2: " + repository.findOne(2L)); + + // Remove record from Person + repository.delete(2L); + + // count records + System.out.println("Count Person records: " + repository.count()); + + // find by name + Person p = repository.findOne(new PersonSpecifications.NameEqualSpec("John")); + System.out.println("Find by John is " + p); + + // find by age + persons = repository.findAll(new PersonSpecifications.AgeBetweenSpec(20, 40)); + + System.out.println("Find Person with age between 20,40: "); + for (Person person : persons) { + System.out.println(person); + } + + context.close(); + + } + +} diff --git a/repository/src/main/java/com/iluwatar/repository/Person.java b/repository/src/main/java/com/iluwatar/repository/Person.java index 97d5e7120..04d65a6d0 100644 --- a/repository/src/main/java/com/iluwatar/repository/Person.java +++ b/repository/src/main/java/com/iluwatar/repository/Person.java @@ -18,11 +18,18 @@ public class Person { private String name; private String surname; - public Person() {} + private int age; - public Person(String name, String surname) { + public Person() { + } + + /** + * Constructor + */ + public Person(String name, String surname, int age) { this.name = name; this.surname = surname; + this.age = age; } public Long getId() { @@ -49,8 +56,68 @@ public class Person { this.surname = surname; } + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + @Override public String toString() { - return "Person [id=" + id + ", name=" + name + ", surname=" + surname + "]"; + return "Person [id=" + id + ", name=" + name + ", surname=" + surname + ", age=" + age + "]"; } + + @Override + public int hashCode() { + + final int prime = 31; + int result = 1; + result = prime * result + age; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((surname == null) ? 0 : surname.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Person other = (Person) obj; + if (age != other.age) { + return false; + } + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (surname == null) { + if (other.surname != null) { + return false; + } + } else if (!surname.equals(other.surname)) { + return false; + } + return true; + } + } diff --git a/repository/src/main/java/com/iluwatar/repository/PersonRepository.java b/repository/src/main/java/com/iluwatar/repository/PersonRepository.java index 167b40d19..8d687f32d 100644 --- a/repository/src/main/java/com/iluwatar/repository/PersonRepository.java +++ b/repository/src/main/java/com/iluwatar/repository/PersonRepository.java @@ -1,7 +1,6 @@ package com.iluwatar.repository; -import java.util.List; - +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; @@ -11,7 +10,8 @@ import org.springframework.stereotype.Repository; * */ @Repository -public interface PersonRepository extends CrudRepository { +public interface PersonRepository + extends CrudRepository, JpaSpecificationExecutor { - public List findBySurname(String surname); + Person findByName(String name); } diff --git a/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java b/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java new file mode 100644 index 000000000..fa96f3ca6 --- /dev/null +++ b/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java @@ -0,0 +1,57 @@ +package com.iluwatar.repository; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import org.springframework.data.jpa.domain.Specification; + +/** + * Helper class, includes vary Specification as the abstraction of sql query criteria + */ +public class PersonSpecifications { + + public static class AgeBetweenSpec implements Specification { + + private int from; + + private int to; + + public AgeBetweenSpec(int from, int to) { + this.from = from; + this.to = to; + } + + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + + return cb.between(root.get("age"), from, to); + + } + + } + + /** + * Name specification + * + */ + public static class NameEqualSpec implements Specification { + + public String name; + + public NameEqualSpec(String name) { + this.name = name; + } + + /** + * Get predicate + */ + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + + return cb.equal(root.get("name"), this.name); + + } + } + +} diff --git a/repository/src/main/resources/META-INF/persistence.xml b/repository/src/main/resources/META-INF/persistence.xml index 0aded0dbd..00767fbc2 100644 --- a/repository/src/main/resources/META-INF/persistence.xml +++ b/repository/src/main/resources/META-INF/persistence.xml @@ -1,8 +1,8 @@ + xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> - + diff --git a/repository/src/main/resources/applicationContext.xml b/repository/src/main/resources/applicationContext.xml index 3fe15b2f6..ed03aba0a 100644 --- a/repository/src/main/resources/applicationContext.xml +++ b/repository/src/main/resources/applicationContext.xml @@ -1,39 +1,37 @@ - + - + - - - + + + - - - - - - + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java b/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java new file mode 100644 index 000000000..6f8746aa6 --- /dev/null +++ b/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java @@ -0,0 +1,110 @@ +package com.iluwatar.repository; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Resource; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import com.google.common.collect.Lists; + +/** + * Test case to test the functions of {@link PersonRepository}, beside the CRUD functions, the query + * by {@link org.springframework.data.jpa.domain.Specification} are also test. + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { AppConfig.class }, loader = AnnotationConfigContextLoader.class) +public class AnnotationBasedRepositoryTest { + + @Resource + private PersonRepository repository; + + Person peter = new Person("Peter", "Sagan", 17); + Person nasta = new Person("Nasta", "Kuzminova", 25); + Person john = new Person("John", "lawrence", 35); + Person terry = new Person("Terry", "Law", 36); + + List persons = Arrays.asList(peter, nasta, john, terry); + + /** + * Prepare data for test + */ + @Before + public void setup() { + + repository.save(persons); + } + + @Test + public void testFindAll() { + + List actuals = Lists.newArrayList(repository.findAll()); + assertTrue(actuals.containsAll(persons) && persons.containsAll(actuals)); + } + + @Test + public void testSave() { + + Person terry = repository.findByName("Terry"); + terry.setSurname("Lee"); + terry.setAge(47); + repository.save(terry); + + terry = repository.findByName("Terry"); + assertEquals(terry.getSurname(), "Lee"); + assertEquals(47, terry.getAge()); + } + + @Test + public void testDelete() { + + Person terry = repository.findByName("Terry"); + repository.delete(terry); + + assertEquals(3, repository.count()); + assertNull(repository.findByName("Terry")); + } + + @Test + public void testCount() { + + assertEquals(4, repository.count()); + } + + @Test + public void testFindAllByAgeBetweenSpec() { + + List persons = repository.findAll(new PersonSpecifications.AgeBetweenSpec(20, 40)); + + assertEquals(3, persons.size()); + assertTrue(persons.stream().allMatch((item) -> { + return item.getAge() > 20 && item.getAge() < 40; + })); + } + + @Test + public void testFindOneByNameEqualSpec() { + + Person actual = repository.findOne(new PersonSpecifications.NameEqualSpec("Terry")); + assertEquals(terry, actual); + } + + @After + public void cleanup() { + + repository.deleteAll(); + } + +} diff --git a/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java b/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java new file mode 100644 index 000000000..17393f2ff --- /dev/null +++ b/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java @@ -0,0 +1,54 @@ +package com.iluwatar.repository; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import javax.sql.DataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; +import org.springframework.transaction.annotation.Transactional; + +/** + * This case is Just for test the Annotation Based configuration + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { AppConfig.class }, loader = AnnotationConfigContextLoader.class) +public class AppConfigTest { + + @Autowired + DataSource dataSource; + + /** + * Test for bean instance + */ + @Test + public void testDataSource() { + assertNotNull(dataSource); + } + + /** + * Test for correct query execution + */ + @Test + @Transactional + public void testQuery() throws SQLException { + ResultSet resultSet = dataSource.getConnection().createStatement().executeQuery("SELECT 1"); + String result = null; + String expected = "1"; + while (resultSet.next()) { + result = resultSet.getString(1); + + } + assertTrue(result.equals(expected)); + } + +} diff --git a/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java b/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java new file mode 100644 index 000000000..3d6708815 --- /dev/null +++ b/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java @@ -0,0 +1,108 @@ +package com.iluwatar.repository; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Resource; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.google.common.collect.Lists; + +/** + * Test case to test the functions of {@link PersonRepository}, beside the CRUD functions, the query + * by {@link org.springframework.data.jpa.domain.Specification} are also test. + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "classpath:applicationContext.xml" }) +public class RepositoryTest { + + @Resource + private PersonRepository repository; + + Person peter = new Person("Peter", "Sagan", 17); + Person nasta = new Person("Nasta", "Kuzminova", 25); + Person john = new Person("John", "lawrence", 35); + Person terry = new Person("Terry", "Law", 36); + + List persons = Arrays.asList(peter, nasta, john, terry); + + /** + * Prepare data for test + */ + @Before + public void setup() { + + repository.save(persons); + } + + @Test + public void testFindAll() { + + List actuals = Lists.newArrayList(repository.findAll()); + assertTrue(actuals.containsAll(persons) && persons.containsAll(actuals)); + } + + @Test + public void testSave() { + + Person terry = repository.findByName("Terry"); + terry.setSurname("Lee"); + terry.setAge(47); + repository.save(terry); + + terry = repository.findByName("Terry"); + assertEquals(terry.getSurname(), "Lee"); + assertEquals(47, terry.getAge()); + } + + @Test + public void testDelete() { + + Person terry = repository.findByName("Terry"); + repository.delete(terry); + + assertEquals(3, repository.count()); + assertNull(repository.findByName("Terry")); + } + + @Test + public void testCount() { + + assertEquals(4, repository.count()); + } + + @Test + public void testFindAllByAgeBetweenSpec() { + + List persons = repository.findAll(new PersonSpecifications.AgeBetweenSpec(20, 40)); + + assertEquals(3, persons.size()); + assertTrue(persons.stream().allMatch((item) -> { + return item.getAge() > 20 && item.getAge() < 40; + })); + } + + @Test + public void testFindOneByNameEqualSpec() { + + Person actual = repository.findOne(new PersonSpecifications.NameEqualSpec("Terry")); + assertEquals(terry, actual); + } + + @After + public void cleanup() { + + repository.deleteAll(); + } + +} diff --git a/resource-acquisition-is-initialization/index.md b/resource-acquisition-is-initialization/index.md index c3aa6c045..e808783fb 100644 --- a/resource-acquisition-is-initialization/index.md +++ b/resource-acquisition-is-initialization/index.md @@ -4,7 +4,10 @@ title: Resource Acquisition Is Initialization folder: resource-acquisition-is-initialization permalink: /patterns/resource-acquisition-is-initialization/ categories: Other -tags: Java +tags: + - Java + - Difficulty-Beginner + - Idiom --- **Intent:** Resource Acquisition Is Initialization pattern can be used to implement exception safe resource management. diff --git a/resource-acquisition-is-initialization/pom.xml b/resource-acquisition-is-initialization/pom.xml index 4b0b2cadb..3e3fc8729 100644 --- a/resource-acquisition-is-initialization/pom.xml +++ b/resource-acquisition-is-initialization/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT resource-acquisition-is-initialization @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java index 32cd3792e..f734432af 100644 --- a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java +++ b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java @@ -24,9 +24,6 @@ public class App { /** * Program entry point - * - * @param args command line args - * @throws Exception */ public static void main(String[] args) throws Exception { diff --git a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java index 2859f74ba..71b104b7c 100644 --- a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java +++ b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.resource.acquisition.is.initialization; import org.junit.Test; -import com.iluwatar.resource.acquisition.is.initialization.App; - /** * * Application test diff --git a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/ClosableTest.java b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/ClosableTest.java new file mode 100644 index 000000000..423d0ab51 --- /dev/null +++ b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/ClosableTest.java @@ -0,0 +1,27 @@ +package com.iluwatar.resource.acquisition.is.initialization; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; + +/** + * Date: 12/28/15 - 9:31 PM + * + * @author Jeroen Meulemeester + */ +public class ClosableTest extends StdOutTest { + + @Test + public void testOpenClose() throws Exception { + final InOrder inOrder = inOrder(getStdOutMock()); + try (final SlidingDoor door = new SlidingDoor(); final TreasureChest chest = new TreasureChest()) { + inOrder.verify(getStdOutMock()).println("Sliding door opens."); + inOrder.verify(getStdOutMock()).println("Treasure chest opens."); + } + inOrder.verify(getStdOutMock()).println("Treasure chest closes."); + inOrder.verify(getStdOutMock()).println("Sliding door closes."); + inOrder.verifyNoMoreInteractions(); + } + +} \ No newline at end of file diff --git a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/StdOutTest.java b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/StdOutTest.java new file mode 100644 index 000000000..2fdc09e27 --- /dev/null +++ b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.resource.acquisition.is.initialization; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to std-out using {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/servant/index.md b/servant/index.md index 38a8e2c60..9cf20a53e 100644 --- a/servant/index.md +++ b/servant/index.md @@ -4,7 +4,9 @@ title: Servant folder: servant permalink: /patterns/servant/ categories: Structural -tags: Java +tags: + - Java + - Difficulty-Beginner --- **Intent:** Servant is used for providing some behavior to a group of classes. diff --git a/servant/pom.xml b/servant/pom.xml index 348905d24..3da9cae69 100644 --- a/servant/pom.xml +++ b/servant/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT servant @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/servant/src/main/java/com/iluwatar/servant/App.java b/servant/src/main/java/com/iluwatar/servant/App.java index 42babbc1d..cb5a63fa5 100644 --- a/servant/src/main/java/com/iluwatar/servant/App.java +++ b/servant/src/main/java/com/iluwatar/servant/App.java @@ -18,15 +18,13 @@ public class App { /** * Program entry point - * - * @param args */ public static void main(String[] args) { scenario(jenkins, 1); scenario(travis, 0); } - /* + /** * Can add a List with enum Actions for variable scenarios */ public static void scenario(Servant servant, int compliment) { @@ -44,16 +42,18 @@ public class App { servant.giveWine(k); servant.giveWine(q); // compliment - servant.GiveCompliments(guests.get(compliment)); + servant.giveCompliments(guests.get(compliment)); // outcome of the night - for (Royalty r : guests) + for (Royalty r : guests) { r.changeMood(); + } // check your luck - if (servant.checkIfYouWillBeHanged(guests)) + if (servant.checkIfYouWillBeHanged(guests)) { System.out.println(servant.name + " will live another day"); - else + } else { System.out.println("Poor " + servant.name + ". His days are numbered"); + } } } diff --git a/servant/src/main/java/com/iluwatar/servant/King.java b/servant/src/main/java/com/iluwatar/servant/King.java index 5e931c149..ab99252ad 100644 --- a/servant/src/main/java/com/iluwatar/servant/King.java +++ b/servant/src/main/java/com/iluwatar/servant/King.java @@ -28,10 +28,12 @@ public class King implements Royalty { @Override public void changeMood() { - if (!isHungry && isDrunk) + if (!isHungry && isDrunk) { isHappy = true; - if (complimentReceived) + } + if (complimentReceived) { isHappy = false; + } } @Override diff --git a/servant/src/main/java/com/iluwatar/servant/Queen.java b/servant/src/main/java/com/iluwatar/servant/Queen.java index db5446d34..b8568bdf1 100644 --- a/servant/src/main/java/com/iluwatar/servant/Queen.java +++ b/servant/src/main/java/com/iluwatar/servant/Queen.java @@ -29,8 +29,9 @@ public class Queen implements Royalty { @Override public void changeMood() { - if (complimentReceived && isFlirty && isDrunk) + if (complimentReceived && isFlirty && isDrunk && !isHungry) { isHappy = true; + } } @Override diff --git a/servant/src/main/java/com/iluwatar/servant/Servant.java b/servant/src/main/java/com/iluwatar/servant/Servant.java index 987bf8791..dbb623331 100644 --- a/servant/src/main/java/com/iluwatar/servant/Servant.java +++ b/servant/src/main/java/com/iluwatar/servant/Servant.java @@ -11,6 +11,9 @@ public class Servant { public String name; + /** + * Constructor + */ public Servant(String name) { this.name = name; } @@ -23,15 +26,20 @@ public class Servant { r.getDrink(); } - public void GiveCompliments(Royalty r) { + public void giveCompliments(Royalty r) { r.receiveCompliments(); } + /** + * Check if we will be hanged + */ public boolean checkIfYouWillBeHanged(ArrayList tableGuests) { boolean anotherDay = true; - for (Royalty r : tableGuests) - if (!r.getMood()) + for (Royalty r : tableGuests) { + if (!r.getMood()) { anotherDay = false; + } + } return anotherDay; } diff --git a/servant/src/test/java/com/iluwatar/servant/AppTest.java b/servant/src/test/java/com/iluwatar/servant/AppTest.java index d5a404291..20d5e6c0f 100644 --- a/servant/src/test/java/com/iluwatar/servant/AppTest.java +++ b/servant/src/test/java/com/iluwatar/servant/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.servant; import org.junit.Test; -import com.iluwatar.servant.App; - /** * * Application test diff --git a/servant/src/test/java/com/iluwatar/servant/KingTest.java b/servant/src/test/java/com/iluwatar/servant/KingTest.java new file mode 100644 index 000000000..3c0811bc5 --- /dev/null +++ b/servant/src/test/java/com/iluwatar/servant/KingTest.java @@ -0,0 +1,82 @@ +package com.iluwatar.servant; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Date: 12/28/15 - 9:40 PM + * + * @author Jeroen Meulemeester + */ +public class KingTest { + + @Test + public void testHungrySoberUncomplimentedKing() { + final King king = new King(); + king.changeMood(); + assertFalse(king.getMood()); + } + + @Test + public void testFedSoberUncomplimentedKing() { + final King king = new King(); + king.getFed(); + king.changeMood(); + assertFalse(king.getMood()); + } + + @Test + public void testHungryDrunkUncomplimentedKing() { + final King king = new King(); + king.getDrink(); + king.changeMood(); + assertFalse(king.getMood()); + } + + @Test + public void testHungrySoberComplimentedKing() { + final King king = new King(); + king.receiveCompliments(); + king.changeMood(); + assertFalse(king.getMood()); + } + + @Test + public void testFedDrunkUncomplimentedKing() { + final King king = new King(); + king.getFed(); + king.getDrink(); + king.changeMood(); + assertTrue(king.getMood()); + } + + @Test + public void testFedSoberComplimentedKing() { + final King king = new King(); + king.getFed(); + king.receiveCompliments(); + king.changeMood(); + assertFalse(king.getMood()); + } + + @Test + public void testFedDrunkComplimentedKing() { + final King king = new King(); + king.getFed(); + king.getDrink(); + king.receiveCompliments(); + king.changeMood(); + assertFalse(king.getMood()); + } + + @Test + public void testHungryDrunkComplimentedKing() { + final King king = new King(); + king.getDrink(); + king.receiveCompliments(); + king.changeMood(); + assertFalse(king.getMood()); + } + +} \ No newline at end of file diff --git a/servant/src/test/java/com/iluwatar/servant/QueenTest.java b/servant/src/test/java/com/iluwatar/servant/QueenTest.java new file mode 100644 index 000000000..d6f02774c --- /dev/null +++ b/servant/src/test/java/com/iluwatar/servant/QueenTest.java @@ -0,0 +1,46 @@ +package com.iluwatar.servant; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Date: 12/28/15 - 9:52 PM + * + * @author Jeroen Meulemeester + */ +public class QueenTest { + + @Test + public void testNotFlirtyUncomplemented() throws Exception { + final Queen queen = new Queen(); + queen.setFlirtiness(false); + queen.changeMood(); + assertFalse(queen.getMood()); + } + + @Test + public void testNotFlirtyComplemented() throws Exception { + final Queen queen = new Queen(); + queen.setFlirtiness(false); + queen.receiveCompliments(); + queen.changeMood(); + assertFalse(queen.getMood()); + } + + @Test + public void testFlirtyUncomplemented() throws Exception { + final Queen queen = new Queen(); + queen.changeMood(); + assertFalse(queen.getMood()); + } + + @Test + public void testFlirtyComplemented() throws Exception { + final Queen queen = new Queen(); + queen.receiveCompliments(); + queen.changeMood(); + assertTrue(queen.getMood()); + } + +} \ No newline at end of file diff --git a/servant/src/test/java/com/iluwatar/servant/ServantTest.java b/servant/src/test/java/com/iluwatar/servant/ServantTest.java new file mode 100644 index 000000000..9527bdbc9 --- /dev/null +++ b/servant/src/test/java/com/iluwatar/servant/ServantTest.java @@ -0,0 +1,70 @@ +package com.iluwatar.servant; + +import org.junit.Test; + +import java.util.ArrayList; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +/** + * Date: 12/28/15 - 10:02 PM + * + * @author Jeroen Meulemeester + */ +public class ServantTest { + + @Test + public void testFeed() throws Exception { + final Royalty royalty = mock(Royalty.class); + final Servant servant = new Servant("test"); + servant.feed(royalty); + verify(royalty).getFed(); + verifyNoMoreInteractions(royalty); + } + + @Test + public void testGiveWine() throws Exception { + final Royalty royalty = mock(Royalty.class); + final Servant servant = new Servant("test"); + servant.giveWine(royalty); + verify(royalty).getDrink(); + verifyNoMoreInteractions(royalty); + } + + @Test + public void testGiveCompliments() throws Exception { + final Royalty royalty = mock(Royalty.class); + final Servant servant = new Servant("test"); + servant.giveCompliments(royalty); + verify(royalty).receiveCompliments(); + verifyNoMoreInteractions(royalty); + } + + @Test + public void testCheckIfYouWillBeHanged() throws Exception { + final Royalty goodMoodRoyalty = mock(Royalty.class); + when(goodMoodRoyalty.getMood()).thenReturn(true); + + final Royalty badMoodRoyalty = mock(Royalty.class); + when(badMoodRoyalty.getMood()).thenReturn(true); + + final ArrayList goodCompany = new ArrayList<>(); + goodCompany.add(goodMoodRoyalty); + goodCompany.add(goodMoodRoyalty); + goodCompany.add(goodMoodRoyalty); + + final ArrayList badCompany = new ArrayList<>(); + goodCompany.add(goodMoodRoyalty); + goodCompany.add(goodMoodRoyalty); + goodCompany.add(badMoodRoyalty); + + assertTrue(new Servant("test").checkIfYouWillBeHanged(goodCompany)); + assertTrue(new Servant("test").checkIfYouWillBeHanged(badCompany)); + + } + +} \ No newline at end of file diff --git a/service-layer/index.md b/service-layer/index.md index ea3f3d0ba..68f4f6130 100644 --- a/service-layer/index.md +++ b/service-layer/index.md @@ -4,7 +4,9 @@ title: Service Layer folder: service-layer permalink: /patterns/service-layer/ categories: Architectural -tags: Java +tags: + - Java + - Difficulty-Intermediate --- **Intent:** Service Layer is an abstraction over domain logic. Typically diff --git a/service-layer/pom.xml b/service-layer/pom.xml index b188ba1d7..b8b977829 100644 --- a/service-layer/pom.xml +++ b/service-layer/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT service-layer @@ -22,5 +22,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java b/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java index a7053165d..ab0d3f9a0 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java @@ -47,6 +47,9 @@ public class App { queryData(); } + /** + * Initialize data + */ public static void initData() { // spells Spell spell1 = new Spell("Ice dart"); @@ -149,6 +152,9 @@ public class App { wizardDao.merge(wizard4); } + /** + * Query the data + */ public static void queryData() { MagicService service = new MagicServiceImpl(new WizardDaoImpl(), new SpellbookDaoImpl(), new SpellDaoImpl()); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java b/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java index d8c796427..ab0000922 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java @@ -12,8 +12,37 @@ import javax.persistence.Version; */ @MappedSuperclass @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) -public class BaseEntity { +public abstract class BaseEntity { @Version private Long version; + + /** + * Indicates the unique id of this entity + * + * @return The id of the entity, or 'null' when not persisted + */ + public abstract Long getId(); + + /** + * Set the id of this entity + * + * @param id The new id + */ + public abstract void setId(Long id); + + /** + * Get the name of this entity + * + * @return The name of the entity + */ + public abstract String getName(); + + /** + * Set the name of this entity + * + * @param name The new name + */ + public abstract void setName(final String name); + } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java index eae9286fa..2665ff858 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java @@ -39,8 +39,9 @@ public abstract class DaoBaseImpl implements Dao { result = (E) criteria.uniqueResult(); tx.commit(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); @@ -57,8 +58,9 @@ public abstract class DaoBaseImpl implements Dao { session.persist(entity); tx.commit(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); @@ -75,8 +77,9 @@ public abstract class DaoBaseImpl implements Dao { result = (E) session.merge(entity); tx.commit(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); @@ -93,8 +96,9 @@ public abstract class DaoBaseImpl implements Dao { session.delete(entity); tx.commit(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); @@ -111,8 +115,9 @@ public abstract class DaoBaseImpl implements Dao { Criteria criteria = session.createCriteria(persistentClass); result = criteria.list(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java b/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java index 9d1aec488..9920a50df 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java @@ -1,38 +1,56 @@ package com.iluwatar.servicelayer.hibernate; -import org.hibernate.SessionFactory; -import org.hibernate.cfg.Configuration; - import com.iluwatar.servicelayer.spell.Spell; import com.iluwatar.servicelayer.spellbook.Spellbook; import com.iluwatar.servicelayer.wizard.Wizard; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; + /** - * * Produces the Hibernate {@link SessionFactory}. - * */ public class HibernateUtil { - private static final SessionFactory sessionFactory; + /** + * The cached session factory + */ + private static volatile SessionFactory sessionFactory; - static { - try { - sessionFactory = - new Configuration().addAnnotatedClass(Wizard.class).addAnnotatedClass(Spellbook.class) - .addAnnotatedClass(Spell.class) - .setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect") - .setProperty("hibernate.connection.url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1") - .setProperty("hibernate.current_session_context_class", "thread") - .setProperty("hibernate.show_sql", "true") - .setProperty("hibernate.hbm2ddl.auto", "create-drop").buildSessionFactory(); - } catch (Throwable ex) { - System.err.println("Initial SessionFactory creation failed." + ex); - throw new ExceptionInInitializerError(ex); - } + private HibernateUtil() { } - public static SessionFactory getSessionFactory() { + /** + * Create the current session factory instance, create a new one when there is none yet. + * + * @return The session factory + */ + public static synchronized SessionFactory getSessionFactory() { + if (sessionFactory == null) { + try { + sessionFactory = + new Configuration().addAnnotatedClass(Wizard.class).addAnnotatedClass(Spellbook.class) + .addAnnotatedClass(Spell.class) + .setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect") + .setProperty("hibernate.connection.url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1") + .setProperty("hibernate.current_session_context_class", "thread") + .setProperty("hibernate.show_sql", "true") + .setProperty("hibernate.hbm2ddl.auto", "create-drop").buildSessionFactory(); + } catch (Throwable ex) { + System.err.println("Initial SessionFactory creation failed." + ex); + throw new ExceptionInInitializerError(ex); + } + } return sessionFactory; } + + /** + * Drop the current connection, resulting in a create-drop clean database next time. This is + * mainly used for JUnit testing since one test should not influence the other + */ + public static void dropSession() { + getSessionFactory().close(); + sessionFactory = null; + } + } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java index f46f55184..cda3fe58d 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java @@ -21,6 +21,9 @@ public class MagicServiceImpl implements MagicService { private SpellbookDao spellbookDao; private SpellDao spellDao; + /** + * Constructor + */ public MagicServiceImpl(WizardDao wizardDao, SpellbookDao spellbookDao, SpellDao spellDao) { this.wizardDao = wizardDao; this.spellbookDao = spellbookDao; diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java index f5f017625..708ba033e 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java @@ -1,12 +1,12 @@ package com.iluwatar.servicelayer.spell; +import com.iluwatar.servicelayer.common.DaoBaseImpl; + import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.criterion.Restrictions; -import com.iluwatar.servicelayer.common.DaoBaseImpl; - /** * * SpellDao implementation. @@ -24,11 +24,11 @@ public class SpellDaoImpl extends DaoBaseImpl implements SpellDao { Criteria criteria = session.createCriteria(persistentClass); criteria.add(Restrictions.eq("name", name)); result = (Spell) criteria.uniqueResult(); - result.getSpellbook().getWizards().size(); tx.commit(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java index 49d81a955..165dcdc22 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java @@ -6,6 +6,7 @@ import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToMany; @@ -50,7 +51,7 @@ public class Spellbook extends BaseEntity { private String name; - @ManyToMany(mappedBy = "spellbooks") + @ManyToMany(mappedBy = "spellbooks", fetch = FetchType.EAGER) private Set wizards; @OneToMany(mappedBy = "spellbook", orphanRemoval = true, cascade = CascadeType.ALL) diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java index 1de82d4a9..842764056 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java @@ -28,8 +28,9 @@ public class SpellbookDaoImpl extends DaoBaseImpl implements Spellboo result.getWizards().size(); tx.commit(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java index ad89dd28a..9ff36edef 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java @@ -30,8 +30,9 @@ public class WizardDaoImpl extends DaoBaseImpl implements WizardDao { } tx.commit(); } catch (Exception e) { - if (tx != null) + if (tx != null) { tx.rollback(); + } throw e; } finally { session.close(); diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java index 2a6202104..f92af7cff 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java @@ -1,8 +1,9 @@ package com.iluwatar.servicelayer.app; -import org.junit.Test; +import com.iluwatar.servicelayer.hibernate.HibernateUtil; -import com.iluwatar.servicelayer.app.App; +import org.junit.After; +import org.junit.Test; /** * @@ -16,4 +17,10 @@ public class AppTest { String[] args = {}; App.main(args); } + + @After + public void tearDown() throws Exception { + HibernateUtil.dropSession(); + } + } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java new file mode 100644 index 000000000..1dabe117a --- /dev/null +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java @@ -0,0 +1,123 @@ +package com.iluwatar.servicelayer.common; + +import com.iluwatar.servicelayer.hibernate.HibernateUtil; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * Date: 12/28/15 - 10:53 PM + * + * @author Jeroen Meulemeester + */ +public abstract class BaseDaoTest> { + + /** + * The number of entities stored before each test + */ + private static final int INITIAL_COUNT = 5; + + /** + * The unique id generator, shared between all entities + */ + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); + + /** + * Factory, used to create new entity instances with the given name + */ + private final Function factory; + + /** + * The tested data access object + */ + private final D dao; + + /** + * Create a new test using the given factory and dao + * + * @param factory The factory, used to create new entity instances with the given name + * @param dao The tested data access object + */ + public BaseDaoTest(final Function factory, final D dao) { + this.factory = factory; + this.dao = dao; + } + + @Before + public void setUp() throws Exception { + for (int i = 0; i < INITIAL_COUNT; i++) { + final String className = dao.persistentClass.getSimpleName(); + final String entityName = String.format("%s%d", className, ID_GENERATOR.incrementAndGet()); + this.dao.persist(this.factory.apply(entityName)); + } + } + + @After + public void tearDown() throws Exception { + HibernateUtil.dropSession(); + } + + protected final D getDao() { + return this.dao; + } + + @Test + public void testFind() throws Exception { + final List all = this.dao.findAll(); + for (final E entity : all) { + final E byId = this.dao.find(entity.getId()); + assertNotNull(byId); + assertEquals(byId.getId(), byId.getId()); + } + } + + @Test + public void testDelete() throws Exception { + final List originalEntities = this.dao.findAll(); + this.dao.delete(originalEntities.get(1)); + this.dao.delete(originalEntities.get(2)); + + final List entitiesLeft = this.dao.findAll(); + assertNotNull(entitiesLeft); + assertEquals(INITIAL_COUNT - 2, entitiesLeft.size()); + } + + @Test + public void testFindAll() throws Exception { + final List all = this.dao.findAll(); + assertNotNull(all); + assertEquals(INITIAL_COUNT, all.size()); + } + + @Test + public void testSetId() throws Exception { + final E entity = this.factory.apply("name"); + assertNull(entity.getId()); + + final Long expectedId = Long.valueOf(1); + entity.setId(expectedId); + assertEquals(expectedId, entity.getId()); + } + + @Test + public void testSetName() throws Exception { + final E entity = this.factory.apply("name"); + assertEquals("name", entity.getName()); + assertEquals("name", entity.toString()); + + final String expectedName = "new name"; + entity.setName(expectedName); + assertEquals(expectedName, entity.getName()); + assertEquals(expectedName, entity.toString()); + } + +} \ No newline at end of file diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java new file mode 100644 index 000000000..48f3ae9d3 --- /dev/null +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java @@ -0,0 +1,138 @@ +package com.iluwatar.servicelayer.magic; + +import com.iluwatar.servicelayer.spell.Spell; +import com.iluwatar.servicelayer.spell.SpellDao; +import com.iluwatar.servicelayer.spellbook.Spellbook; +import com.iluwatar.servicelayer.spellbook.SpellbookDao; +import com.iluwatar.servicelayer.wizard.Wizard; +import com.iluwatar.servicelayer.wizard.WizardDao; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +/** + * Date: 12/29/15 - 12:06 AM + * + * @author Jeroen Meulemeester + */ +public class MagicServiceImplTest { + + @Test + public void testFindAllWizards() throws Exception { + final WizardDao wizardDao = mock(WizardDao.class); + final SpellbookDao spellbookDao = mock(SpellbookDao.class); + final SpellDao spellDao = mock(SpellDao.class); + + final MagicServiceImpl service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); + verifyZeroInteractions(wizardDao, spellbookDao, spellDao); + + service.findAllWizards(); + verify(wizardDao).findAll(); + verifyNoMoreInteractions(wizardDao, spellbookDao, spellDao); + } + + @Test + public void testFindAllSpellbooks() throws Exception { + final WizardDao wizardDao = mock(WizardDao.class); + final SpellbookDao spellbookDao = mock(SpellbookDao.class); + final SpellDao spellDao = mock(SpellDao.class); + + final MagicServiceImpl service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); + verifyZeroInteractions(wizardDao, spellbookDao, spellDao); + + service.findAllSpellbooks(); + verify(spellbookDao).findAll(); + verifyNoMoreInteractions(wizardDao, spellbookDao, spellDao); + } + + @Test + public void testFindAllSpells() throws Exception { + final WizardDao wizardDao = mock(WizardDao.class); + final SpellbookDao spellbookDao = mock(SpellbookDao.class); + final SpellDao spellDao = mock(SpellDao.class); + + final MagicServiceImpl service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); + verifyZeroInteractions(wizardDao, spellbookDao, spellDao); + + service.findAllSpells(); + verify(spellDao).findAll(); + verifyNoMoreInteractions(wizardDao, spellbookDao, spellDao); + } + + @Test + public void testFindWizardsWithSpellbook() throws Exception { + final String bookname = "bookname"; + final Spellbook spellbook = mock(Spellbook.class); + final Set wizards = new HashSet<>(); + wizards.add(mock(Wizard.class)); + wizards.add(mock(Wizard.class)); + wizards.add(mock(Wizard.class)); + + when(spellbook.getWizards()).thenReturn(wizards); + + final SpellbookDao spellbookDao = mock(SpellbookDao.class); + when(spellbookDao.findByName(eq(bookname))).thenReturn(spellbook); + + final WizardDao wizardDao = mock(WizardDao.class); + final SpellDao spellDao = mock(SpellDao.class); + + + final MagicServiceImpl service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); + verifyZeroInteractions(wizardDao, spellbookDao, spellDao, spellbook); + + final List result = service.findWizardsWithSpellbook(bookname); + verify(spellbookDao).findByName(eq(bookname)); + verify(spellbook).getWizards(); + + assertNotNull(result); + assertEquals(3, result.size()); + + verifyNoMoreInteractions(wizardDao, spellbookDao, spellDao); + } + + @Test + public void testFindWizardsWithSpell() throws Exception { + final Set wizards = new HashSet<>(); + wizards.add(mock(Wizard.class)); + wizards.add(mock(Wizard.class)); + wizards.add(mock(Wizard.class)); + + final Spellbook spellbook = mock(Spellbook.class); + when(spellbook.getWizards()).thenReturn(wizards); + + final SpellbookDao spellbookDao = mock(SpellbookDao.class); + final WizardDao wizardDao = mock(WizardDao.class); + + final Spell spell = mock(Spell.class); + when(spell.getSpellbook()).thenReturn(spellbook); + + final String spellName = "spellname"; + final SpellDao spellDao = mock(SpellDao.class); + when(spellDao.findByName(eq(spellName))).thenReturn(spell); + + final MagicServiceImpl service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); + verifyZeroInteractions(wizardDao, spellbookDao, spellDao, spellbook); + + final List result = service.findWizardsWithSpell(spellName); + verify(spellDao).findByName(eq(spellName)); + verify(spellbook).getWizards(); + + assertNotNull(result); + assertEquals(3, result.size()); + + verifyNoMoreInteractions(wizardDao, spellbookDao, spellDao); + } + +} diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java new file mode 100644 index 000000000..99a8e142f --- /dev/null +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java @@ -0,0 +1,35 @@ +package com.iluwatar.servicelayer.spell; + +import com.iluwatar.servicelayer.common.BaseDaoTest; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/28/15 - 11:02 PM + * + * @author Jeroen Meulemeester + */ +public class SpellDaoImplTest extends BaseDaoTest { + + public SpellDaoImplTest() { + super(Spell::new, new SpellDaoImpl()); + } + + @Test + public void testFindByName() throws Exception { + final SpellDaoImpl dao = getDao(); + final List allSpells = dao.findAll(); + for (final Spell spell : allSpells) { + final Spell spellByName = dao.findByName(spell.getName()); + assertNotNull(spellByName); + assertEquals(spell.getId(), spellByName.getId()); + assertEquals(spell.getName(), spellByName.getName()); + } + } + +} diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java new file mode 100644 index 000000000..fda46009e --- /dev/null +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java @@ -0,0 +1,35 @@ +package com.iluwatar.servicelayer.spellbook; + +import com.iluwatar.servicelayer.common.BaseDaoTest; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/28/15 - 11:44 PM + * + * @author Jeroen Meulemeester + */ +public class SpellbookDaoImplTest extends BaseDaoTest { + + public SpellbookDaoImplTest() { + super(Spellbook::new, new SpellbookDaoImpl()); + } + + @Test + public void testFindByName() throws Exception { + final SpellbookDaoImpl dao = getDao(); + final List allBooks = dao.findAll(); + for (final Spellbook book : allBooks) { + final Spellbook spellByName = dao.findByName(book.getName()); + assertNotNull(spellByName); + assertEquals(book.getId(), spellByName.getId()); + assertEquals(book.getName(), spellByName.getName()); + } + } + +} diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java new file mode 100644 index 000000000..1812f4c36 --- /dev/null +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java @@ -0,0 +1,35 @@ +package com.iluwatar.servicelayer.wizard; + +import com.iluwatar.servicelayer.common.BaseDaoTest; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/28/15 - 11:46 PM + * + * @author Jeroen Meulemeester + */ +public class WizardDaoImplTest extends BaseDaoTest { + + public WizardDaoImplTest() { + super(Wizard::new, new WizardDaoImpl()); + } + + @Test + public void testFindByName() throws Exception { + final WizardDaoImpl dao = getDao(); + final List allWizards = dao.findAll(); + for (final Wizard spell : allWizards) { + final Wizard byName = dao.findByName(spell.getName()); + assertNotNull(byName); + assertEquals(spell.getId(), byName.getId()); + assertEquals(spell.getName(), byName.getName()); + } + } + +} diff --git a/service-locator/index.md b/service-locator/index.md index 03c432749..7357a0ac0 100644 --- a/service-locator/index.md +++ b/service-locator/index.md @@ -4,7 +4,10 @@ title: Service Locator folder: service-locator permalink: /patterns/service-locator/ categories: Structural -tags: Java +tags: + - Java + - Difficulty-Beginner + - Performance --- **Intent:** Encapsulate the processes involved in obtaining a service with a diff --git a/service-locator/pom.xml b/service-locator/pom.xml index f04f9199e..09acb03b8 100644 --- a/service-locator/pom.xml +++ b/service-locator/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT service-locator diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java index f2d338cdc..f2dd31221 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java @@ -12,6 +12,9 @@ public class ServiceImpl implements Service { private final String serviceName; private final int id; + /** + * Constructor + */ public ServiceImpl(String serviceName) { // set the service name this.serviceName = serviceName; diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java index 6df74f84e..6ec51b989 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java @@ -10,6 +10,9 @@ public class ServiceLocator { private static ServiceCache serviceCache = new ServiceCache(); + private ServiceLocator() { + } + /** * Fetch the service with the name param from the cache first, if no service is found, lookup the * service from the {@link InitContext} and then add the newly created service into the cache map @@ -29,7 +32,9 @@ public class ServiceLocator { */ InitContext ctx = new InitContext(); serviceObj = (Service) ctx.lookup(serviceJndiName); - serviceCache.addService(serviceObj); + if (serviceObj != null) { // Only cache a service if it actually exists + serviceCache.addService(serviceObj); + } return serviceObj; } } diff --git a/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java b/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java index ab1549182..0ed27656c 100644 --- a/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java +++ b/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.servicelocator; import org.junit.Test; -import com.iluwatar.servicelocator.App; - /** * * Application test diff --git a/service-locator/src/test/java/com/iluwatar/servicelocator/ServiceLocatorTest.java b/service-locator/src/test/java/com/iluwatar/servicelocator/ServiceLocatorTest.java new file mode 100644 index 000000000..ce54e054f --- /dev/null +++ b/service-locator/src/test/java/com/iluwatar/servicelocator/ServiceLocatorTest.java @@ -0,0 +1,46 @@ +package com.iluwatar.servicelocator; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/29/15 - 19:07 PM + * + * @author Jeroen Meulemeester + */ +public class ServiceLocatorTest { + + /** + * Verify if we just receive 'null' when requesting a non-existing service + */ + @Test + public void testGetNonExistentService() { + assertNull(ServiceLocator.getService("fantastic/unicorn/service")); + assertNull(ServiceLocator.getService("another/fantastic/unicorn/service")); + } + + /** + * Verify if we get the same cached instance when requesting the same service twice + */ + @Test + public void testServiceCache() { + final String[] serviceNames = new String[]{ + "jndi/serviceA", "jndi/serviceB" + }; + + for (final String serviceName : serviceNames) { + final Service service = ServiceLocator.getService(serviceName); + assertNotNull(service); + assertEquals(serviceName, service.getName()); + assertTrue(service.getId() > 0); // The id is generated randomly, but the minimum value is '1' + assertSame(service, ServiceLocator.getService(serviceName)); + } + + } + +} \ No newline at end of file diff --git a/singleton/pom.xml b/singleton/pom.xml index de316c34c..e08ffec86 100644 --- a/singleton/pom.xml +++ b/singleton/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT singleton diff --git a/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java b/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java index 585b11e61..f8b7e170f 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java +++ b/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java @@ -8,7 +8,7 @@ public final class IvoryTower { /** * Static to class instance of the class. */ - private static final IvoryTower instance = new IvoryTower(); + private static final IvoryTower INSTANCE = new IvoryTower(); /** * Private constructor so nobody can instantiate the class. @@ -21,6 +21,6 @@ public final class IvoryTower { * @return instance of the singleton. */ public static IvoryTower getInstance() { - return instance; + return INSTANCE; } } diff --git a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java index 1aca15b30..ab39a652d 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java +++ b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java @@ -11,14 +11,14 @@ package com.iluwatar.singleton; */ public class ThreadSafeDoubleCheckLocking { - private static volatile ThreadSafeDoubleCheckLocking INSTANCE; + private static volatile ThreadSafeDoubleCheckLocking instance; /** * private constructor to prevent client from instantiating. */ private ThreadSafeDoubleCheckLocking() { // to prevent instantiating by Reflection call - if (INSTANCE != null) { + if (instance != null) { throw new IllegalStateException("Already initialized."); } } @@ -31,12 +31,12 @@ public class ThreadSafeDoubleCheckLocking { public static ThreadSafeDoubleCheckLocking getInstance() { // local variable increases performance by 25 percent // Joshua Bloch "Effective Java, Second Edition", p. 283-284 - ThreadSafeDoubleCheckLocking result = INSTANCE; + ThreadSafeDoubleCheckLocking result = instance; if (result == null) { synchronized (ThreadSafeDoubleCheckLocking.class) { - result = INSTANCE; + result = instance; if (result == null) { - INSTANCE = result = new ThreadSafeDoubleCheckLocking(); + instance = result = new ThreadSafeDoubleCheckLocking(); } } } diff --git a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java index 98281b4c8..e67922016 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java +++ b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java @@ -16,7 +16,7 @@ public class ThreadSafeLazyLoadedIvoryTower { /** * The instance gets created only when it is called for first time. Lazy-loading */ - public synchronized static ThreadSafeLazyLoadedIvoryTower getInstance() { + public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() { if (instance == null) { instance = new ThreadSafeLazyLoadedIvoryTower(); diff --git a/singleton/src/test/java/com/iluwatar/singleton/AppTest.java b/singleton/src/test/java/com/iluwatar/singleton/AppTest.java index 4957eec6b..232de4e40 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/AppTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.singleton; import org.junit.Test; -import com.iluwatar.singleton.App; - /** * * Application test diff --git a/singleton/src/test/java/com/iluwatar/singleton/EnumIvoryTowerTest.java b/singleton/src/test/java/com/iluwatar/singleton/EnumIvoryTowerTest.java new file mode 100644 index 000000000..49c65c716 --- /dev/null +++ b/singleton/src/test/java/com/iluwatar/singleton/EnumIvoryTowerTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.singleton; + +/** + * Date: 12/29/15 - 19:20 PM + * + * @author Jeroen Meulemeester + */ +public class EnumIvoryTowerTest extends SingletonTest { + + /** + * Create a new singleton test instance using the given 'getInstance' method + */ + public EnumIvoryTowerTest() { + super(() -> EnumIvoryTower.INSTANCE); + } + +} diff --git a/singleton/src/test/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiomTest.java b/singleton/src/test/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiomTest.java new file mode 100644 index 000000000..60ae4798d --- /dev/null +++ b/singleton/src/test/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiomTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.singleton; + +/** + * Date: 12/29/15 - 19:22 PM + * + * @author Jeroen Meulemeester + */ +public class InitializingOnDemandHolderIdiomTest extends SingletonTest { + + /** + * Create a new singleton test instance using the given 'getInstance' method + */ + public InitializingOnDemandHolderIdiomTest() { + super(InitializingOnDemandHolderIdiom::getInstance); + } + +} \ No newline at end of file diff --git a/singleton/src/test/java/com/iluwatar/singleton/IvoryTowerTest.java b/singleton/src/test/java/com/iluwatar/singleton/IvoryTowerTest.java new file mode 100644 index 000000000..e9a222aef --- /dev/null +++ b/singleton/src/test/java/com/iluwatar/singleton/IvoryTowerTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.singleton; + +/** + * Date: 12/29/15 - 19:23 PM + * + * @author Jeroen Meulemeester + */ +public class IvoryTowerTest extends SingletonTest { + + /** + * Create a new singleton test instance using the given 'getInstance' method + */ + public IvoryTowerTest() { + super(IvoryTower::getInstance); + } + +} \ No newline at end of file diff --git a/singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java b/singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java deleted file mode 100644 index 3afc1bf14..000000000 --- a/singleton/src/test/java/com/iluwatar/singleton/LazyLoadedSingletonThreadSafetyTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.iluwatar.singleton; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.*; - -import static org.junit.Assert.assertEquals; - -/** - * This class provides several test case that test singleton construction. - * - * The first proves that multiple calls to the singleton getInstance object are the same when called - * in the SAME thread. The second proves that multiple calls to the singleton getInstance object are - * the same when called in the DIFFERENT thread. - * - */ -public class LazyLoadedSingletonThreadSafetyTest { - - private static final int NUM_THREADS = 5; - private List threadObjects = Collections - .synchronizedList(new ArrayList<>()); - - // NullObject class so Callable has to return something - private class NullObject { - private NullObject() {} - } - - @Test - public void test_MultipleCallsReturnTheSameObjectInSameThread() { - // Create several instances in the same calling thread - ThreadSafeLazyLoadedIvoryTower instance1 = ThreadSafeLazyLoadedIvoryTower.getInstance(); - ThreadSafeLazyLoadedIvoryTower instance2 = ThreadSafeLazyLoadedIvoryTower.getInstance(); - ThreadSafeLazyLoadedIvoryTower instance3 = ThreadSafeLazyLoadedIvoryTower.getInstance(); - // now check they are equal - assertEquals(instance1, instance1); - assertEquals(instance1, instance2); - assertEquals(instance2, instance3); - assertEquals(instance1, instance3); - } - - @Test - public void test_MultipleCallsReturnTheSameObjectInDifferentThreads() - throws InterruptedException, ExecutionException { - {// create several threads and inside each callable instantiate the singleton class - ExecutorService executorService = Executors.newSingleThreadExecutor(); - - List> threadList = new ArrayList<>(); - for (int i = 0; i < NUM_THREADS; i++) { - threadList.add(new SingletonCreatingThread()); - } - - ExecutorService service = Executors.newCachedThreadPool(); - List> results = service.invokeAll(threadList); - - // wait for all of the threads to complete - for (Future res : results) { - res.get(); - } - - // tidy up the executor - executorService.shutdown(); - } - {// now check the contents that were added to threadObjects by each thread - assertEquals(NUM_THREADS, threadObjects.size()); - assertEquals(threadObjects.get(0), threadObjects.get(1)); - assertEquals(threadObjects.get(1), threadObjects.get(2)); - assertEquals(threadObjects.get(2), threadObjects.get(3)); - assertEquals(threadObjects.get(3), threadObjects.get(4)); - } - } - - private class SingletonCreatingThread implements Callable { - @Override - public NullObject call() { - // instantiate the thread safety class and add to list to test afterwards - ThreadSafeLazyLoadedIvoryTower instance = ThreadSafeLazyLoadedIvoryTower.getInstance(); - threadObjects.add(instance); - return new NullObject();// return null object (cannot return Void) - } - } -} diff --git a/singleton/src/test/java/com/iluwatar/singleton/SingletonTest.java b/singleton/src/test/java/com/iluwatar/singleton/SingletonTest.java new file mode 100644 index 000000000..6c6c4a3f4 --- /dev/null +++ b/singleton/src/test/java/com/iluwatar/singleton/SingletonTest.java @@ -0,0 +1,88 @@ +package com.iluwatar.singleton; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Supplier; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +/** + * This class provides several test case that test singleton construction. + * + * The first proves that multiple calls to the singleton getInstance object are the same when called + * in the SAME thread. The second proves that multiple calls to the singleton getInstance object are + * the same when called in the DIFFERENT thread. + * + * Date: 12/29/15 - 19:25 PM + * + * @author Jeroen Meulemeester + * @author Richard Jones + */ +public abstract class SingletonTest { + + /** + * The singleton's getInstance method + */ + private final Supplier singletonInstanceMethod; + + /** + * Create a new singleton test instance using the given 'getInstance' method + * + * @param singletonInstanceMethod The singleton's getInstance method + */ + public SingletonTest(final Supplier singletonInstanceMethod) { + this.singletonInstanceMethod = singletonInstanceMethod; + } + + /** + * Test the singleton in a non-concurrent setting + */ + @Test + public void testMultipleCallsReturnTheSameObjectInSameThread() { + // Create several instances in the same calling thread + S instance1 = this.singletonInstanceMethod.get(); + S instance2 = this.singletonInstanceMethod.get(); + S instance3 = this.singletonInstanceMethod.get(); + // now check they are equal + assertSame(instance1, instance2); + assertSame(instance1, instance3); + assertSame(instance2, instance3); + } + + /** + * Test singleton instance in a concurrent setting + */ + @Test(timeout = 10000) + public void testMultipleCallsReturnTheSameObjectInDifferentThreads() throws Exception { + + // Create 10000 tasks and inside each callable instantiate the singleton class + final List> tasks = new ArrayList<>(); + for (int i = 0; i < 10000; i++) { + tasks.add(this.singletonInstanceMethod::get); + } + + // Use up to 8 concurrent threads to handle the tasks + final ExecutorService executorService = Executors.newFixedThreadPool(8); + final List> results = executorService.invokeAll(tasks); + + // wait for all of the threads to complete + final S expectedInstance = this.singletonInstanceMethod.get(); + for (Future res : results) { + final S instance = res.get(); + assertNotNull(instance); + assertSame(expectedInstance, instance); + } + + // tidy up the executor + executorService.shutdown(); + + } + +} diff --git a/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java new file mode 100644 index 000000000..f40f0cbc7 --- /dev/null +++ b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.singleton; + +/** + * Date: 12/29/15 - 19:26 PM + * + * @author Jeroen Meulemeester + */ +public class ThreadSafeDoubleCheckLockingTest extends SingletonTest { + + /** + * Create a new singleton test instance using the given 'getInstance' method + */ + public ThreadSafeDoubleCheckLockingTest() { + super(ThreadSafeDoubleCheckLocking::getInstance); + } + +} \ No newline at end of file diff --git a/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTowerTest.java b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTowerTest.java new file mode 100644 index 000000000..8f2a5e6e1 --- /dev/null +++ b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTowerTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.singleton; + +/** + * Date: 12/29/15 - 19:26 PM + * + * @author Jeroen Meulemeester + */ +public class ThreadSafeLazyLoadedIvoryTowerTest extends SingletonTest { + + /** + * Create a new singleton test instance using the given 'getInstance' method + */ + public ThreadSafeLazyLoadedIvoryTowerTest() { + super(ThreadSafeLazyLoadedIvoryTower::getInstance); + } + +} diff --git a/specification/index.md b/specification/index.md index 6b76d5102..f95c7921a 100644 --- a/specification/index.md +++ b/specification/index.md @@ -4,7 +4,9 @@ title: Specification folder: specification permalink: /patterns/specification/ categories: Behavioral -tags: Java +tags: + - Java + - Difficulty-Beginner --- **Intent:** Specification pattern separates the statement of how to match a diff --git a/specification/pom.xml b/specification/pom.xml index bf69e481e..64caa16b0 100644 --- a/specification/pom.xml +++ b/specification/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT specification @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + 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 d755d6c2e..373a24f92 100644 --- a/specification/src/main/java/com/iluwatar/specification/app/App.java +++ b/specification/src/main/java/com/iluwatar/specification/app/App.java @@ -31,6 +31,9 @@ import com.iluwatar.specification.selector.MovementSelector; */ public class App { + /** + * Program entry point + */ public static void main(String[] args) { // initialize creatures list List creatures = diff --git a/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java b/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java index 2ec3ccf55..f02befb73 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java @@ -16,6 +16,9 @@ public abstract class AbstractCreature implements Creature { private Movement movement; private Color color; + /** + * Constructor + */ public AbstractCreature(String name, Size size, Movement movement, Color color) { this.name = name; this.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 fe613eab7..b1bf00c24 100644 --- a/specification/src/test/java/com/iluwatar/specification/app/AppTest.java +++ b/specification/src/test/java/com/iluwatar/specification/app/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.specification.app; import org.junit.Test; -import com.iluwatar.specification.app.App; - /** * * Application test diff --git a/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java b/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java new file mode 100644 index 000000000..0548788a4 --- /dev/null +++ b/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java @@ -0,0 +1,111 @@ +package com.iluwatar.specification.creature; + +import com.iluwatar.specification.property.Color; +import com.iluwatar.specification.property.Movement; +import com.iluwatar.specification.property.Size; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/29/15 - 7:47 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class CreatureTest { + + /** + * @return The tested {@link Creature} instance and its expected specs + */ + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList( + new Object[]{new Dragon(), "Dragon", Size.LARGE, Movement.FLYING, Color.RED}, + new Object[]{new Goblin(), "Goblin", Size.SMALL, Movement.WALKING, Color.GREEN}, + new Object[]{new KillerBee(), "KillerBee", Size.SMALL, Movement.FLYING, Color.LIGHT}, + new Object[]{new Octopus(), "Octopus", Size.NORMAL, Movement.SWIMMING, Color.DARK}, + new Object[]{new Shark(), "Shark", Size.NORMAL, Movement.SWIMMING, Color.LIGHT}, + new Object[]{new Troll(), "Troll", Size.LARGE, Movement.WALKING, Color.DARK} + ); + } + + /** + * The tested creature + */ + private final Creature testedCreature; + + /** + * The expected name of the tested creature + */ + private final String name; + + /** + * The expected size of the tested creature + */ + private final Size size; + + /** + * The expected movement type of the tested creature + */ + private final Movement movement; + + /** + * The expected color of the tested creature + */ + private final Color color; + + /** + * @param testedCreature The tested creature + * @param name The expected name of the creature + * @param size The expected size of the creature + * @param movement The expected movement type of the creature + * @param color The expected color of the creature + */ + public CreatureTest(final Creature testedCreature, final String name, final Size size, + final Movement movement, final Color color) { + this.testedCreature = testedCreature; + this.name = name; + this.size = size; + this.movement = movement; + this.color = color; + } + + + @Test + public void testGetName() throws Exception { + assertEquals(this.name, this.testedCreature.getName()); + } + + @Test + public void testGetSize() throws Exception { + assertEquals(this.size, this.testedCreature.getSize()); + } + + @Test + public void testGetMovement() throws Exception { + assertEquals(this.movement, this.testedCreature.getMovement()); + } + + @Test + public void testGetColor() throws Exception { + assertEquals(this.color, this.testedCreature.getColor()); + } + + @Test + public void testToString() throws Exception { + final String toString = this.testedCreature.toString(); + assertNotNull(toString); + assertEquals( + String.format("%s [size=%s, movement=%s, color=%s]", name, size, movement, color), + 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 new file mode 100644 index 000000000..894f6c58e --- /dev/null +++ b/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java @@ -0,0 +1,37 @@ +package com.iluwatar.specification.selector; + +import com.iluwatar.specification.creature.Creature; +import com.iluwatar.specification.property.Color; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Date: 12/29/15 - 7:35 PM + * + * @author Jeroen Meulemeester + */ +public class ColorSelectorTest { + + /** + * Verify if the color selector gives the correct results + */ + @Test + public void testColor() { + final Creature greenCreature = mock(Creature.class); + when(greenCreature.getColor()).thenReturn(Color.GREEN); + + final Creature redCreature = mock(Creature.class); + when(redCreature.getColor()).thenReturn(Color.RED); + + final ColorSelector greenSelector = new ColorSelector(Color.GREEN); + assertTrue(greenSelector.test(greenCreature)); + assertFalse(greenSelector.test(redCreature)); + + } + +} \ No newline at end of file diff --git a/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java new file mode 100644 index 000000000..c2a251b5a --- /dev/null +++ b/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java @@ -0,0 +1,38 @@ +package com.iluwatar.specification.selector; + +import com.iluwatar.specification.creature.Creature; +import com.iluwatar.specification.property.Color; +import com.iluwatar.specification.property.Movement; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Date: 12/29/15 - 7:37 PM + * + * @author Jeroen Meulemeester + */ +public class MovementSelectorTest { + + /** + * Verify if the movement selector gives the correct results + */ + @Test + public void testMovement() { + final Creature swimmingCreature = mock(Creature.class); + when(swimmingCreature.getMovement()).thenReturn(Movement.SWIMMING); + + final Creature flyingCreature = mock(Creature.class); + when(flyingCreature.getMovement()).thenReturn(Movement.FLYING); + + final MovementSelector swimmingSelector = new MovementSelector(Movement.SWIMMING); + assertTrue(swimmingSelector.test(swimmingCreature)); + assertFalse(swimmingSelector.test(flyingCreature)); + + } + +} \ No newline at end of file diff --git a/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java new file mode 100644 index 000000000..d2a534c18 --- /dev/null +++ b/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java @@ -0,0 +1,36 @@ +package com.iluwatar.specification.selector; + +import com.iluwatar.specification.creature.Creature; +import com.iluwatar.specification.property.Size; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Date: 12/29/15 - 7:43 PM + * + * @author Jeroen Meulemeester + */ +public class SizeSelectorTest { + + /** + * Verify if the size selector gives the correct results + */ + @Test + public void testMovement() { + final Creature normalCreature = mock(Creature.class); + when(normalCreature.getSize()).thenReturn(Size.NORMAL); + + final Creature smallCreature = mock(Creature.class); + when(smallCreature.getSize()).thenReturn(Size.SMALL); + + final SizeSelector normalSelector = new SizeSelector(Size.NORMAL); + assertTrue(normalSelector.test(normalCreature)); + assertFalse(normalSelector.test(smallCreature)); + } + +} diff --git a/state/pom.xml b/state/pom.xml index 2bd9d9168..4c0dbe8d3 100644 --- a/state/pom.xml +++ b/state/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT state @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/state/src/main/java/com/iluwatar/state/App.java b/state/src/main/java/com/iluwatar/state/App.java index 2013466e0..63b59ad59 100644 --- a/state/src/main/java/com/iluwatar/state/App.java +++ b/state/src/main/java/com/iluwatar/state/App.java @@ -13,6 +13,9 @@ package com.iluwatar.state; */ public class App { + /** + * Program entry point + */ public static void main(String[] args) { Mammoth mammoth = new Mammoth(); diff --git a/state/src/main/java/com/iluwatar/state/Mammoth.java b/state/src/main/java/com/iluwatar/state/Mammoth.java index 8269ecb3d..92f4d7188 100644 --- a/state/src/main/java/com/iluwatar/state/Mammoth.java +++ b/state/src/main/java/com/iluwatar/state/Mammoth.java @@ -13,6 +13,9 @@ public class Mammoth { state = new PeacefulState(this); } + /** + * Makes time pass for the mammoth + */ public void timePasses() { if (state.getClass().equals(PeacefulState.class)) { changeStateTo(new AngryState(this)); diff --git a/state/src/test/java/com/iluwatar/state/AppTest.java b/state/src/test/java/com/iluwatar/state/AppTest.java index 0961a1c26..d03592739 100644 --- a/state/src/test/java/com/iluwatar/state/AppTest.java +++ b/state/src/test/java/com/iluwatar/state/AppTest.java @@ -2,10 +2,8 @@ package com.iluwatar.state; import org.junit.Test; -import com.iluwatar.state.App; - /** - * + * * Application test * */ diff --git a/state/src/test/java/com/iluwatar/state/MammothTest.java b/state/src/test/java/com/iluwatar/state/MammothTest.java new file mode 100644 index 000000000..4f7224208 --- /dev/null +++ b/state/src/test/java/com/iluwatar/state/MammothTest.java @@ -0,0 +1,90 @@ +package com.iluwatar.state; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +import java.io.PrintStream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; + +/** + * Date: 12/29/15 - 8:27 PM + * + * @author Jeroen Meulemeester + */ +public class MammothTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to std-out using {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Switch to a complete mammoth 'mood'-cycle and verify if the observed mood matches the expected + * value. + */ + @Test + public void testTimePasses() { + final InOrder inOrder = Mockito.inOrder(this.stdOutMock); + final Mammoth mammoth = new Mammoth(); + + mammoth.observe(); + inOrder.verify(this.stdOutMock).println("The mammoth is calm and peaceful."); + inOrder.verifyNoMoreInteractions(); + + mammoth.timePasses(); + inOrder.verify(this.stdOutMock).println("The mammoth gets angry!"); + inOrder.verifyNoMoreInteractions(); + + mammoth.observe(); + inOrder.verify(this.stdOutMock).println("The mammoth is furious!"); + inOrder.verifyNoMoreInteractions(); + + mammoth.timePasses(); + inOrder.verify(this.stdOutMock).println("The mammoth calms down."); + inOrder.verifyNoMoreInteractions(); + + mammoth.observe(); + inOrder.verify(this.stdOutMock).println("The mammoth is calm and peaceful."); + inOrder.verifyNoMoreInteractions(); + + } + + /** + * Verify if {@link Mammoth#toString()} gives the expected value + */ + @Test + public void testToString() { + final String toString = new Mammoth().toString(); + assertNotNull(toString); + assertEquals("The mammoth", toString); + } + +} \ No newline at end of file diff --git a/step-builder/index.md b/step-builder/index.md index 766479358..7ca93c276 100644 --- a/step-builder/index.md +++ b/step-builder/index.md @@ -4,7 +4,9 @@ title: Step Builder folder: step-builder permalink: /patterns/step-builder/ categories: Creational -tags: Java +tags: + - Java + - Difficulty-Intermediate --- **Intent:** An extension of the Builder pattern that fully guides the user through the creation of the object with no chances of confusion. diff --git a/step-builder/pom.xml b/step-builder/pom.xml index d31192323..a00d7e8d6 100644 --- a/step-builder/pom.xml +++ b/step-builder/pom.xml @@ -6,7 +6,7 @@ java-design-patterns com.iluwatar - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT step-builder diff --git a/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java b/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java index 3bf7b9a68..a839cd49e 100644 --- a/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java +++ b/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java @@ -11,7 +11,7 @@ package com.iluwatar.stepbuilder; * methods available, NO build method until is the right time to build the object. * *

- * Implementation
+ * Implementation *

    * The concept is simple: * diff --git a/step-builder/src/test/java/com/iluwatar/stepbuilder/CharacterStepBuilderTest.java b/step-builder/src/test/java/com/iluwatar/stepbuilder/CharacterStepBuilderTest.java new file mode 100644 index 000000000..b26635416 --- /dev/null +++ b/step-builder/src/test/java/com/iluwatar/stepbuilder/CharacterStepBuilderTest.java @@ -0,0 +1,155 @@ +package com.iluwatar.stepbuilder; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Date: 12/29/15 - 9:21 PM + * + * @author Jeroen Meulemeester + */ +public class CharacterStepBuilderTest { + + /** + * Build a new wizard {@link Character} and verify if it has the expected attributes + */ + @Test + public void testBuildWizard() { + final Character character = CharacterStepBuilder.newBuilder() + .name("Merlin") + .wizardClass("alchemist") + .withSpell("poison") + .withAbility("invisibility") + .withAbility("wisdom") + .noMoreAbilities() + .build(); + + assertEquals("Merlin", character.getName()); + assertEquals("alchemist", character.getWizardClass()); + assertEquals("poison", character.getSpell()); + assertNotNull(character.toString()); + + final List abilities = character.getAbilities(); + assertNotNull(abilities); + assertEquals(2, abilities.size()); + assertTrue(abilities.contains("invisibility")); + assertTrue(abilities.contains("wisdom")); + + } + + /** + * Build a new wizard {@link Character} without spell or abilities and verify if it has the + * expected attributes + */ + @Test + public void testBuildPoorWizard() { + final Character character = CharacterStepBuilder.newBuilder() + .name("Merlin") + .wizardClass("alchemist") + .noSpell() + .build(); + + assertEquals("Merlin", character.getName()); + assertEquals("alchemist", character.getWizardClass()); + assertNull(character.getSpell()); + assertNull(character.getAbilities()); + assertNotNull(character.toString()); + + } + + /** + * Build a new wizard {@link Character} and verify if it has the expected attributes + */ + @Test + public void testBuildWeakWizard() { + final Character character = CharacterStepBuilder.newBuilder() + .name("Merlin") + .wizardClass("alchemist") + .withSpell("poison") + .noAbilities() + .build(); + + assertEquals("Merlin", character.getName()); + assertEquals("alchemist", character.getWizardClass()); + assertEquals("poison", character.getSpell()); + assertNull(character.getAbilities()); + assertNotNull(character.toString()); + + } + + + /** + * Build a new warrior {@link Character} and verify if it has the expected attributes + */ + @Test + public void testBuildWarrior() { + final Character character = CharacterStepBuilder.newBuilder() + .name("Cuauhtemoc") + .fighterClass("aztec") + .withWeapon("spear") + .withAbility("speed") + .withAbility("strength") + .noMoreAbilities() + .build(); + + assertEquals("Cuauhtemoc", character.getName()); + assertEquals("aztec", character.getFighterClass()); + assertEquals("spear", character.getWeapon()); + assertNotNull(character.toString()); + + final List abilities = character.getAbilities(); + assertNotNull(abilities); + assertEquals(2, abilities.size()); + assertTrue(abilities.contains("speed")); + assertTrue(abilities.contains("strength")); + + } + + /** + * Build a new wizard {@link Character} without weapon and abilities and verify if it has the + * expected attributes + */ + @Test + public void testBuildPoorWarrior() { + final Character character = CharacterStepBuilder.newBuilder() + .name("Poor warrior") + .fighterClass("none") + .noWeapon() + .build(); + + assertEquals("Poor warrior", character.getName()); + assertEquals("none", character.getFighterClass()); + assertNull(character.getWeapon()); + assertNull(character.getAbilities()); + assertNotNull(character.toString()); + + } + + /** + * Build a new warrior {@link Character} without any abilities, but with a weapon and verify if it + * has the expected attributes + */ + @Test + public void testBuildWeakWarrior() { + final Character character = CharacterStepBuilder.newBuilder() + .name("Weak warrior") + .fighterClass("none") + .withWeapon("Slingshot") + .noAbilities() + .build(); + + assertEquals("Weak warrior", character.getName()); + assertEquals("none", character.getFighterClass()); + assertEquals("Slingshot", character.getWeapon()); + assertNull(character.getAbilities()); + assertNotNull(character.toString()); + + } + +} \ No newline at end of file diff --git a/strategy/pom.xml b/strategy/pom.xml index 4b4448ffe..b194365f1 100644 --- a/strategy/pom.xml +++ b/strategy/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT strategy @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/strategy/src/main/java/com/iluwatar/strategy/App.java b/strategy/src/main/java/com/iluwatar/strategy/App.java index 272e56cf9..e2bcfef8a 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/App.java +++ b/strategy/src/main/java/com/iluwatar/strategy/App.java @@ -5,6 +5,10 @@ package com.iluwatar.strategy; * The Strategy pattern (also known as the policy pattern) is a software design pattern that enables * an algorithm's behavior to be selected at runtime. *

    + * Before Java 8 the Strategies needed to be separate classes forcing the developer + * to write lots of boilerplate code. With modern Java it is easy to pass behavior + * with method references and lambdas making the code shorter and more readable. + *

    * In this example ({@link DragonSlayingStrategy}) encapsulates an algorithm. The containing object * ({@link DragonSlayer}) can alter its behavior by changing its strategy. * @@ -17,6 +21,7 @@ public class App { * @param args command line args */ public static void main(String[] args) { + // GoF Strategy pattern System.out.println("Green dragon spotted ahead!"); DragonSlayer dragonSlayer = new DragonSlayer(new MeleeStrategy()); dragonSlayer.goToBattle(); @@ -26,5 +31,19 @@ public class App { System.out.println("Black dragon lands before you."); dragonSlayer.changeStrategy(new SpellStrategy()); dragonSlayer.goToBattle(); + + // Java 8 Strategy pattern + System.out.println("Green dragon spotted ahead!"); + dragonSlayer = new DragonSlayer( + () -> System.out.println("With your Excalibur you severe the dragon's head!")); + dragonSlayer.goToBattle(); + System.out.println("Red dragon emerges."); + dragonSlayer.changeStrategy(() -> System.out.println( + "You shoot the dragon with the magical crossbow and it falls dead on the ground!")); + dragonSlayer.goToBattle(); + System.out.println("Black dragon lands before you."); + dragonSlayer.changeStrategy(() -> System.out.println( + "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!")); + dragonSlayer.goToBattle(); } } diff --git a/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java b/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java index e17e5fbf0..aa57c34f0 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java +++ b/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java @@ -5,6 +5,7 @@ package com.iluwatar.strategy; * Strategy interface. * */ +@FunctionalInterface public interface DragonSlayingStrategy { void execute(); diff --git a/strategy/src/test/java/com/iluwatar/strategy/AppTest.java b/strategy/src/test/java/com/iluwatar/strategy/AppTest.java index d3c970b89..88ee28be4 100644 --- a/strategy/src/test/java/com/iluwatar/strategy/AppTest.java +++ b/strategy/src/test/java/com/iluwatar/strategy/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.strategy; import org.junit.Test; -import com.iluwatar.strategy.App; - /** * * Application test diff --git a/strategy/src/test/java/com/iluwatar/strategy/DragonSlayerTest.java b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayerTest.java new file mode 100644 index 000000000..907d65ac4 --- /dev/null +++ b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayerTest.java @@ -0,0 +1,48 @@ +package com.iluwatar.strategy; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/29/15 - 10:50 PM + * + * @author Jeroen Meulemeester + */ +public class DragonSlayerTest { + + /** + * Verify if the dragon slayer uses the strategy during battle + */ + @Test + public void testGoToBattle() { + final DragonSlayingStrategy strategy = mock(DragonSlayingStrategy.class); + final DragonSlayer dragonSlayer = new DragonSlayer(strategy); + + dragonSlayer.goToBattle(); + verify(strategy).execute(); + verifyNoMoreInteractions(strategy); + } + + /** + * Verify if the dragon slayer uses the new strategy during battle after a change of strategy + */ + @Test + public void testChangeStrategy() throws Exception { + final DragonSlayingStrategy initialStrategy = mock(DragonSlayingStrategy.class); + final DragonSlayer dragonSlayer = new DragonSlayer(initialStrategy); + + dragonSlayer.goToBattle(); + verify(initialStrategy).execute(); + + final DragonSlayingStrategy newStrategy = mock(DragonSlayingStrategy.class); + dragonSlayer.changeStrategy(newStrategy); + + dragonSlayer.goToBattle(); + verify(newStrategy).execute(); + + verifyNoMoreInteractions(initialStrategy, newStrategy); + } +} \ No newline at end of file diff --git a/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java new file mode 100644 index 000000000..f9d18e22c --- /dev/null +++ b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java @@ -0,0 +1,104 @@ +package com.iluwatar.strategy; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.PrintStream; +import java.util.Arrays; +import java.util.Collection; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/29/15 - 10:58 PM + * + * @author Jeroen Meulemeester + */ +@RunWith(Parameterized.class) +public class DragonSlayingStrategyTest { + + /** + * @return The test parameters for each cycle + */ + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList( + new Object[]{ + new MeleeStrategy(), + "With your Excalibur you severe the dragon's head!" + }, + new Object[]{ + new ProjectileStrategy(), + "You shoot the dragon with the magical crossbow and it falls dead on the ground!" + }, + new Object[]{ + new SpellStrategy(), + "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!" + } + ); + } + + /** + * The tested strategy + */ + private final DragonSlayingStrategy strategy; + + /** + * The expected action on the std-out + */ + private final String expectedResult; + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to std-out using {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Create a new test instance for the given strategy + * + * @param strategy The tested strategy + * @param expectedResult The expected result + */ + public DragonSlayingStrategyTest(final DragonSlayingStrategy strategy, final String expectedResult) { + this.strategy = strategy; + this.expectedResult = expectedResult; + } + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Test if executing the strategy gives the correct response + */ + @Test + public void testExecute() { + this.strategy.execute(); + verify(this.stdOutMock).println(this.expectedResult); + verifyNoMoreInteractions(this.stdOutMock); + } + +} \ No newline at end of file diff --git a/template-method/pom.xml b/template-method/pom.xml index ecc0c4efa..ef2980203 100644 --- a/template-method/pom.xml +++ b/template-method/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT template-method @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java b/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java index bad3d790f..096d51b4e 100644 --- a/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java +++ b/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java @@ -13,6 +13,9 @@ public abstract class StealingMethod { protected abstract void stealTheItem(String target); + /** + * Steal + */ public void steal() { String target = pickTarget(); System.out.println("The target has been chosen as " + target + "."); diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java index ddc46de5b..80e867327 100644 --- a/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java +++ b/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.templatemethod; import org.junit.Test; -import com.iluwatar.templatemethod.App; - /** * * Application test diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/HalflingThiefTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/HalflingThiefTest.java new file mode 100644 index 000000000..be049720f --- /dev/null +++ b/template-method/src/test/java/com/iluwatar/templatemethod/HalflingThiefTest.java @@ -0,0 +1,50 @@ +package com.iluwatar.templatemethod; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/29/15 - 18:15 PM + * + * @author Jeroen Meulemeester + */ +public class HalflingThiefTest { + + /** + * Verify if the thief uses the provided stealing method + */ + @Test + public void testSteal() { + final StealingMethod method = mock(StealingMethod.class); + final HalflingThief thief = new HalflingThief(method); + + thief.steal(); + verify(method).steal(); + + verifyNoMoreInteractions(method); + } + + /** + * Verify if the thief uses the provided stealing method, and the new method after changing it + */ + @Test + public void testChangeMethod() { + final StealingMethod initialMethod = mock(StealingMethod.class); + final HalflingThief thief = new HalflingThief(initialMethod); + + thief.steal(); + verify(initialMethod).steal(); + + final StealingMethod newMethod = mock(StealingMethod.class); + thief.changeMethod(newMethod); + + thief.steal(); + verify(newMethod).steal(); + + verifyNoMoreInteractions(initialMethod, newMethod); + + } +} \ No newline at end of file diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/HitAndRunMethodTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/HitAndRunMethodTest.java new file mode 100644 index 000000000..86fc2591d --- /dev/null +++ b/template-method/src/test/java/com/iluwatar/templatemethod/HitAndRunMethodTest.java @@ -0,0 +1,23 @@ +package com.iluwatar.templatemethod; + +/** + * Date: 12/30/15 - 18:12 PM + * + * @author Jeroen Meulemeester + */ +public class HitAndRunMethodTest extends StealingMethodTest { + + /** + * Create a new test for the {@link HitAndRunMethod} + */ + public HitAndRunMethodTest() { + super( + new HitAndRunMethod(), + "old goblin woman", + "The target has been chosen as old goblin woman.", + "Approach the old goblin woman from behind.", + "Grab the handbag and run away fast!" + ); + } + +} \ No newline at end of file diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java new file mode 100644 index 000000000..61143a15d --- /dev/null +++ b/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java @@ -0,0 +1,142 @@ +package com.iluwatar.templatemethod; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; + +import java.io.PrintStream; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/30/15 - 18:12 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StealingMethodTest { + + /** + * The tested stealing method + */ + private final M method; + + /** + * The expected target + */ + private final String expectedTarget; + + /** + * The expected target picking result + */ + private final String expectedTargetResult; + + /** + * The expected confusion method + */ + private final String expectedConfuseMethod; + + /** + * The expected stealing method + */ + private final String expectedStealMethod; + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to std-out using {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Create a new test for the given stealing method, together with the expected results + * + * @param method The tested stealing method + * @param expectedTarget The expected target name + * @param expectedTargetResult The expected target picking result + * @param expectedConfuseMethod The expected confusion method + * @param expectedStealMethod The expected stealing method + */ + public StealingMethodTest(final M method, String expectedTarget, final String expectedTargetResult, + final String expectedConfuseMethod, final String expectedStealMethod) { + + this.method = method; + this.expectedTarget = expectedTarget; + this.expectedTargetResult = expectedTargetResult; + this.expectedConfuseMethod = expectedConfuseMethod; + this.expectedStealMethod = expectedStealMethod; + } + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Verify if the thief picks the correct target + */ + @Test + public void testPickTarget() { + assertEquals(expectedTarget, this.method.pickTarget()); + } + + /** + * Verify if the target confusing step goes as planned + */ + @Test + public void testConfuseTarget() { + verifyZeroInteractions(this.stdOutMock); + + this.method.confuseTarget(this.expectedTarget); + verify(this.stdOutMock).println(this.expectedConfuseMethod); + verifyNoMoreInteractions(this.stdOutMock); + } + + /** + * Verify if the stealing step goes as planned + */ + @Test + public void testStealTheItem() { + verifyZeroInteractions(this.stdOutMock); + + this.method.stealTheItem(this.expectedTarget); + verify(this.stdOutMock).println(this.expectedStealMethod); + verifyNoMoreInteractions(this.stdOutMock); + } + + /** + * Verify if the complete steal process goes as planned + */ + @Test + public void testSteal() { + final InOrder inOrder = inOrder(this.stdOutMock); + + this.method.steal(); + + inOrder.verify(this.stdOutMock).println(this.expectedTargetResult); + inOrder.verify(this.stdOutMock).println(this.expectedConfuseMethod); + inOrder.verify(this.stdOutMock).println(this.expectedStealMethod); + inOrder.verifyNoMoreInteractions(); + } + +} \ No newline at end of file diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/SubtleMethodTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/SubtleMethodTest.java new file mode 100644 index 000000000..8b3681a76 --- /dev/null +++ b/template-method/src/test/java/com/iluwatar/templatemethod/SubtleMethodTest.java @@ -0,0 +1,23 @@ +package com.iluwatar.templatemethod; + +/** + * Date: 12/30/15 - 18:19 PM + * + * @author Jeroen Meulemeester + */ +public class SubtleMethodTest extends StealingMethodTest { + + /** + * Create a new test for the {@link SubtleMethod} + */ + public SubtleMethodTest() { + super( + new SubtleMethod(), + "shop keeper", + "The target has been chosen as shop keeper.", + "Approach the shop keeper with tears running and hug him!", + "While in close contact grab the shop keeper's wallet." + ); + } + +} \ No newline at end of file diff --git a/thread-pool/index.md b/thread-pool/index.md index d9f00f428..d4b61607d 100644 --- a/thread-pool/index.md +++ b/thread-pool/index.md @@ -4,7 +4,10 @@ title: Thread Pool folder: thread-pool permalink: /patterns/thread-pool/ categories: Concurrency -tags: Java +tags: + - Java + - Difficulty-Intermediate + - Performance --- **Intent:** It is often the case that tasks to be executed are short-lived and diff --git a/thread-pool/pom.xml b/thread-pool/pom.xml index f79388008..5965b46bb 100644 --- a/thread-pool/pom.xml +++ b/thread-pool/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT thread-pool @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/App.java b/thread-pool/src/main/java/com/iluwatar/threadpool/App.java index 75971cd18..1833f3950 100644 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/App.java +++ b/thread-pool/src/main/java/com/iluwatar/threadpool/App.java @@ -65,6 +65,7 @@ public class App { // All tasks were executed, now shutdown executor.shutdown(); while (!executor.isTerminated()) { + Thread.yield(); } System.out.println("Program finished"); } diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java b/thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java index f1247101c..3a8464092 100644 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java +++ b/thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java @@ -7,7 +7,7 @@ package com.iluwatar.threadpool; */ public class CoffeeMakingTask extends Task { - private static final int TIME_PER_CUP = 300; + private static final int TIME_PER_CUP = 100; public CoffeeMakingTask(int numCups) { super(numCups * TIME_PER_CUP); diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java b/thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java index a90bf4bec..2be941406 100644 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java +++ b/thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java @@ -7,7 +7,7 @@ package com.iluwatar.threadpool; */ public class PotatoPeelingTask extends Task { - private static final int TIME_PER_POTATO = 500; + private static final int TIME_PER_POTATO = 200; public PotatoPeelingTask(int numPotatoes) { super(numPotatoes * TIME_PER_POTATO); diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/Task.java b/thread-pool/src/main/java/com/iluwatar/threadpool/Task.java index 12fecbbd0..2426948b3 100644 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/Task.java +++ b/thread-pool/src/main/java/com/iluwatar/threadpool/Task.java @@ -1,19 +1,21 @@ package com.iluwatar.threadpool; +import java.util.concurrent.atomic.AtomicInteger; + /** - * + * * Abstract base class for tasks * */ public abstract class Task { - private static int nextId = 1; + private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); private final int id; private final int timeMs; public Task(final int timeMs) { - this.id = nextId++; + this.id = ID_GENERATOR.incrementAndGet(); this.timeMs = timeMs; } diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java index c725983e2..f0f7b74bb 100644 --- a/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java +++ b/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.threadpool; import org.junit.Test; -import com.iluwatar.threadpool.App; - /** * Application test * diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/CoffeeMakingTaskTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/CoffeeMakingTaskTest.java new file mode 100644 index 000000000..ab3d47d9a --- /dev/null +++ b/thread-pool/src/test/java/com/iluwatar/threadpool/CoffeeMakingTaskTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.threadpool; + +/** + * Date: 12/30/15 - 18:23 PM + * + * @author Jeroen Meulemeester + */ +public class CoffeeMakingTaskTest extends TaskTest { + + /** + * Create a new test instance + */ + public CoffeeMakingTaskTest() { + super(CoffeeMakingTask::new, 100); + } + +} diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/PotatoPeelingTaskTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/PotatoPeelingTaskTest.java new file mode 100644 index 000000000..4f9b1496c --- /dev/null +++ b/thread-pool/src/test/java/com/iluwatar/threadpool/PotatoPeelingTaskTest.java @@ -0,0 +1,17 @@ +package com.iluwatar.threadpool; + +/** + * Date: 12/30/15 - 18:23 PM + * + * @author Jeroen Meulemeester + */ +public class PotatoPeelingTaskTest extends TaskTest { + + /** + * Create a new test instance + */ + public PotatoPeelingTaskTest() { + super(PotatoPeelingTask::new, 200); + } + +} \ No newline at end of file diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/TaskTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/TaskTest.java new file mode 100644 index 000000000..f1ef8160f --- /dev/null +++ b/thread-pool/src/test/java/com/iluwatar/threadpool/TaskTest.java @@ -0,0 +1,121 @@ +package com.iluwatar.threadpool; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Date: 12/30/15 - 18:22 PM + * + * @author Jeroen Meulemeester + */ +public abstract class TaskTest { + + /** + * The number of tasks used during the concurrency test + */ + private static final int TASK_COUNT = 128 * 1024; + + /** + * The number of threads used during the concurrency test + */ + private static final int THREAD_COUNT = 8; + + /** + * The task factory, used to create new test items + */ + private final Function factory; + + /** + * The expected time needed to run the task 1 single time, in milli seconds + */ + private final int expectedExecutionTime; + + /** + * Create a new test instance + * + * @param factory The task factory, used to create new test items + * @param expectedExecutionTime The expected time needed to run the task 1 time, in milli seconds + */ + public TaskTest(final Function factory, final int expectedExecutionTime) { + this.factory = factory; + this.expectedExecutionTime = expectedExecutionTime; + } + + /** + * Verify if the generated id is unique for each task, even if the tasks are created in separate + * threads + */ + @Test(timeout = 10000) + public void testIdGeneration() throws Exception { + final ExecutorService service = Executors.newFixedThreadPool(THREAD_COUNT); + + final List> tasks = new ArrayList<>(); + for (int i = 0; i < TASK_COUNT; i++) { + tasks.add(() -> factory.apply(1).getId()); + } + + final List ids = service.invokeAll(tasks) + .stream() + .map(TaskTest::get) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + service.shutdownNow(); + + final long uniqueIdCount = ids.stream() + .distinct() + .count(); + + assertEquals(TASK_COUNT, ids.size()); + assertEquals(TASK_COUNT, uniqueIdCount); + + } + + /** + * Verify if the time per execution of a task matches the actual time required to execute the task + * a given number of times + */ + @Test + public void testTimeMs() { + for (int i = 0; i < 10; i++) { + assertEquals(this.expectedExecutionTime * i, this.factory.apply(i).getTimeMs()); + } + } + + /** + * Verify if the task has some sort of {@link T#toString()}, different from 'null' + */ + @Test + public void testToString() { + assertNotNull(this.factory.apply(0).toString()); + } + + /** + * Extract the result from a future or returns 'null' when an exception occurred + * + * @param future The future we want the result from + * @param The result type + * @return The result or 'null' when a checked exception occurred + */ + private static O get(Future future) { + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + return null; + } + } + +} \ No newline at end of file diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/WorkerTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/WorkerTest.java new file mode 100644 index 000000000..53a1d8694 --- /dev/null +++ b/thread-pool/src/test/java/com/iluwatar/threadpool/WorkerTest.java @@ -0,0 +1,31 @@ +package com.iluwatar.threadpool; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/30/15 - 18:21 PM + * + * @author Jeroen Meulemeester + */ +public class WorkerTest { + + /** + * Verify if a worker does the actual job + */ + @Test + public void testRun() { + final Task task = mock(Task.class); + final Worker worker = new Worker(task); + verifyZeroInteractions(task); + + worker.run(); + verify(task).getTimeMs(); + verifyNoMoreInteractions(task); + } + +} \ No newline at end of file diff --git a/tolerant-reader/index.md b/tolerant-reader/index.md index b2bfd376a..895886f77 100644 --- a/tolerant-reader/index.md +++ b/tolerant-reader/index.md @@ -4,7 +4,9 @@ title: Tolerant Reader folder: tolerant-reader permalink: /patterns/tolerant-reader/ categories: Integration -tags: Java +tags: + - Java + - Difficulty-Beginner --- **Intent:** Tolerant Reader is an integration pattern that helps creating diff --git a/tolerant-reader/pom.xml b/tolerant-reader/pom.xml index d32006ed7..c7677b934 100644 --- a/tolerant-reader/pom.xml +++ b/tolerant-reader/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT tolerant-reader diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java index eb73fb7f3..242b71390 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java @@ -19,6 +19,9 @@ import java.io.IOException; */ public class App { + /** + * Program entry point + */ public static void main(String[] args) throws IOException, ClassNotFoundException { // Write V1 RainbowFish fishV1 = new RainbowFish("Zed", 10, 11, 12); diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java index 74c4526a0..d12ed4dbf 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java @@ -16,6 +16,9 @@ public class RainbowFish implements Serializable { private int lengthMeters; private int weightTons; + /** + * Constructor + */ public RainbowFish(String name, int age, int lengthMeters, int weightTons) { this.name = name; this.age = age; diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java index 3929e06e7..5d2a13735 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java @@ -18,12 +18,11 @@ import java.util.Map; */ public class RainbowFishSerializer { + private RainbowFishSerializer() { + } + /** * Write V1 RainbowFish to file - * - * @param rainbowFish - * @param filename - * @throws IOException */ public static void writeV1(RainbowFish rainbowFish, String filename) throws IOException { Map map = new HashMap<>(); @@ -40,10 +39,6 @@ public class RainbowFishSerializer { /** * Write V2 RainbowFish to file - * - * @param rainbowFish - * @param filename - * @throws IOException */ public static void writeV2(RainbowFishV2 rainbowFish, String filename) throws IOException { Map map = new HashMap<>(); @@ -63,17 +58,11 @@ public class RainbowFishSerializer { /** * Read V1 RainbowFish from file - * - * @param filename - * @return - * @throws IOException - * @throws ClassNotFoundException */ public static RainbowFish readV1(String filename) throws IOException, ClassNotFoundException { - Map map = null; FileInputStream fileIn = new FileInputStream(filename); ObjectInputStream objIn = new ObjectInputStream(fileIn); - map = (Map) objIn.readObject(); + Map map = (Map) objIn.readObject(); objIn.close(); fileIn.close(); return new RainbowFish(map.get("name"), Integer.parseInt(map.get("age")), Integer.parseInt(map diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java index 6146946e1..2c72bee4d 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java @@ -17,6 +17,9 @@ public class RainbowFishV2 extends RainbowFish { super(name, age, lengthMeters, weightTons); } + /** + * Constructor + */ public RainbowFishV2(String name, int age, int lengthMeters, int weightTons, boolean sleeping, boolean hungry, boolean angry) { this(name, age, lengthMeters, weightTons); diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java index ceb1c3f66..c7906adb2 100644 --- a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java @@ -1,13 +1,11 @@ package com.iluwatar.tolerantreader; -import java.io.File; -import java.io.IOException; - import org.junit.After; import org.junit.Before; import org.junit.Test; -import com.iluwatar.tolerantreader.App; +import java.io.File; +import java.io.IOException; /** * diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishSerializerTest.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishSerializerTest.java new file mode 100644 index 000000000..5f7ca0262 --- /dev/null +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishSerializerTest.java @@ -0,0 +1,68 @@ +package com.iluwatar.tolerantreader; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; + +/** + * Date: 12/30/15 - 18:39 PM + * + * @author Jeroen Meulemeester + */ +public class RainbowFishSerializerTest { + + /** + * Create a temporary folder, used to generate files in during this test + */ + @Rule + public final TemporaryFolder testFolder = new TemporaryFolder(); + + /** + * Rainbow fish version 1 used during the tests + */ + private static final RainbowFish V1 = new RainbowFish("version1", 1, 2, 3); + + /** + * Rainbow fish version 2 used during the tests + */ + private static final RainbowFishV2 V2 = new RainbowFishV2("version2", 4, 5, 6, true, false, true); + + /** + * Verify if a fish, written as version 1 can be read back as version 1 + */ + @Test + public void testWriteV1ReadV1() throws Exception { + final File outputFile = this.testFolder.newFile(); + RainbowFishSerializer.writeV1(V1, outputFile.getPath()); + + final RainbowFish fish = RainbowFishSerializer.readV1(outputFile.getPath()); + assertNotSame(V1, fish); + assertEquals(V1.getName(), fish.getName()); + assertEquals(V1.getAge(), fish.getAge()); + assertEquals(V1.getLengthMeters(), fish.getLengthMeters()); + assertEquals(V1.getWeightTons(), fish.getWeightTons()); + + } + + /** + * Verify if a fish, written as version 2 can be read back as version 1 + */ + @Test + public void testWriteV2ReadV1() throws Exception { + final File outputFile = this.testFolder.newFile(); + RainbowFishSerializer.writeV2(V2, outputFile.getPath()); + + final RainbowFish fish = RainbowFishSerializer.readV1(outputFile.getPath()); + assertNotSame(V2, fish); + assertEquals(V2.getName(), fish.getName()); + assertEquals(V2.getAge(), fish.getAge()); + assertEquals(V2.getLengthMeters(), fish.getLengthMeters()); + assertEquals(V2.getWeightTons(), fish.getWeightTons()); + } + +} \ No newline at end of file diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishTest.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishTest.java new file mode 100644 index 000000000..0f7df25c8 --- /dev/null +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishTest.java @@ -0,0 +1,26 @@ +package com.iluwatar.tolerantreader; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Date: 12/30/15 - 18:34 PM + * + * @author Jeroen Meulemeester + */ +public class RainbowFishTest { + + /** + * Verify if the getters of a {@link RainbowFish} return the expected values + */ + @Test + public void testValues() { + final RainbowFish fish = new RainbowFish("name", 1, 2, 3); + assertEquals("name", fish.getName()); + assertEquals(1, fish.getAge()); + assertEquals(2, fish.getLengthMeters()); + assertEquals(3, fish.getWeightTons()); + } + +} \ No newline at end of file diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishV2Test.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishV2Test.java new file mode 100644 index 000000000..5e8bdcef5 --- /dev/null +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishV2Test.java @@ -0,0 +1,29 @@ +package com.iluwatar.tolerantreader; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Date: 12/30/15 - 18:35 PM + * + * @author Jeroen Meulemeester + */ +public class RainbowFishV2Test { + + /** + * Verify if the getters of a {@link RainbowFish} return the expected values + */ + @Test + public void testValues() { + final RainbowFishV2 fish = new RainbowFishV2("name", 1, 2, 3, false, true, false); + assertEquals("name", fish.getName()); + assertEquals(1, fish.getAge()); + assertEquals(2, fish.getLengthMeters()); + assertEquals(3, fish.getWeightTons()); + assertEquals(false, fish.getSleeping()); + assertEquals(true, fish.getHungry()); + assertEquals(false, fish.getAngry()); + } + +} \ No newline at end of file diff --git a/twin/index.md b/twin/index.md index 475437754..e0e449047 100644 --- a/twin/index.md +++ b/twin/index.md @@ -4,14 +4,14 @@ title: Twin folder: twin permalink: /patterns/twin/ categories: Creational -tags: Java +tags: + - Java + - Difficulty-Intermediate --- **Intent:** Twin pattern is a design pattern which provides a standard solution to simulate multiple inheritance in java - - ![alt text](./etc/twin.png "Twin") **Applicability:** Use the Twin idiom when @@ -21,4 +21,4 @@ inheritance in java **Credits:** -* [Twin – A Design Pattern for Modeling Multiple Inheritance](http://www.ssw.uni-linz.ac.at/Research/Papers/Moe99/Paper.pdf) \ No newline at end of file +* [Twin – A Design Pattern for Modeling Multiple Inheritance](http://www.ssw.uni-linz.ac.at/Research/Papers/Moe99/Paper.pdf) diff --git a/twin/pom.xml b/twin/pom.xml index c2a4b131b..46e8de15a 100644 --- a/twin/pom.xml +++ b/twin/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT twin @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/twin/src/main/java/com/iluwatar/twin/App.java b/twin/src/main/java/com/iluwatar/twin/App.java index eaa21a849..cb971c490 100644 --- a/twin/src/main/java/com/iluwatar/twin/App.java +++ b/twin/src/main/java/com/iluwatar/twin/App.java @@ -41,6 +41,6 @@ public class App { } private static void waiting() throws Exception { - Thread.sleep(2500); + Thread.sleep(750); } } diff --git a/twin/src/main/java/com/iluwatar/twin/BallThread.java b/twin/src/main/java/com/iluwatar/twin/BallThread.java index dae537e64..2d9e7c41a 100644 --- a/twin/src/main/java/com/iluwatar/twin/BallThread.java +++ b/twin/src/main/java/com/iluwatar/twin/BallThread.java @@ -19,18 +19,20 @@ public class BallThread extends Thread { this.twin = twin; } + /** + * Run the thread + */ public void run() { while (isRunning) { - while (!isSuspended) { + if (!isSuspended) { twin.draw(); twin.move(); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - + } + try { + Thread.sleep(250); + } catch (InterruptedException e) { + throw new RuntimeException(e); } } } diff --git a/twin/src/main/java/com/iluwatar/twin/GameItem.java b/twin/src/main/java/com/iluwatar/twin/GameItem.java index d797eda95..e98202d0c 100644 --- a/twin/src/main/java/com/iluwatar/twin/GameItem.java +++ b/twin/src/main/java/com/iluwatar/twin/GameItem.java @@ -9,9 +9,6 @@ public abstract class GameItem { /** * Template method, do some common logic before draw - * - * @param other - * @return */ public void draw() { System.out.println("draw"); diff --git a/twin/src/test/java/com/iluwatar/twin/BallItemTest.java b/twin/src/test/java/com/iluwatar/twin/BallItemTest.java new file mode 100644 index 000000000..ca1da7ac8 --- /dev/null +++ b/twin/src/test/java/com/iluwatar/twin/BallItemTest.java @@ -0,0 +1,62 @@ +package com.iluwatar.twin; + +import org.junit.Test; +import org.mockito.InOrder; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/30/15 - 18:44 PM + * + * @author Jeroen Meulemeester + */ +public class BallItemTest extends StdOutTest { + + @Test + public void testClick() { + final BallThread ballThread = mock(BallThread.class); + final BallItem ballItem = new BallItem(); + ballItem.setTwin(ballThread); + + final InOrder inOrder = inOrder(ballThread); + + for (int i = 0; i < 10; i++) { + ballItem.click(); + inOrder.verify(ballThread).suspendMe(); + + ballItem.click(); + inOrder.verify(ballThread).resumeMe(); + } + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testDoDraw() { + final BallItem ballItem = new BallItem(); + final BallThread ballThread = mock(BallThread.class); + ballItem.setTwin(ballThread); + + ballItem.draw(); + verify(getStdOutMock()).println("draw"); + verify(getStdOutMock()).println("doDraw"); + + verifyNoMoreInteractions(ballThread, getStdOutMock()); + } + + @Test + public void testMove() { + final BallItem ballItem = new BallItem(); + final BallThread ballThread = mock(BallThread.class); + ballItem.setTwin(ballThread); + + ballItem.move(); + verify(getStdOutMock()).println("move"); + + verifyNoMoreInteractions(ballThread, getStdOutMock()); + } + +} \ No newline at end of file diff --git a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java new file mode 100644 index 000000000..7e0bdc11e --- /dev/null +++ b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java @@ -0,0 +1,89 @@ +package com.iluwatar.twin; + +import org.junit.Test; + +import static java.lang.Thread.UncaughtExceptionHandler; +import static java.lang.Thread.sleep; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Date: 12/30/15 - 18:55 PM + * + * @author Jeroen Meulemeester + */ +public class BallThreadTest { + + /** + * Verify if the {@link BallThread} can be resumed + */ + @Test(timeout = 5000) + public void testSuspend() throws Exception { + final BallThread ballThread = new BallThread(); + + final BallItem ballItem = mock(BallItem.class); + ballThread.setTwin(ballItem); + + ballThread.start(); + + verify(ballItem, timeout(2000).atLeastOnce()).draw(); + verify(ballItem, timeout(2000).atLeastOnce()).move(); + ballThread.suspendMe(); + + sleep(1000); + + ballThread.stopMe(); + ballThread.join(); + + verifyNoMoreInteractions(ballItem); + } + + /** + * Verify if the {@link BallThread} can be resumed + */ + @Test(timeout = 5000) + public void testResume() throws Exception { + final BallThread ballThread = new BallThread(); + + final BallItem ballItem = mock(BallItem.class); + ballThread.setTwin(ballItem); + + ballThread.suspendMe(); + ballThread.start(); + + sleep(1000); + + verifyZeroInteractions(ballItem); + + ballThread.resumeMe(); + verify(ballItem, timeout(2000).atLeastOnce()).draw(); + verify(ballItem, timeout(2000).atLeastOnce()).move(); + + ballThread.stopMe(); + ballThread.join(); + + verifyNoMoreInteractions(ballItem); + } + + /** + * Verify if the {@link BallThread} is interruptible + */ + @Test(timeout = 5000) + public void testInterrupt() throws Exception { + final BallThread ballThread = new BallThread(); + final UncaughtExceptionHandler exceptionHandler = mock(UncaughtExceptionHandler.class); + ballThread.setUncaughtExceptionHandler(exceptionHandler); + ballThread.setTwin(mock(BallItem.class)); + ballThread.start(); + ballThread.interrupt(); + ballThread.join(); + + verify(exceptionHandler).uncaughtException(eq(ballThread), any(RuntimeException.class)); + verifyNoMoreInteractions(exceptionHandler); + } +} \ No newline at end of file diff --git a/twin/src/test/java/com/iluwatar/twin/StdOutTest.java b/twin/src/test/java/com/iluwatar/twin/StdOutTest.java new file mode 100644 index 000000000..f506886e1 --- /dev/null +++ b/twin/src/test/java/com/iluwatar/twin/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.twin; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to std-out using {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/visitor/index.md b/visitor/index.md index 5f32edbbd..760f2c705 100644 --- a/visitor/index.md +++ b/visitor/index.md @@ -6,7 +6,7 @@ permalink: /patterns/visitor/ categories: Behavioral tags: - Java - - Difficulty-Expert + - Difficulty-Intermediate - Gang Of Four --- diff --git a/visitor/pom.xml b/visitor/pom.xml index b33f9975b..a53e2bcc5 100644 --- a/visitor/pom.xml +++ b/visitor/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.8.0-SNAPSHOT + 1.10.0-SNAPSHOT visitor @@ -14,5 +14,10 @@ junit test + + org.mockito + mockito-core + test + diff --git a/visitor/src/main/java/com/iluwatar/visitor/Unit.java b/visitor/src/main/java/com/iluwatar/visitor/Unit.java index 9fb52f6e0..300a6299b 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/Unit.java +++ b/visitor/src/main/java/com/iluwatar/visitor/Unit.java @@ -13,6 +13,9 @@ public abstract class Unit { this.children = children; } + /** + * Accept visitor + */ public void accept(UnitVisitor visitor) { for (Unit child : children) { child.accept(visitor); diff --git a/visitor/src/test/java/com/iluwatar/visitor/AppTest.java b/visitor/src/test/java/com/iluwatar/visitor/AppTest.java index 912f1a228..573a11532 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/AppTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/AppTest.java @@ -2,8 +2,6 @@ package com.iluwatar.visitor; import org.junit.Test; -import com.iluwatar.visitor.App; - /** * * Application test diff --git a/visitor/src/test/java/com/iluwatar/visitor/CommanderTest.java b/visitor/src/test/java/com/iluwatar/visitor/CommanderTest.java new file mode 100644 index 000000000..bbf6c7963 --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/CommanderTest.java @@ -0,0 +1,25 @@ +package com.iluwatar.visitor; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; + +/** + * Date: 12/30/15 - 19:45 PM + * + * @author Jeroen Meulemeester + */ +public class CommanderTest extends UnitTest { + + /** + * Create a new test instance for the given {@link Commander} + */ + public CommanderTest() { + super(Commander::new); + } + + @Override + void verifyVisit(Commander unit, UnitVisitor mockedVisitor) { + verify(mockedVisitor).visitCommander(eq(unit)); + } + +} \ No newline at end of file diff --git a/visitor/src/test/java/com/iluwatar/visitor/CommanderVisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/CommanderVisitorTest.java new file mode 100644 index 000000000..ac296c332 --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/CommanderVisitorTest.java @@ -0,0 +1,24 @@ +package com.iluwatar.visitor; + +import java.util.Optional; + +/** + * Date: 12/30/15 - 18:43 PM + * + * @author Jeroen Meulemeester + */ +public class CommanderVisitorTest extends VisitorTest { + + /** + * Create a new test instance for the given visitor + */ + public CommanderVisitorTest() { + super( + new CommanderVisitor(), + Optional.of("Good to see you commander"), + Optional.empty(), + Optional.empty() + ); + } + +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/SergeantTest.java b/visitor/src/test/java/com/iluwatar/visitor/SergeantTest.java new file mode 100644 index 000000000..d0e6d3db2 --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/SergeantTest.java @@ -0,0 +1,25 @@ +package com.iluwatar.visitor; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; + +/** + * Date: 12/30/15 - 19:45 PM + * + * @author Jeroen Meulemeester + */ +public class SergeantTest extends UnitTest { + + /** + * Create a new test instance for the given {@link Sergeant} + */ + public SergeantTest() { + super(Sergeant::new); + } + + @Override + void verifyVisit(Sergeant unit, UnitVisitor mockedVisitor) { + verify(mockedVisitor).visitSergeant(eq(unit)); + } + +} \ No newline at end of file diff --git a/visitor/src/test/java/com/iluwatar/visitor/SergeantVisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/SergeantVisitorTest.java new file mode 100644 index 000000000..54e274bc7 --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/SergeantVisitorTest.java @@ -0,0 +1,24 @@ +package com.iluwatar.visitor; + +import java.util.Optional; + +/** + * Date: 12/30/15 - 18:36 PM + * + * @author Jeroen Meulemeester + */ +public class SergeantVisitorTest extends VisitorTest { + + /** + * Create a new test instance for the given visitor + */ + public SergeantVisitorTest() { + super( + new SergeantVisitor(), + Optional.empty(), + Optional.of("Hello sergeant"), + Optional.empty() + ); + } + +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/SoldierTest.java b/visitor/src/test/java/com/iluwatar/visitor/SoldierTest.java new file mode 100644 index 000000000..e9aa54608 --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/SoldierTest.java @@ -0,0 +1,25 @@ +package com.iluwatar.visitor; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; + +/** + * Date: 12/30/15 - 19:45 PM + * + * @author Jeroen Meulemeester + */ +public class SoldierTest extends UnitTest { + + /** + * Create a new test instance for the given {@link Soldier} + */ + public SoldierTest() { + super(Soldier::new); + } + + @Override + void verifyVisit(Soldier unit, UnitVisitor mockedVisitor) { + verify(mockedVisitor).visitSoldier(eq(unit)); + } + +} \ No newline at end of file diff --git a/visitor/src/test/java/com/iluwatar/visitor/SoldierVisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/SoldierVisitorTest.java new file mode 100644 index 000000000..a5f16e9e3 --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/SoldierVisitorTest.java @@ -0,0 +1,24 @@ +package com.iluwatar.visitor; + +import java.util.Optional; + +/** + * Date: 12/30/15 - 18:59 PM + * + * @author Jeroen Meulemeester + */ +public class SoldierVisitorTest extends VisitorTest { + + /** + * Create a new test instance for the given visitor + */ + public SoldierVisitorTest() { + super( + new SoldierVisitor(), + Optional.empty(), + Optional.empty(), + Optional.of("Greetings soldier") + ); + } + +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/StdOutTest.java b/visitor/src/test/java/com/iluwatar/visitor/StdOutTest.java new file mode 100644 index 000000000..2c54994bb --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/StdOutTest.java @@ -0,0 +1,53 @@ +package com.iluwatar.visitor; + +import org.junit.After; +import org.junit.Before; + +import java.io.PrintStream; + +import static org.mockito.Mockito.mock; + +/** + * Date: 12/10/15 - 8:37 PM + * + * @author Jeroen Meulemeester + */ +public abstract class StdOutTest { + + /** + * The mocked standard out {@link PrintStream}, required since some actions don't have any + * influence on accessible objects, except for writing to std-out using {@link System#out} + */ + private final PrintStream stdOutMock = mock(PrintStream.class); + + /** + * Keep the original std-out so it can be restored after the test + */ + private final PrintStream stdOutOrig = System.out; + + /** + * Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test + */ + @Before + public void setUp() { + System.setOut(this.stdOutMock); + } + + /** + * Removed the mocked std-out {@link PrintStream} again from the {@link System} class + */ + @After + public void tearDown() { + System.setOut(this.stdOutOrig); + } + + /** + * Get the mocked stdOut {@link PrintStream} + * + * @return The stdOut print stream mock, renewed before each test + */ + final PrintStream getStdOutMock() { + return this.stdOutMock; + } + +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/UnitTest.java b/visitor/src/test/java/com/iluwatar/visitor/UnitTest.java new file mode 100644 index 000000000..291ab544a --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/UnitTest.java @@ -0,0 +1,60 @@ +package com.iluwatar.visitor; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.function.Function; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/30/15 - 18:59 PM + * + * @author Jeroen Meulemeester + */ +public abstract class UnitTest { + + /** + * Factory to create new instances of the tested unit + */ + private final Function factory; + + /** + * Create a new test instance for the given unit type {@link U} + * + * @param factory Factory to create new instances of the tested unit + */ + public UnitTest(final Function factory) { + this.factory = factory; + } + + @Test + public void testAccept() throws Exception { + final Unit[] children = new Unit[5]; + Arrays.setAll(children, (i) -> mock(Unit.class)); + + final U unit = this.factory.apply(children); + final UnitVisitor visitor = mock(UnitVisitor.class); + unit.accept(visitor); + verifyVisit(unit, visitor); + + for (final Unit child : children) { + verify(child).accept(eq(visitor)); + } + + verifyNoMoreInteractions(children); + verifyNoMoreInteractions(visitor); + } + + /** + * Verify if the correct visit method is called on the mock, depending on the tested instance + * + * @param unit The tested unit instance + * @param mockedVisitor The mocked {@link UnitVisitor} who should have gotten a visit by the unit + */ + abstract void verifyVisit(final U unit, final UnitVisitor mockedVisitor); + +} \ No newline at end of file diff --git a/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java new file mode 100644 index 000000000..7bd9f03c0 --- /dev/null +++ b/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java @@ -0,0 +1,80 @@ +package com.iluwatar.visitor; + +import org.junit.Test; + +import java.util.Optional; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Date: 12/30/15 - 18:59 PM + * + * @author Jeroen Meulemeester + */ +public abstract class VisitorTest extends StdOutTest { + + /** + * The tested visitor instance + */ + private final V visitor; + + /** + * The optional expected response when being visited by a commander + */ + private final Optional commanderResponse; + + /** + * The optional expected response when being visited by a sergeant + */ + private final Optional sergeantResponse; + + /** + * The optional expected response when being visited by a soldier + */ + private final Optional soldierResponse; + + /** + * Create a new test instance for the given visitor + * + * @param commanderResponse The optional expected response when being visited by a commander + * @param sergeantResponse The optional expected response when being visited by a sergeant + * @param soldierResponse The optional expected response when being visited by a soldier + */ + public VisitorTest(final V visitor, final Optional commanderResponse, + final Optional sergeantResponse, final Optional soldierResponse) { + + this.visitor = visitor; + this.commanderResponse = commanderResponse; + this.sergeantResponse = sergeantResponse; + this.soldierResponse = soldierResponse; + } + + @Test + public void testVisitCommander() { + this.visitor.visitCommander(new Commander()); + if (this.commanderResponse.isPresent()) { + verify(getStdOutMock()).println(this.commanderResponse.get()); + } + verifyNoMoreInteractions(getStdOutMock()); + } + + @Test + public void testVisitSergeant() { + this.visitor.visitSergeant(new Sergeant()); + if (this.sergeantResponse.isPresent()) { + verify(getStdOutMock()).println(this.sergeantResponse.get()); + } + verifyNoMoreInteractions(getStdOutMock()); + } + + @Test + public void testVisitSoldier() { + this.visitor.visitSoldier(new Soldier()); + if (this.soldierResponse.isPresent()) { + verify(getStdOutMock()).println(this.soldierResponse.get()); + } + verifyNoMoreInteractions(getStdOutMock()); + } + +}