diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000000..0155bcdcd2b3 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [iluwatar] diff --git a/.gitignore b/.gitignore index fd0bb7810faf..ada1e7d10ce5 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,4 @@ datanucleus.log /bin/ /bin/ *.log -data-mapper/src/main/resources/log4j.xml event-sourcing/Journal.json diff --git a/.travis.yml b/.travis.yml index 85f53bcd3024..0a5ac7709f2a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,30 +1,29 @@ language: java +dist: bionic jdk: -- oraclejdk8 +- openjdk11 +sudo: required env: global: - - GH_REF: github.com/iluwatar/java-design-patterns.git - - secure: LxTDuNS/rBWIvKkaEqr79ImZAe48mCdoYCF41coxNXgNoippo4GIBArknqtv+XvdkiuRZ1yGyj6pn8GU33c/yn+krddTUkVCwTbVatbalW5jhQjDbHYym/JcxaK9ZS/3JTeGcWrBgiPqHEEDhCf26vPZsXoMSeVCEORVKTp1BSg= - - secure: "eoWlW9GyTJY04P8K3pxayXwU9/hmptQg/LfirispQkV9YvmziCfSzXnatnBhNfud98sCzY8BScXnb+OWLTnjLKpId4rtEqb0aJ40Jc32cUKzgzFAUn7cNcDAbUIfyPAGVqyQqfj/11wYSADwWMMOPlW97ExUtoyiH2WenXuRHso=" - -before_install: -- export DISPLAY=:99.0 -- sh -e /etc/init.d/xvfb start - -# default install command is just "mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V" -install: -- mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -e + - secure: "DCpazS3nkLnter3sguXEAS2fC/1ZWNfM+XLyif9MfNFxlZdpni2vCD/jA0Rdpga8puQWHNVLyAec+RPFH/2qSmJ1c1UTV5MaLv8tPqwUX0VFA+1I6XoSv6oX4ldHTBWHEWqQHkRFOLoil0h0edc0tTOWQwXF8U+DLAB+HkRb4gw=" -after_success: -- mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=$SONAR_TOKEN -- bash update-ghpages.sh +services: +- xvfb -# use latest java version available instead of travis default addons: - apt: - packages: - - oracle-java8-installer + sonarcloud: + organization: "iluwatar" + token: + secure: "FpHwMYPMkdWU6CeIB7+O3qIeIM4vJMp47UjkKK53f0w0s6tPZofZZkab+gcL2TqKSil7sFVB/AQXU1cUubflRszwcLbNsc8H2yFehD79o0o0Mqd1Dd5ip/q0KQbHkkln+InFlVLfvrLB4Xd4mlQVxbGhqpULBhXjKzFzQlRFcuU=" +script: + # Because of Travis security restrictions, SonarCloud analysis cannot be run on pull requests originated from forks + # See https://docs.travis-ci.com/user/pull-requests/#pull-requests-and-security-restrictions + - 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then mvn clean verify; fi' + - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then mvn clean verify sonar:sonar -Dsonar.projectKey=iluwatar_java-design-patterns -Dsonar.host.url=https://sonarcloud.io; fi' + +after_success: +- bash update-website.sh notifications: email: @@ -35,5 +34,3 @@ notifications: on_success: change # options: [always|never|change] default: always on_failure: always # options: [always|never|change] default: always on_start: never # options: [always|never|change] default: always - -sudo: required diff --git a/CODE_COVERAGE.md b/CODE_COVERAGE.md deleted file mode 100644 index 333ee199f8e0..000000000000 --- a/CODE_COVERAGE.md +++ /dev/null @@ -1,13 +0,0 @@ -# Code Coverage Report generation - -To generate the code coverage report, execute the following command: -> mvn clean verify jacoco:report - -This will generate code coverage report in each of the modules. In order to view the same, open the following file in your browser. -> target/site/jacoco/index.html - -Please note that the above folder is created under each of the modules. For example: -* adapter/target/site/jacoco/index.html -* business-delegate/target/site/jacoco/index.html - - diff --git a/README.md b/README.md index 7ef0fbd216b1..1d8245d5dc7a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Build status](https://travis-ci.org/iluwatar/java-design-patterns.svg?branch=master)](https://travis-ci.org/iluwatar/java-design-patterns) [![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md) [![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) -[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=com.iluwatar%3Ajava-design-patterns&metric=alert_status)](https://sonarcloud.io/dashboard/index/com.iluwatar%3Ajava-design-patterns) +[![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=alert_status)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) # Introduction @@ -17,14 +17,21 @@ solve common problems when designing an application or system. Design patterns can speed up the development process by providing tested, proven development paradigms. -Reusing design patterns help prevent subtle issues which cause major +Reusing design patterns help prevent subtle issues that cause major problems, and it also improves code readability for coders and architects who are familiar with the patterns. # Getting started +This site showcases Java Design Patterns. The solutions have been developed by +experienced programmers and architects from the open source community. The +patterns can be browsed by their high level descriptions or by looking at their +source code. The source code examples are well commented and can be thought as +programming tutorials how to implement a specific pattern. We use the most +popular battle-proven open source Java technologies. + Before you dive into the material, you should be familiar with various -Programming/Software Design Principles. +software design principles. All designs should be as simple as possible. You should start with KISS, YAGNI, and Do The Simplest Thing That Could Possibly Work principles. Complexity and @@ -35,12 +42,17 @@ Once you are familiar with these concepts you can start drilling down into patterns by any of the following approaches - Using difficulty tags, `Difficulty-Beginner`, `Difficulty-Intermediate` & `Difficulty-Expert`. - - Using pattern categories, `Creational`, `Behavioral` and others. + - Using pattern categories, `Creational`, `Behavioral`, and others. - Search for a specific pattern. Can't find one? Please report a new pattern [here](https://github.com/iluwatar/java-design-patterns/issues). +Hopefully you find the object oriented solutions presented on this site useful +in your architectures and have as much fun learning them as we had developing them. + # 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). We will help you and answer your questions in the [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns). +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). # License diff --git a/abstract-document/README.md b/abstract-document/README.md index 2adb3732b822..15f5c6c85de9 100644 --- a/abstract-document/README.md +++ b/abstract-document/README.md @@ -5,13 +5,13 @@ folder: abstract-document permalink: /patterns/abstract-document/ categories: Structural tags: - - Java - - Difficulty-Intermediate + - Extensibility --- ## Intent Achieve flexibility of untyped languages and keep the type-safety +## Class diagram ![alt text](./etc/abstract-document.png "Abstract Document Traits and Domain") @@ -26,4 +26,4 @@ Use the Abstract Document Pattern when ## Credits * [Wikipedia: Abstract Document Pattern](https://en.wikipedia.org/wiki/Abstract_Document_Pattern) -* [Martin Fowler: Dealing with properties](http://martinfowler.com/apsupp/properties.pdf) \ No newline at end of file +* [Martin Fowler: Dealing with properties](http://martinfowler.com/apsupp/properties.pdf) diff --git a/abstract-document/etc/abstract-document.urm.puml b/abstract-document/etc/abstract-document.urm.puml new file mode 100644 index 000000000000..9940dc2cfd05 --- /dev/null +++ b/abstract-document/etc/abstract-document.urm.puml @@ -0,0 +1,65 @@ +@startuml +package com.iluwatar.abstractdocument.domain.enums { + enum Property { + + MODEL {static} + + PARTS {static} + + PRICE {static} + + TYPE {static} + + valueOf(name : String) : Property {static} + + values() : Property[] {static} + } +} +package com.iluwatar.abstractdocument.domain { + class Car { + + Car(properties : Map) + } + interface HasModel { + + getModel() : Optional + } + interface HasParts { + + getParts() : Stream + } + interface HasPrice { + + getPrice() : Optional + } + interface HasType { + + getType() : Optional + } + class Part { + + Part(properties : Map) + } +} +package com.iluwatar.abstractdocument { + abstract class AbstractDocument { + - properties : Map + # AbstractDocument(properties : Map) + + children(key : String, constructor : Function, T>) : Stream + + get(key : String) : Object + + put(key : String, value : Object) + + toString() : String + } + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + interface Document { + + children(String, Function, T>) : Stream {abstract} + + get(String) : Object {abstract} + + put(String, Object) {abstract} + } +} +AbstractDocument ..|> Document +Car ..|> HasModel +Car ..|> HasPrice +Car ..|> HasParts +Car --|> AbstractDocument +HasModel --|> Document +HasParts --|> Document +HasPrice --|> Document +HasType --|> Document +Part ..|> HasType +Part ..|> HasModel +Part ..|> HasPrice +Part --|> AbstractDocument +@enduml \ No newline at end of file diff --git a/abstract-document/pom.xml b/abstract-document/pom.xml index de6ef506982c..5a19c3c8193c 100644 --- a/abstract-document/pom.xml +++ b/abstract-document/pom.xml @@ -1,47 +1,55 @@ - - - 4.0.0 - - java-design-patterns - com.iluwatar - 1.21.0-SNAPSHOT - - abstract-document - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - \ No newline at end of file + + + 4.0.0 + + java-design-patterns + com.iluwatar + 1.23.0-SNAPSHOT + + abstract-document + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.abstractdocument.App + + + + + + + + + diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.java index 36a946cc9eca..485af25b3a45 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,17 +20,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractdocument; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.function.Function; import java.util.stream.Stream; /** - * Abstract implementation of Document interface + * Abstract implementation of Document interface. */ public abstract class AbstractDocument implements Document { @@ -54,17 +55,21 @@ public Object get(String key) { @Override public Stream children(String key, Function, T> constructor) { - Optional>> any = Stream.of(get(key)).filter(el -> el != null) - .map(el -> (List>) el).findAny(); - return any.isPresent() ? any.get().stream().map(constructor) : Stream.empty(); + return Stream.ofNullable(get(key)) + .filter(Objects::nonNull) + .map(el -> (List>) el) + .findAny() + .stream() + .flatMap(Collection::stream) + .map(constructor); } @Override public String toString() { - StringBuilder builder = new StringBuilder(); + var builder = new StringBuilder(); builder.append(getClass().getName()).append("["); - properties.entrySet() - .forEach(e -> builder.append("[").append(e.getKey()).append(" : ").append(e.getValue()).append("]")); + properties.forEach((key, value) -> builder.append("[").append(key).append(" : ").append(value) + .append("]")); builder.append("]"); return builder.toString(); } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java index 83f6ab3d59dd..b881ee7ac0f6 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,67 +20,64 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractdocument; import com.iluwatar.abstractdocument.domain.Car; -import com.iluwatar.abstractdocument.domain.HasModel; -import com.iluwatar.abstractdocument.domain.HasParts; -import com.iluwatar.abstractdocument.domain.HasPrice; -import com.iluwatar.abstractdocument.domain.HasType; +import com.iluwatar.abstractdocument.domain.enums.Property; +import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - /** - * The Abstract Document pattern enables handling additional, non-static - * properties. This pattern uses concept of traits to enable type safety and - * separate properties of different classes into set of interfaces. - *

- *

- * In Abstract Document pattern,({@link AbstractDocument}) fully implements - * {@link Document}) interface. Traits are then defined to enable access to - * properties in usual, static way. + * The Abstract Document pattern enables handling additional, non-static properties. This pattern + * uses concept of traits to enable type safety and separate properties of different classes into + * set of interfaces. + * + *

In Abstract Document pattern,({@link AbstractDocument}) fully implements {@link Document}) + * interface. Traits are then defined to enable access to properties in usual, static way. */ public class App { private static final Logger LOGGER = LoggerFactory.getLogger(App.class); /** - * Executes the App + * Executes the App. */ public App() { LOGGER.info("Constructing parts and car"); - Map carProperties = new HashMap<>(); - carProperties.put(HasModel.PROPERTY, "300SL"); - carProperties.put(HasPrice.PROPERTY, 10000L); - - Map wheelProperties = new HashMap<>(); - wheelProperties.put(HasType.PROPERTY, "wheel"); - wheelProperties.put(HasModel.PROPERTY, "15C"); - wheelProperties.put(HasPrice.PROPERTY, 100L); + var wheelProperties = Map.of( + Property.TYPE.toString(), "wheel", + Property.MODEL.toString(), "15C", + Property.PRICE.toString(), 100L); - Map doorProperties = new HashMap<>(); - doorProperties.put(HasType.PROPERTY, "door"); - doorProperties.put(HasModel.PROPERTY, "Lambo"); - doorProperties.put(HasPrice.PROPERTY, 300L); + var doorProperties = Map.of( + Property.TYPE.toString(), "door", + Property.MODEL.toString(), "Lambo", + Property.PRICE.toString(), 300L); - carProperties.put(HasParts.PROPERTY, Arrays.asList(wheelProperties, doorProperties)); + var carProperties = Map.of( + Property.MODEL.toString(), "300SL", + Property.PRICE.toString(), 10000L, + Property.PARTS.toString(), List.of(wheelProperties, doorProperties)); - Car car = new Car(carProperties); + var car = new Car(carProperties); LOGGER.info("Here is our car:"); - LOGGER.info("-> model: {}", car.getModel().get()); - LOGGER.info("-> price: {}", car.getPrice().get()); + LOGGER.info("-> model: {}", car.getModel().orElseThrow()); + LOGGER.info("-> price: {}", car.getPrice().orElseThrow()); LOGGER.info("-> parts: "); - car.getParts().forEach(p -> LOGGER.info("\t{}/{}/{}", p.getType().get(), p.getModel().get(), p.getPrice().get())); + car.getParts().forEach(p -> LOGGER.info("\t{}/{}/{}", + p.getType().orElse(null), + p.getModel().orElse(null), + p.getPrice().orElse(null)) + ); } /** - * Program entry point + * Program entry point. * * @param args command line args */ diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java index 13782351492f..d0eb85f34c78 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractdocument; import java.util.Map; @@ -27,12 +28,12 @@ import java.util.stream.Stream; /** - * Document interface + * Document interface. */ public interface Document { /** - * Puts the value related to the key + * Puts the value related to the key. * * @param key element key * @param value element value @@ -41,7 +42,7 @@ public interface Document { Void put(String key, Object value); /** - * Gets the value for the key + * Gets the value for the key. * * @param key element key * @return value or null @@ -49,7 +50,7 @@ public interface Document { Object get(String key); /** - * Gets the stream of child documents + * Gets the stream of child documents. * * @param key element key * @param constructor constructor of child class diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java index 44f640b415fd..bf68d40e57fe 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,14 +20,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.abstractdocument.domain; -import java.util.Map; +package com.iluwatar.abstractdocument.domain; import com.iluwatar.abstractdocument.AbstractDocument; +import java.util.Map; /** - * Car entity + * Car entity. */ public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts { diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java index 252a4644f289..76d7d743139d 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,21 +20,20 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.abstractdocument.domain; -import java.util.Optional; +package com.iluwatar.abstractdocument.domain; import com.iluwatar.abstractdocument.Document; +import com.iluwatar.abstractdocument.domain.enums.Property; +import java.util.Optional; /** - * HasModel trait for static access to 'model' property + * HasModel trait for static access to 'model' property. */ public interface HasModel extends Document { - String PROPERTY = "model"; - default Optional getModel() { - return Optional.ofNullable((String) get(PROPERTY)); + return Optional.ofNullable((String) get(Property.MODEL.toString())); } } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java index 9df836376f55..8ecfa85fb5eb 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,21 +20,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.abstractdocument.domain; -import java.util.stream.Stream; +package com.iluwatar.abstractdocument.domain; import com.iluwatar.abstractdocument.Document; +import com.iluwatar.abstractdocument.domain.enums.Property; +import java.util.stream.Stream; /** - * HasParts trait for static access to 'parts' property + * HasParts trait for static access to 'parts' property. */ public interface HasParts extends Document { - String PROPERTY = "parts"; default Stream getParts() { - return children(PROPERTY, Part::new); + return children(Property.PARTS.toString(), Part::new); } } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java index 39e4d159ce58..9a95f2a514db 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,21 +20,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.abstractdocument.domain; -import java.util.Optional; +package com.iluwatar.abstractdocument.domain; import com.iluwatar.abstractdocument.Document; +import com.iluwatar.abstractdocument.domain.enums.Property; +import java.util.Optional; /** - * HasPrice trait for static access to 'price' property + * HasPrice trait for static access to 'price' property. */ public interface HasPrice extends Document { - String PROPERTY = "price"; default Optional getPrice() { - return Optional.ofNullable((Number) get(PROPERTY)); + return Optional.ofNullable((Number) get(Property.PRICE.toString())); } } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java index 4a80bff20c16..b1d5bd6b563f 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,21 +20,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractdocument.domain; import com.iluwatar.abstractdocument.Document; - +import com.iluwatar.abstractdocument.domain.enums.Property; import java.util.Optional; /** - * HasType trait for static access to 'type' property + * HasType trait for static access to 'type' property. */ public interface HasType extends Document { - String PROPERTY = "type"; default Optional getType() { - return Optional.ofNullable((String) get(PROPERTY)); + return Optional.ofNullable((String) get(Property.TYPE.toString())); } } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java index 649ccf41365b..996598a92712 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,14 +20,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.abstractdocument.domain; -import java.util.Map; +package com.iluwatar.abstractdocument.domain; import com.iluwatar.abstractdocument.AbstractDocument; +import java.util.Map; /** - * Part entity + * Part entity. */ public class Part extends AbstractDocument implements HasType, HasModel, HasPrice { diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/enums/Property.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/enums/Property.java new file mode 100644 index 000000000000..640f0ed83f1d --- /dev/null +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/enums/Property.java @@ -0,0 +1,32 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.abstractdocument.domain.enums; + +/** + * Enum To Describe Property type. + */ +public enum Property { + + PARTS, TYPE, PRICE, MODEL +} diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java index 03b2126c6edc..d7fe5688d262 100644 --- a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java +++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,19 +20,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractdocument; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; /** * AbstractDocument test class @@ -59,29 +57,26 @@ public void shouldPutAndGetValue() { @Test public void shouldRetrieveChildren() { - Map child1 = new HashMap<>(); - Map child2 = new HashMap<>(); - List> children = Arrays.asList(child1, child2); + var children = List.of(Map.of(), Map.of()); document.put(KEY, children); - Stream childrenStream = document.children(KEY, DocumentImplementation::new); + var childrenStream = document.children(KEY, DocumentImplementation::new); assertNotNull(children); assertEquals(2, childrenStream.count()); } @Test public void shouldRetrieveEmptyStreamForNonExistingChildren() { - Stream children = document.children(KEY, DocumentImplementation::new); + var children = document.children(KEY, DocumentImplementation::new); assertNotNull(children); assertEquals(0, children.count()); } @Test public void shouldIncludePropsInToString() { - Map props = new HashMap<>(); - props.put(KEY, VALUE); - DocumentImplementation document = new DocumentImplementation(props); + var props = Map.of(KEY, (Object) VALUE); + var document = new DocumentImplementation(props); assertTrue(document.toString().contains(KEY)); assertTrue(document.toString().contains(VALUE)); } diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java index 0546be523f67..aed63f303181 100644 --- a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java +++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractdocument; import org.junit.jupiter.api.Test; diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java index 891e5a45763e..5b1311ff6af9 100644 --- a/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java +++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,21 +20,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractdocument; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.iluwatar.abstractdocument.domain.Car; -import com.iluwatar.abstractdocument.domain.HasModel; -import com.iluwatar.abstractdocument.domain.HasParts; -import com.iluwatar.abstractdocument.domain.HasPrice; -import com.iluwatar.abstractdocument.domain.HasType; import com.iluwatar.abstractdocument.domain.Part; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.HashMap; +import com.iluwatar.abstractdocument.domain.enums.Property; +import java.util.List; import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; /** * Test for Part and Car @@ -50,27 +46,27 @@ public class DomainTest { @Test public void shouldConstructPart() { - Map partProperties = new HashMap<>(); - partProperties.put(HasType.PROPERTY, TEST_PART_TYPE); - partProperties.put(HasModel.PROPERTY, TEST_PART_MODEL); - partProperties.put(HasPrice.PROPERTY, TEST_PART_PRICE); - Part part = new Part(partProperties); - - assertEquals(TEST_PART_TYPE, part.getType().get()); - assertEquals(TEST_PART_MODEL, part.getModel().get()); - assertEquals(TEST_PART_PRICE, part.getPrice().get()); + var partProperties = Map.of( + Property.TYPE.toString(), TEST_PART_TYPE, + Property.MODEL.toString(), TEST_PART_MODEL, + Property.PRICE.toString(), (Object) TEST_PART_PRICE + ); + var part = new Part(partProperties); + assertEquals(TEST_PART_TYPE, part.getType().orElseThrow()); + assertEquals(TEST_PART_MODEL, part.getModel().orElseThrow()); + assertEquals(TEST_PART_PRICE, part.getPrice().orElseThrow()); } @Test public void shouldConstructCar() { - Map carProperties = new HashMap<>(); - carProperties.put(HasModel.PROPERTY, TEST_CAR_MODEL); - carProperties.put(HasPrice.PROPERTY, TEST_CAR_PRICE); - carProperties.put(HasParts.PROPERTY, Arrays.asList(new HashMap<>(), new HashMap<>())); - Car car = new Car(carProperties); - - assertEquals(TEST_CAR_MODEL, car.getModel().get()); - assertEquals(TEST_CAR_PRICE, car.getPrice().get()); + var carProperties = Map.of( + Property.MODEL.toString(), TEST_CAR_MODEL, + Property.PRICE.toString(), TEST_CAR_PRICE, + Property.PARTS.toString(), List.of(Map.of(), Map.of()) + ); + var car = new Car(carProperties); + assertEquals(TEST_CAR_MODEL, car.getModel().orElseThrow()); + assertEquals(TEST_CAR_PRICE, car.getPrice().orElseThrow()); assertEquals(2, car.getParts().count()); } diff --git a/abstract-factory/README.md b/abstract-factory/README.md index 0bf86dfa6a91..6a840f316974 100644 --- a/abstract-factory/README.md +++ b/abstract-factory/README.md @@ -5,9 +5,7 @@ folder: abstract-factory permalink: /patterns/abstract-factory/ categories: Creational tags: - - Java - - Gang Of Four - - Difficulty-Intermediate + - Gang of Four --- ## Also known as @@ -109,10 +107,10 @@ public class OrcKingdomFactory implements KingdomFactory { Now we have our abstract factory that lets us make family of related objects i.e. Elven kingdom factory creates Elven castle, king and army etc. ```java -KingdomFactory factory = new ElfKingdomFactory(); -Castle castle = factory.createCastle(); -King king = factory.createKing(); -Army army = factory.createArmy(); +var factory = new ElfKingdomFactory(); +var castle = factory.createCastle(); +var king = factory.createKing(); +var army = factory.createArmy(); castle.getDescription(); // Output: This is the Elven castle! king.getDescription(); // Output: This is the Elven king! @@ -143,7 +141,7 @@ public static class FactoryMaker { } public static void main(String[] args) { - App app = new App(); + var app = new App(); LOGGER.info("Elf Kingdom"); app.createKingdom(FactoryMaker.makeFactory(KingdomType.ELF)); @@ -157,6 +155,9 @@ public static void main(String[] args) { } ``` +## Class diagram +![alt text](./etc/abstract-factory.urm.png "Abstract Factory class diagram") + ## Applicability Use the Abstract Factory pattern when @@ -169,24 +170,25 @@ Use the Abstract Factory pattern when * you need a run-time value to construct a particular dependency * you want to decide which product to call from a family at runtime. * you need to supply one or more parameters only known at run-time before you can resolve a dependency. +* when you need consistency among products +* you don’t want to change existing code when adding new products or families of products to the program. ## Use Cases: * Selecting to call the appropriate implementation of FileSystemAcmeService or DatabaseAcmeService or NetworkAcmeService at runtime. * Unit test case writing becomes much easier +* UI tools for different OS ## Consequences: * Dependency injection in java hides the service class dependencies that can lead to runtime errors that would have been caught at compile time. +* While the pattern is great when creating predefined objects, adding the new ones might be challenging. +* The code may become more complicated than it should be, since a lot of new interfaces and classes are introduced along with the pattern. ## Tutorial * [Abstract Factory Pattern Tutorial](https://www.journaldev.com/1418/abstract-factory-design-pattern-in-java) -## Presentations - -* [Abstract Factory Pattern](etc/presentation.html) - ## Real world examples diff --git a/abstract-factory/etc/abstract-factory.urm.png b/abstract-factory/etc/abstract-factory.urm.png new file mode 100644 index 000000000000..836858a2c652 Binary files /dev/null and b/abstract-factory/etc/abstract-factory.urm.png differ diff --git a/abstract-factory/etc/abstract-factory.urm.puml b/abstract-factory/etc/abstract-factory.urm.puml new file mode 100644 index 000000000000..999091ef54f6 --- /dev/null +++ b/abstract-factory/etc/abstract-factory.urm.puml @@ -0,0 +1,101 @@ +@startuml +package com.iluwatar.abstractfactory { + class App { + - LOGGER : Logger {static} + - army : Army + - castle : Castle + - king : King + + App() + + createKingdom(factory : KingdomFactory) + + getArmy() : Army + ~ getArmy(factory : KingdomFactory) : Army + + getCastle() : Castle + ~ getCastle(factory : KingdomFactory) : Castle + + getKing() : King + ~ getKing(factory : KingdomFactory) : King + + main(args : String[]) {static} + - setArmy(army : Army) + - setCastle(castle : Castle) + - setKing(king : King) + } + class FactoryMaker { + + FactoryMaker() + + makeFactory(type : KingdomType) : KingdomFactory {static} + } + enum KingdomType { + + ELF {static} + + ORC {static} + + valueOf(name : String) : KingdomType {static} + + values() : KingdomType[] {static} + } + interface Army { + + getDescription() : String {abstract} + } + interface Castle { + + getDescription() : String {abstract} + } + class ElfArmy { + ~ DESCRIPTION : String {static} + + ElfArmy() + + getDescription() : String + } + class ElfCastle { + ~ DESCRIPTION : String {static} + + ElfCastle() + + getDescription() : String + } + class ElfKing { + ~ DESCRIPTION : String {static} + + ElfKing() + + getDescription() : String + } + class ElfKingdomFactory { + + ElfKingdomFactory() + + createArmy() : Army + + createCastle() : Castle + + createKing() : King + } + interface King { + + getDescription() : String {abstract} + } + interface KingdomFactory { + + createArmy() : Army {abstract} + + createCastle() : Castle {abstract} + + createKing() : King {abstract} + } + class OrcArmy { + ~ DESCRIPTION : String {static} + + OrcArmy() + + getDescription() : String + } + class OrcCastle { + ~ DESCRIPTION : String {static} + + OrcCastle() + + getDescription() : String + } + class OrcKing { + ~ DESCRIPTION : String {static} + + OrcKing() + + getDescription() : String + } + class OrcKingdomFactory { + + OrcKingdomFactory() + + createArmy() : Army + + createCastle() : Castle + + createKing() : King + } +} +KingdomType ..+ FactoryMaker +App --> "-castle" Castle +FactoryMaker ..+ App +App --> "-king" King +App --> "-army" Army +ElfArmy ..|> Army +ElfCastle ..|> Castle +ElfKing ..|> King +ElfKingdomFactory ..|> KingdomFactory +OrcArmy ..|> Army +OrcCastle ..|> Castle +OrcKing ..|> King +OrcKingdomFactory ..|> KingdomFactory +@enduml \ No newline at end of file diff --git a/abstract-factory/etc/diagram1.png b/abstract-factory/etc/diagram1.png deleted file mode 100644 index e9c3c84c212d..000000000000 Binary files a/abstract-factory/etc/diagram1.png and /dev/null differ diff --git a/abstract-factory/etc/diagram2.png b/abstract-factory/etc/diagram2.png deleted file mode 100644 index 2ab52453533f..000000000000 Binary files a/abstract-factory/etc/diagram2.png and /dev/null differ diff --git a/abstract-factory/etc/presentation.html b/abstract-factory/etc/presentation.html deleted file mode 100644 index dc1c302b5e0d..000000000000 --- a/abstract-factory/etc/presentation.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - Design Patterns - Abstract Factory Presentation - - - - - - - - - \ No newline at end of file diff --git a/abstract-factory/pom.xml b/abstract-factory/pom.xml index 57bbdb5bbb98..614b26232d99 100644 --- a/abstract-factory/pom.xml +++ b/abstract-factory/pom.xml @@ -1,47 +1,56 @@ - - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.21.0-SNAPSHOT - - abstract-factory - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + abstract-factory + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.abstractfactory.App + + + + + + + + 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 4d6043abc007..e158ece74b48 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,27 +20,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; +import com.iluwatar.abstractfactory.App.FactoryMaker.KingdomType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.iluwatar.abstractfactory.App.FactoryMaker.KingdomType; - /** - * - * 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 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. */ public class App { @@ -51,14 +50,14 @@ public class App { private Army army; /** - * Creates kingdom + * Creates kingdom. */ public void createKingdom(final KingdomFactory factory) { setKing(factory.createKing()); setCastle(factory.createCastle()); setArmy(factory.createArmy()); } - + King getKing(final KingdomFactory factory) { return factory.createKing(); } @@ -70,7 +69,7 @@ public King getKing() { private void setKing(final King king) { this.king = king; } - + Castle getCastle(final KingdomFactory factory) { return factory.createCastle(); } @@ -82,7 +81,7 @@ public Castle getCastle() { private void setCastle(final Castle castle) { this.castle = castle; } - + Army getArmy(final KingdomFactory factory) { return factory.createArmy(); } @@ -124,13 +123,12 @@ public static KingdomFactory makeFactory(KingdomType type) { /** * Program entry point. - * - * @param args - * command line args + * + * @param args command line args */ public static void main(String[] args) { - App app = new App(); + var app = new App(); LOGGER.info("Elf Kingdom"); app.createKingdom(FactoryMaker.makeFactory(KingdomType.ELF)); diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Army.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Army.java index 3d107dd3479e..51f69a82256c 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Army.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Army.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; /** - * - * Army interface - * + * Army interface. */ public interface Army { diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Castle.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Castle.java index 837952d0d364..c75eb32ef333 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Castle.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Castle.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; /** - * - * Castle interface - * + * Castle interface. */ public interface Castle { diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfArmy.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfArmy.java index f9687073df29..6d2da97ccfd9 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfArmy.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfArmy.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; /** - * - * ElfArmy - * + * ElfArmy. */ public class ElfArmy implements Army { diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfCastle.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfCastle.java index 108bdc8d315a..5f2b6ed2a5e8 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfCastle.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfCastle.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; /** - * - * ElfCastle - * + * ElfCastle. */ public class ElfCastle implements Castle { diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKing.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKing.java index f6e400bc0d8c..f7c4c614667f 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKing.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKing.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; /** - * - * ElfKing - * + * ElfKing. */ public class ElfKing implements King { diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKingdomFactory.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKingdomFactory.java index 31cfddb70ff8..4493d2d08a39 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKingdomFactory.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKingdomFactory.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,23 +20,25 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; /** - * * ElfKingdomFactory concrete factory. - * */ public class ElfKingdomFactory implements KingdomFactory { + @Override public Castle createCastle() { return new ElfCastle(); } + @Override public King createKing() { return new ElfKing(); } + @Override public Army createArmy() { return new ElfArmy(); } diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/King.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/King.java index bffc994d24cb..97e5f676f29f 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/King.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/King.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; /** - * - * King interface - * + * King interface. */ public interface King { diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/KingdomFactory.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/KingdomFactory.java index fb3434fae136..a72dbf78cbe0 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/KingdomFactory.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/KingdomFactory.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; /** - * * KingdomFactory factory interface. - * */ public interface KingdomFactory { diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcArmy.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcArmy.java index 31552f4301db..ae0bd5c3e24a 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcArmy.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcArmy.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; /** - * - * OrcArmy - * + * OrcArmy. */ public class OrcArmy implements Army { diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcCastle.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcCastle.java index e72da4a77b50..458a61bf9920 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcCastle.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcCastle.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; /** - * - * OrcCastle - * + * OrcCastle. */ public class OrcCastle implements Castle { diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKing.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKing.java index f48115f89d3a..f73ada9b080e 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKing.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKing.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; /** - * - * OrcKing - * + * OrcKing. */ public class OrcKing implements King { diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKingdomFactory.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKingdomFactory.java index 5b2ba3a30fc9..ae7c744be72b 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKingdomFactory.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKingdomFactory.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,23 +20,25 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; /** - * * OrcKingdomFactory concrete factory. - * */ public class OrcKingdomFactory implements KingdomFactory { + @Override public Castle createCastle() { return new OrcCastle(); } + @Override public King createKing() { return new OrcKing(); } + @Override public Army createArmy() { return new OrcArmy(); } diff --git a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java index 5940da6c1af3..be83cc315627 100644 --- a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java +++ b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -27,12 +28,11 @@ import com.iluwatar.abstractfactory.App.FactoryMaker; import com.iluwatar.abstractfactory.App.FactoryMaker.KingdomType; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** - * Test for abstract factory + * Test for abstract factory. */ public class AbstractFactoryTest { @@ -48,30 +48,30 @@ public void setUp() { @Test public void king() { - final King elfKing = app.getKing(elfFactory); + final var elfKing = app.getKing(elfFactory); assertTrue(elfKing instanceof ElfKing); assertEquals(ElfKing.DESCRIPTION, elfKing.getDescription()); - final King orcKing = app.getKing(orcFactory); + final var orcKing = app.getKing(orcFactory); assertTrue(orcKing instanceof OrcKing); assertEquals(OrcKing.DESCRIPTION, orcKing.getDescription()); } @Test public void castle() { - final Castle elfCastle = app.getCastle(elfFactory); + final var elfCastle = app.getCastle(elfFactory); assertTrue(elfCastle instanceof ElfCastle); assertEquals(ElfCastle.DESCRIPTION, elfCastle.getDescription()); - final Castle orcCastle = app.getCastle(orcFactory); + final var orcCastle = app.getCastle(orcFactory); assertTrue(orcCastle instanceof OrcCastle); assertEquals(OrcCastle.DESCRIPTION, orcCastle.getDescription()); } @Test public void army() { - final Army elfArmy = app.getArmy(elfFactory); + final var elfArmy = app.getArmy(elfFactory); assertTrue(elfArmy instanceof ElfArmy); assertEquals(ElfArmy.DESCRIPTION, elfArmy.getDescription()); - final Army orcArmy = app.getArmy(orcFactory); + final var orcArmy = app.getArmy(orcFactory); assertTrue(orcArmy instanceof OrcArmy); assertEquals(OrcArmy.DESCRIPTION, orcArmy.getDescription()); } @@ -79,9 +79,9 @@ public void army() { @Test public void createElfKingdom() { app.createKingdom(elfFactory); - final King king = app.getKing(); - final Castle castle = app.getCastle(); - final Army army = app.getArmy(); + final var king = app.getKing(); + final var castle = app.getCastle(); + final var army = app.getArmy(); assertTrue(king instanceof ElfKing); assertEquals(ElfKing.DESCRIPTION, king.getDescription()); assertTrue(castle instanceof ElfCastle); @@ -93,9 +93,9 @@ public void createElfKingdom() { @Test public void createOrcKingdom() { app.createKingdom(orcFactory); - final King king = app.getKing(); - final Castle castle = app.getCastle(); - final Army army = app.getArmy(); + final var king = app.getKing(); + final var castle = app.getCastle(); + final var army = app.getArmy(); assertTrue(king instanceof OrcKing); assertEquals(OrcKing.DESCRIPTION, king.getDescription()); assertTrue(castle instanceof OrcCastle); diff --git a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java index 80c2815dcc00..4036cc9b8e34 100644 --- a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java +++ b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,19 +20,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.abstractfactory; import org.junit.jupiter.api.Test; -import java.io.IOException; - /** * Tests that Abstract Factory example runs without errors. */ public class AppTest { @Test - public void test() throws IOException { - String[] args = {}; - App.main(args); + public void test() { + App.main(new String[]{}); } } diff --git a/acyclic-visitor/README.md b/acyclic-visitor/README.md index 939fe38b97d1..f293e4393f20 100644 --- a/acyclic-visitor/README.md +++ b/acyclic-visitor/README.md @@ -5,17 +5,18 @@ folder: acyclic-visitor permalink: /patterns/acyclic-visitor/ categories: Behavioral tags: - - Java - - Difficulty-Intermediate + - Extensibility --- -![alt text](./etc/acyclic-visitor.png "Acyclic Visitor") - ## Intent Allow new functions to be added to existing class hierarchies without affecting those hierarchies, and without creating the troublesome dependency cycles that are inherent to the GOF VISITOR Pattern. +## Class diagram +![alt text](./etc/acyclic-visitor.png "Acyclic Visitor") + ## Applicability This pattern can be used: + * When you need to add a new function to an existing hierarchy without the need to alter or affect that hierarchy. * When there are functions that operate upon a hierarchy, but which do not belong in the hierarchy itself. e.g. the ConfigureForDOS / ConfigureForUnix / ConfigureForX issue. * When you need to perform very different operations on an object depending upon its type. @@ -24,16 +25,18 @@ This pattern can be used: ## Consequences The good: + * No dependency cycles between class hierarchies. * No need to recompile all the visitors if a new one is added. * Does not cause compilation failure in existing visitors if class hierarchy has a new member. The bad: + * Violates the principle of least surprise or Liskov's Substitution principle by showing that it can accept all visitors but actually only being interested in particular visitors. * Parallel hierarchy of visitors has to be created for all members in visitable class hierarchy. ## Related patterns -* [Visitor Pattern](../visitor/README.md) +* [Visitor Pattern](../visitor/) ## Credits -* [Acyclic Visitor](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf) \ No newline at end of file +* [Acyclic Visitor](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf) diff --git a/acyclic-visitor/etc/acyclic-visitor.urm.puml b/acyclic-visitor/etc/acyclic-visitor.urm.puml new file mode 100644 index 000000000000..e67bbde4407c --- /dev/null +++ b/acyclic-visitor/etc/acyclic-visitor.urm.puml @@ -0,0 +1,53 @@ +@startuml +package com.iluwatar.acyclicvisitor { + interface AllModemVisitor { + } + class App { + + App() + + main(args : String[]) {static} + } + class ConfigureForDosVisitor { + - LOGGER : Logger {static} + + ConfigureForDosVisitor() + + visit(hayes : Hayes) + + visit(zoom : Zoom) + } + class ConfigureForUnixVisitor { + - LOGGER : Logger {static} + + ConfigureForUnixVisitor() + + visit(zoom : Zoom) + } + class Hayes { + - LOGGER : Logger {static} + + Hayes() + + accept(modemVisitor : ModemVisitor) + + toString() : String + } + interface HayesVisitor { + + visit(Hayes) {abstract} + } + abstract class Modem { + + Modem() + + accept(ModemVisitor) {abstract} + } + interface ModemVisitor { + } + class Zoom { + - LOGGER : Logger {static} + + Zoom() + + accept(modemVisitor : ModemVisitor) + + toString() : String + } + interface ZoomVisitor { + + visit(Zoom) {abstract} + } +} +AllModemVisitor --|> ZoomVisitor +AllModemVisitor --|> HayesVisitor +ConfigureForDosVisitor ..|> AllModemVisitor +ConfigureForUnixVisitor ..|> ZoomVisitor +Hayes --|> Modem +HayesVisitor --|> ModemVisitor +Zoom --|> Modem +ZoomVisitor --|> ModemVisitor +@enduml \ No newline at end of file diff --git a/acyclic-visitor/pom.xml b/acyclic-visitor/pom.xml index 8ce2b1e27586..2179774c2eea 100644 --- a/acyclic-visitor/pom.xml +++ b/acyclic-visitor/pom.xml @@ -1,38 +1,29 @@ - + 4.0.0 - + com.iluwatar java-design-patterns - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT - + acyclic-visitor @@ -57,15 +48,10 @@ test - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - + org.junit.jupiter + junit-jupiter-engine + test + org.mockito mockito-all @@ -73,4 +59,25 @@ test + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.acyclicvisitor.App + + + + + + + + diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/AllModemVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/AllModemVisitor.java index 749ea00a25fb..354c4db74962 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/AllModemVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/AllModemVisitor.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; /** - * All ModemVisitor interface extends all visitor interfaces. This interface - * provides ease of use when a visitor needs to visit all modem types. + * All ModemVisitor interface extends all visitor interfaces. This interface provides ease of use + * when a visitor needs to visit all modem types. */ -public interface AllModemVisitor extends ZoomVisitor, HayesVisitor{ +public interface AllModemVisitor extends ZoomVisitor, HayesVisitor { } diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java index 06e4bd2f255d..866abc3b736a 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,34 +20,33 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; /** - * The Acyclic Visitor pattern allows new functions to be added to existing class - * hierarchies without affecting those hierarchies, and without creating the dependency - * cycles that are inherent to the GoF Visitor pattern, by making the Visitor base class - * degenerate - *

- * In this example the visitor base class is {@link ModemVisitor}. The base class of the - * visited hierarchy is {@link Modem} and has two children {@link Hayes} and {@link Zoom} - * each one having its own visitor interface {@link HayesVisitor} and {@link ZoomVisitor} - * respectively. {@link ConfigureForUnixVisitor} and {@link ConfigureForDosVisitor} - * implement each derivative's visit method only if it is required + * The Acyclic Visitor pattern allows new functions to be added to existing class hierarchies + * without affecting those hierarchies, and without creating the dependency cycles that are inherent + * to the GoF Visitor pattern, by making the Visitor base class degenerate + * + *

In this example the visitor base class is {@link ModemVisitor}. The base class of the visited + * hierarchy is {@link Modem} and has two children {@link Hayes} and {@link Zoom} each one having + * its own visitor interface {@link HayesVisitor} and {@link ZoomVisitor} respectively. {@link + * ConfigureForUnixVisitor} and {@link ConfigureForDosVisitor} implement each derivative's visit + * method only if it is required */ public class App { - + /** - * Program's entry point + * Program's entry point. */ - - public static void main(String[] args) { - ConfigureForUnixVisitor conUnix = new ConfigureForUnixVisitor(); - ConfigureForDosVisitor conDos = new ConfigureForDosVisitor(); - - Zoom zoom = new Zoom(); - Hayes hayes = new Hayes(); - - hayes.accept(conDos); // Hayes modem with Unix configurator + public static void main(String[] args) { + var conUnix = new ConfigureForUnixVisitor(); + var conDos = new ConfigureForDosVisitor(); + + var zoom = new Zoom(); + var hayes = new Hayes(); + + hayes.accept(conDos); // Hayes modem with Dos configurator zoom.accept(conDos); // Zoom modem with Dos configurator hayes.accept(conUnix); // Hayes modem with Unix configurator zoom.accept(conUnix); // Zoom modem with Unix configurator diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java index a6d66b1db477..f9df385295d3 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,23 +20,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * ConfigureForDosVisitor class implements both zoom's and hayes' visit method - * for Dos manufacturer + * ConfigureForDosVisitor class implements both zoom's and hayes' visit method for Dos + * manufacturer. */ public class ConfigureForDosVisitor implements AllModemVisitor { private static final Logger LOGGER = LoggerFactory.getLogger(ConfigureForDosVisitor.class); + @Override public void visit(Hayes hayes) { LOGGER.info(hayes + " used with Dos configurator."); } + @Override public void visit(Zoom zoom) { LOGGER.info(zoom + " used with Dos configurator."); } diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java index c3546012166d..3d14eff8fcee 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,20 +20,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * ConfigureForUnixVisitor class implements zoom's visit method for Unix - * manufacturer, unlike traditional visitor pattern, this class may selectively implement - * visit for other modems. + * ConfigureForUnixVisitor class implements zoom's visit method for Unix manufacturer, unlike + * traditional visitor pattern, this class may selectively implement visit for other modems. */ public class ConfigureForUnixVisitor implements ZoomVisitor { private static final Logger LOGGER = LoggerFactory.getLogger(ConfigureForUnixVisitor.class); + @Override public void visit(Zoom zoom) { LOGGER.info(zoom + " used with Unix configurator."); } diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java index 3ee76a812b49..b49a6234cfd6 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,20 +20,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Hayes class implements its accept method + * Hayes class implements its accept method. */ public class Hayes extends Modem { - + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigureForDosVisitor.class); /** - * Accepts all visitors but honors only HayesVisitor + * Accepts all visitors but honors only HayesVisitor. */ @Override public void accept(ModemVisitor modemVisitor) { @@ -44,10 +45,9 @@ public void accept(ModemVisitor modemVisitor) { } } - + /** - * Hayes' modem's toString - * method + * Hayes' modem's toString method. */ @Override public String toString() { diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java index 1731622f201a..59527d57b2ce 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,10 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; /** - * HayesVisitor interface + * HayesVisitor interface. */ public interface HayesVisitor extends ModemVisitor { void visit(Hayes hayes); diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java index 57207a466fa4..201712dd1398 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,10 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; /** - * Modem abstract class + * Modem abstract class. */ public abstract class Modem { public abstract void accept(ModemVisitor modemVisitor); diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ModemVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ModemVisitor.java index 7189a6bba909..b4058f237d22 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ModemVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ModemVisitor.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; /** - * ModemVisitor interface does not contain any visit methods so that it does not - * depend on the visited hierarchy. Each derivative's visit method is declared in - * its own visitor interface + * ModemVisitor interface does not contain any visit methods so that it does not depend on the + * visited hierarchy. Each derivative's visit method is declared in its own visitor interface */ public interface ModemVisitor { // Visitor is a degenerate base class for all visitors. diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java index ee054851e514..3fbaa38dfce6 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,33 +20,33 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Zoom class implements its accept method + * Zoom class implements its accept method. */ public class Zoom extends Modem { - + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigureForDosVisitor.class); /** - * Accepts all visitors but honors only ZoomVisitor + * Accepts all visitors but honors only ZoomVisitor. */ @Override public void accept(ModemVisitor modemVisitor) { - if (modemVisitor instanceof ZoomVisitor) { + if (modemVisitor instanceof ZoomVisitor) { ((ZoomVisitor) modemVisitor).visit(this); } else { LOGGER.info("Only ZoomVisitor is allowed to visit Zoom modem"); } } - + /** - * Zoom modem's toString - * method + * Zoom modem's toString method. */ @Override public String toString() { diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java index 6a47867fc81b..fd54d8b21ca7 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,10 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; /** - * ZoomVisitor interface + * ZoomVisitor interface. */ public interface ZoomVisitor extends ModemVisitor { void visit(Zoom zoom); diff --git a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java index 056401ca2806..4b9a7ec6c1f7 100644 --- a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java +++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,20 +20,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; import org.junit.jupiter.api.Test; -import com.iluwatar.acyclicvisitor.App; - /** * Tests that the Acyclic Visitor example runs without errors. */ public class AppTest { - + @Test public void test() { - String[] args = {}; - App.main(args); + App.main(new String[]{}); } } \ No newline at end of file diff --git a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitorTest.java b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitorTest.java index e8d6ba079e42..8847a131e605 100644 --- a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitorTest.java +++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitorTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,22 +20,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; -import static org.mockito.Mockito.mock; import static uk.org.lidalia.slf4jext.Level.INFO; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; - -import com.iluwatar.acyclicvisitor.ConfigureForDosVisitor; -import com.iluwatar.acyclicvisitor.Hayes; -import com.iluwatar.acyclicvisitor.HayesVisitor; -import com.iluwatar.acyclicvisitor.Zoom; -import com.iluwatar.acyclicvisitor.ZoomVisitor; - import uk.org.lidalia.slf4jtest.TestLogger; import uk.org.lidalia.slf4jtest.TestLoggerFactory; @@ -44,28 +37,30 @@ */ public class ConfigureForDosVisitorTest { - TestLogger logger = TestLoggerFactory.getTestLogger(ConfigureForDosVisitor.class); + private TestLogger logger = TestLoggerFactory.getTestLogger(ConfigureForDosVisitor.class); @Test public void testVisitForZoom() { - ConfigureForDosVisitor conDos = new ConfigureForDosVisitor(); - Zoom zoom = new Zoom(); + var conDos = new ConfigureForDosVisitor(); + var zoom = new Zoom(); conDos.visit(zoom); - assertThat(logger.getLoggingEvents()).extracting("level", "message").contains( - tuple(INFO, zoom + " used with Dos configurator.")); + assertThat(logger.getLoggingEvents()) + .extracting("level", "message") + .contains(tuple(INFO, zoom + " used with Dos configurator.")); } @Test public void testVisitForHayes() { - ConfigureForDosVisitor conDos = new ConfigureForDosVisitor(); - Hayes hayes = new Hayes(); + var conDos = new ConfigureForDosVisitor(); + var hayes = new Hayes(); conDos.visit(hayes); - assertThat(logger.getLoggingEvents()).extracting("level", "message").contains( - tuple(INFO, hayes + " used with Dos configurator.")); + assertThat(logger.getLoggingEvents()) + .extracting("level", "message") + .contains(tuple(INFO, hayes + " used with Dos configurator.")); } @AfterEach diff --git a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitorTest.java b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitorTest.java index 1e7724c5eec6..32067ad385a3 100644 --- a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitorTest.java +++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitorTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; import static org.assertj.core.api.Assertions.assertThat; @@ -46,12 +47,13 @@ public void clearLoggers() { @Test public void testVisitForZoom() { - ConfigureForUnixVisitor conUnix = new ConfigureForUnixVisitor(); - Zoom zoom = new Zoom(); + var conUnix = new ConfigureForUnixVisitor(); + var zoom = new Zoom(); conUnix.visit(zoom); - assertThat(LOGGER.getLoggingEvents()).extracting("level", "message").contains( - tuple(INFO, zoom + " used with Unix configurator.")); + assertThat(LOGGER.getLoggingEvents()) + .extracting("level", "message") + .contains(tuple(INFO, zoom + " used with Unix configurator.")); } } diff --git a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java index ba5139bf6a1f..308dd5879329 100644 --- a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java +++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; import static org.mockito.Matchers.eq; @@ -29,11 +30,6 @@ import org.junit.jupiter.api.Test; -import com.iluwatar.acyclicvisitor.ConfigureForDosVisitor; -import com.iluwatar.acyclicvisitor.ConfigureForUnixVisitor; -import com.iluwatar.acyclicvisitor.Hayes; -import com.iluwatar.acyclicvisitor.HayesVisitor; - /** * Hayes test class */ @@ -41,8 +37,8 @@ public class HayesTest { @Test public void testAcceptForDos() { - Hayes hayes = new Hayes(); - ConfigureForDosVisitor mockVisitor = mock(ConfigureForDosVisitor.class); + var hayes = new Hayes(); + var mockVisitor = mock(ConfigureForDosVisitor.class); hayes.accept(mockVisitor); verify((HayesVisitor)mockVisitor).visit(eq(hayes)); @@ -50,8 +46,8 @@ public void testAcceptForDos() { @Test public void testAcceptForUnix() { - Hayes hayes = new Hayes(); - ConfigureForUnixVisitor mockVisitor = mock(ConfigureForUnixVisitor.class); + var hayes = new Hayes(); + var mockVisitor = mock(ConfigureForUnixVisitor.class); hayes.accept(mockVisitor); diff --git a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ZoomTest.java b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ZoomTest.java index 22391fb2ff8f..2dcfcfbbbf80 100644 --- a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ZoomTest.java +++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ZoomTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.acyclicvisitor; @@ -29,11 +30,6 @@ import org.junit.jupiter.api.Test; -import com.iluwatar.acyclicvisitor.ConfigureForDosVisitor; -import com.iluwatar.acyclicvisitor.ConfigureForUnixVisitor; -import com.iluwatar.acyclicvisitor.Zoom; -import com.iluwatar.acyclicvisitor.ZoomVisitor; - /** * Zoom test class */ @@ -41,8 +37,8 @@ public class ZoomTest { @Test public void testAcceptForDos() { - Zoom zoom = new Zoom(); - ConfigureForDosVisitor mockVisitor = mock(ConfigureForDosVisitor.class); + var zoom = new Zoom(); + var mockVisitor = mock(ConfigureForDosVisitor.class); zoom.accept(mockVisitor); verify((ZoomVisitor)mockVisitor).visit(eq(zoom)); @@ -50,8 +46,8 @@ public void testAcceptForDos() { @Test public void testAcceptForUnix() { - Zoom zoom = new Zoom(); - ConfigureForUnixVisitor mockVisitor = mock(ConfigureForUnixVisitor.class); + var zoom = new Zoom(); + var mockVisitor = mock(ConfigureForUnixVisitor.class); zoom.accept(mockVisitor); verify((ZoomVisitor)mockVisitor).visit(eq(zoom)); diff --git a/adapter/README.md b/adapter/README.md index e943baba52fd..a8a0214be83c 100644 --- a/adapter/README.md +++ b/adapter/README.md @@ -5,9 +5,7 @@ folder: adapter permalink: /patterns/adapter/ categories: Structural tags: - - Java - - Gang Of Four - - Difficulty-Beginner + - Gang of Four --- ## Also known as @@ -56,15 +54,14 @@ public class FishingBoat { And captain expects an implementation of `RowingBoat` interface to be able to move ```java -public class Captain implements RowingBoat { +public class Captain { private RowingBoat rowingBoat; - + // default constructor and setter for rowingBoat public Captain(RowingBoat rowingBoat) { this.rowingBoat = rowingBoat; } - @Override public void row() { rowingBoat.row(); } @@ -94,10 +91,13 @@ public class FishingBoatAdapter implements RowingBoat { And now the `Captain` can use the `FishingBoat` to escape the pirates. ```java -Captain captain = new Captain(new FishingBoatAdapter()); +var captain = new Captain(new FishingBoatAdapter()); captain.row(); ``` +## Class diagram +![alt text](./etc/adapter.urm.png "Adapter class diagram") + ## Applicability Use the Adapter pattern when diff --git a/adapter/etc/adapter.urm.png b/adapter/etc/adapter.urm.png new file mode 100644 index 000000000000..341ad67699d9 Binary files /dev/null and b/adapter/etc/adapter.urm.png differ diff --git a/adapter/etc/adapter.urm.puml b/adapter/etc/adapter.urm.puml new file mode 100644 index 000000000000..1277cbb87125 --- /dev/null +++ b/adapter/etc/adapter.urm.puml @@ -0,0 +1,31 @@ +@startuml +package com.iluwatar.adapter { + class App { + - App() + + main(args : String[]) {static} + } + class Captain { + - rowingBoat : RowingBoat + + Captain() + + Captain(boat : RowingBoat) + ~ row() + ~ setRowingBoat(boat : RowingBoat) + } + ~class FishingBoat { + - LOGGER : Logger {static} + ~ FishingBoat() + ~ sail() + } + class FishingBoatAdapter { + - boat : FishingBoat + + FishingBoatAdapter() + + row() + } + interface RowingBoat { + + row() {abstract} + } +} +FishingBoatAdapter --> "-boat" FishingBoat +Captain --> "-rowingBoat" RowingBoat +FishingBoatAdapter ..|> RowingBoat +@enduml \ No newline at end of file diff --git a/adapter/pom.xml b/adapter/pom.xml index b417432c2aab..4c725def8953 100644 --- a/adapter/pom.xml +++ b/adapter/pom.xml @@ -1,52 +1,61 @@ - - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.21.0-SNAPSHOT - - adapter - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.mockito - mockito-core - test - - + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + adapter + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.adapter.App + + + + + + + + diff --git a/adapter/src/main/java/com/iluwatar/adapter/App.java b/adapter/src/main/java/com/iluwatar/adapter/App.java index a624cc38f264..4e3755fb27f2 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/App.java +++ b/adapter/src/main/java/com/iluwatar/adapter/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.adapter; /** @@ -28,33 +29,33 @@ * 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 FishingBoatAdapter}) converts the interface of the adaptee class ( - * {@link FishingBoat}) into a suitable one expected by the client ( {@link RowingBoat} ). - * - *

- * The story of this implementation is this.
- * Pirates are coming! we need a {@link RowingBoat} to flee! 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 rowing boat which he can operate. The spec is in {@link RowingBoat}. We will - * use the Adapter pattern to reuse {@link FishingBoat}. + *

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

The story of this implementation is this.
Pirates are coming! we need a {@link + * RowingBoat} to flee! 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 rowing boat which he can + * operate. The spec is in {@link RowingBoat}. We will use the Adapter pattern to reuse {@link + * FishingBoat}. */ -public class App { +public final class App { + + private App() { + } /** * Program entry point. * * @param args command line args */ - public static void main(String[] args) { - // The captain can only operate rowing boats but with adapter he is able to use fishing boats as well - Captain captain = new Captain(new FishingBoatAdapter()); + public static void main(final String[] args) { + // The captain can only operate rowing boats but with adapter he is able to + // use fishing boats as well + var captain = new Captain(new FishingBoatAdapter()); captain.row(); } } diff --git a/adapter/src/main/java/com/iluwatar/adapter/Captain.java b/adapter/src/main/java/com/iluwatar/adapter/Captain.java index 8593ca321975..b83b13429660 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/Captain.java +++ b/adapter/src/main/java/com/iluwatar/adapter/Captain.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,27 +20,28 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.adapter; /** - * The Captain uses {@link RowingBoat} to sail.
- * This is the client in the pattern. + * The Captain uses {@link RowingBoat} to sail.
This is the client in the pattern. */ -public class Captain { +public final class Captain { private RowingBoat rowingBoat; - public Captain() {} + public Captain() { + } - public Captain(RowingBoat rowingBoat) { - this.rowingBoat = rowingBoat; + public Captain(final RowingBoat boat) { + this.rowingBoat = boat; } - public void setRowingBoat(RowingBoat rowingBoat) { - this.rowingBoat = rowingBoat; + void setRowingBoat(final RowingBoat boat) { + this.rowingBoat = boat; } - public void row() { + void row() { rowingBoat.row(); } diff --git a/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java b/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java index c46814d18f16..123006c46d42 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java +++ b/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,22 +20,22 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.adapter; +import static org.slf4j.LoggerFactory.getLogger; + import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** - * - * Device class (adaptee in the pattern). We want to reuse this class. - * Fishing boat moves by sailing. - * + * Device class (adaptee in the pattern). We want to reuse this class. Fishing boat moves by + * sailing. */ -public class FishingBoat { +final class FishingBoat { - private static final Logger LOGGER = LoggerFactory.getLogger(FishingBoat.class); + private static final Logger LOGGER = getLogger(FishingBoat.class); - public void sail() { + void sail() { LOGGER.info("The fishing boat is sailing"); } diff --git a/adapter/src/main/java/com/iluwatar/adapter/FishingBoatAdapter.java b/adapter/src/main/java/com/iluwatar/adapter/FishingBoatAdapter.java index 1e758e917040..5ccde5c53bcb 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/FishingBoatAdapter.java +++ b/adapter/src/main/java/com/iluwatar/adapter/FishingBoatAdapter.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,13 +20,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.adapter; /** - * * Adapter class. Adapts the interface of the device ({@link FishingBoat}) into {@link RowingBoat} * interface expected by the client ({@link Captain}). - * */ public class FishingBoatAdapter implements RowingBoat { @@ -36,8 +35,7 @@ public FishingBoatAdapter() { boat = new FishingBoat(); } - @Override - public void row() { + public final void row() { boat.sail(); } } diff --git a/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java b/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java index a9ca9ad39717..908036a3f19c 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java +++ b/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.adapter; /** - * The interface expected by the client.
- * A rowing boat is rowed to move. - * + * The interface expected by the client.
A rowing boat is rowed to move. */ public interface RowingBoat { diff --git a/adapter/src/main/java/com/iluwatar/adapter/package-info.java b/adapter/src/main/java/com/iluwatar/adapter/package-info.java new file mode 100644 index 000000000000..d036d86ddb47 --- /dev/null +++ b/adapter/src/main/java/com/iluwatar/adapter/package-info.java @@ -0,0 +1,24 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.adapter; diff --git a/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java b/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java index 82a951e30bdc..f87073b234f2 100644 --- a/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java +++ b/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,20 +20,19 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.adapter; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import java.util.HashMap; import java.util.Map; - -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; /** * Test class - * */ public class AdapterPatternTest { @@ -50,29 +49,29 @@ public class AdapterPatternTest { public void setup() { beans = new HashMap<>(); - FishingBoatAdapter fishingBoatAdapter = spy(new FishingBoatAdapter()); + var fishingBoatAdapter = spy(new FishingBoatAdapter()); beans.put(FISHING_BEAN, fishingBoatAdapter); - Captain captain = new Captain(); + var captain = new Captain(); captain.setRowingBoat((FishingBoatAdapter) beans.get(FISHING_BEAN)); beans.put(ROWING_BEAN, captain); } /** - * This test asserts that when we use the row() method on a captain bean(client), it is - * internally calling sail method on the fishing boat object. The Adapter ({@link FishingBoatAdapter} - * ) converts the interface of the target class ( {@link FishingBoat}) into a suitable one - * expected by the client ({@link Captain} ). + * This test asserts that when we use the row() method on a captain bean(client), it is internally + * calling sail method on the fishing boat object. The Adapter ({@link FishingBoatAdapter} ) + * converts the interface of the target class ( {@link FishingBoat}) into a suitable one expected + * by the client ({@link Captain} ). */ @Test public void testAdapter() { - Captain captain = (Captain) beans.get(ROWING_BEAN); + var captain = (Captain) beans.get(ROWING_BEAN); // when captain moves captain.row(); // the captain internally calls the battleship object to move - RowingBoat adapter = (RowingBoat) beans.get(FISHING_BEAN); + var adapter = (RowingBoat) beans.get(FISHING_BEAN); verify(adapter).row(); } } diff --git a/adapter/src/test/java/com/iluwatar/adapter/AppTest.java b/adapter/src/test/java/com/iluwatar/adapter/AppTest.java index ca2c103c35e5..3bf8e1010a11 100644 --- a/adapter/src/test/java/com/iluwatar/adapter/AppTest.java +++ b/adapter/src/test/java/com/iluwatar/adapter/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,19 +20,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.adapter; import org.junit.jupiter.api.Test; -import java.io.IOException; - /** * Tests that Adapter example runs without errors. */ public class AppTest { @Test - public void test() throws IOException { - String[] args = {}; - App.main(args); + public void test() { + App.main(new String[]{}); } } diff --git a/aggregator-microservices/README.md b/aggregator-microservices/README.md index e65f26d9a38a..4d64178a66a5 100644 --- a/aggregator-microservices/README.md +++ b/aggregator-microservices/README.md @@ -5,8 +5,7 @@ folder: aggregator-microservices permalink: /patterns/aggregator-microservices/ categories: Architectural tags: -- Java -- Spring +- Cloud distributed --- ## Intent @@ -18,6 +17,8 @@ More variations of the aggregator are: - Chained Microservice Design Pattern: In this case each microservice is dependent/ chained to a series of other microservices. +## Class diagram + ![alt text](./etc/aggregator-microservice.png "Aggregator Microservice") ## Applicability diff --git a/aggregator-microservices/aggregator-service/etc/aggregator-service.urm.puml b/aggregator-microservices/aggregator-service/etc/aggregator-service.urm.puml new file mode 100644 index 000000000000..32ab28920b87 --- /dev/null +++ b/aggregator-microservices/aggregator-service/etc/aggregator-service.urm.puml @@ -0,0 +1,43 @@ +@startuml +package com.iluwatar.aggregator.microservices { + class Aggregator { + - informationClient : ProductInformationClient + - inventoryClient : ProductInventoryClient + + Aggregator() + + getProduct() : Product + } + class App { + + App() + + main(args : String[]) {static} + } + class Product { + - productInventories : int + - title : String + + Product() + + getProductInventories() : int + + getTitle() : String + + setProductInventories(productInventories : int) + + setTitle(title : String) + } + interface ProductInformationClient { + + getProductTitle() : String {abstract} + } + class ProductInformationClientImpl { + - LOGGER : Logger {static} + + ProductInformationClientImpl() + + getProductTitle() : String + } + interface ProductInventoryClient { + + getProductInventories() : Integer {abstract} + } + class ProductInventoryClientImpl { + - LOGGER : Logger {static} + + ProductInventoryClientImpl() + + getProductInventories() : Integer + } +} +Aggregator --> "-informationClient" ProductInformationClient +Aggregator --> "-inventoryClient" ProductInventoryClient +ProductInformationClientImpl ..|> ProductInformationClient +ProductInventoryClientImpl ..|> ProductInventoryClient +@enduml \ No newline at end of file diff --git a/aggregator-microservices/aggregator-service/pom.xml b/aggregator-microservices/aggregator-service/pom.xml index 626ddcd0ba4f..f4482d0e3f8b 100644 --- a/aggregator-microservices/aggregator-service/pom.xml +++ b/aggregator-microservices/aggregator-service/pom.xml @@ -1,93 +1,81 @@ - + - - aggregator-microservices - com.iluwatar - 1.21.0-SNAPSHOT - - 4.0.0 - - aggregator-service - jar - - - - - org.springframework.boot - spring-boot-dependencies - - - - - - org.springframework - spring-webmvc - - - org.springframework.boot - spring-boot-starter-web - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.mockito - mockito-core - test - - - org.apache.httpcomponents - httpclient - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + aggregator-microservices + com.iluwatar + 1.23.0-SNAPSHOT + + 4.0.0 + aggregator-service + jar + + + org.springframework + spring-webmvc + + + org.springframework.boot + spring-boot-starter-web + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - - - repackage - - - - - - - \ No newline at end of file + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.aggregator.microservices.App + + + + + + + + + diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java index 9f1e4f4daeb8..f28377a1d722 100644 --- a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java +++ b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,16 +20,19 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.aggregator.microservices; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import static java.util.Objects.requireNonNullElse; import javax.annotation.Resource; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; /** - * The aggregator aggregates calls on various micro-services, collects - * data and further publishes them under a REST endpoint. + * The aggregator aggregates calls on various micro-services, collects data and further publishes + * them under a REST endpoint. */ @RestController public class Aggregator { @@ -47,11 +50,19 @@ public class Aggregator { * * @return a Product. */ - @RequestMapping("/product") + @RequestMapping(path = "/product", method = RequestMethod.GET) public Product getProduct() { - Product product = new Product(); - product.setTitle(informationClient.getProductTitle()); - product.setProductInventories(inventoryClient.getProductInventories()); + + var product = new Product(); + var productTitle = informationClient.getProductTitle(); + var productInventory = inventoryClient.getProductInventories(); + + //Fallback to error message + product.setTitle(requireNonNullElse(productTitle, "Error: Fetching Product Title Failed")); + + //Fallback to default error inventory + product.setProductInventories(requireNonNullElse(productInventory, -1)); + return product; } diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java index c43112c2e00a..3c09c54be3b4 100644 --- a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java +++ b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,19 +20,20 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.aggregator.microservices; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** - * Spring Boot EntryPoint Class + * Spring Boot EntryPoint Class. */ @SpringBootApplication public class App { /** - * Program entry point + * Program entry point. * * @param args command line args */ diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java index 1ded980f90ea..3c214a58a9f5 100644 --- a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java +++ b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.aggregator.microservices; /** diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java index 81cfdc87bb36..47d786ec6aeb 100644 --- a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java +++ b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.aggregator.microservices; /** diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java index 16849d529386..d19dcd829452 100644 --- a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java +++ b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,19 +20,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.aggregator.microservices; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.util.EntityUtils; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; -import java.io.IOException; - /** * An adapter to communicate with information micro-service. */ @@ -43,15 +42,19 @@ public class ProductInformationClientImpl implements ProductInformationClient { @Override public String getProductTitle() { - String response = null; - try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpGet httpGet = new HttpGet("http://localhost:51515/information"); - try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) { - response = EntityUtils.toString(httpResponse.getEntity()); - } - } catch (IOException e) { - LOGGER.error("Exception caught.", e); + var request = HttpRequest.newBuilder() + .GET() + .uri(URI.create("http://localhost:51515/information")) + .build(); + var client = HttpClient.newHttpClient(); + try { + var httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString()); + return httpResponse.body(); + } catch (IOException ioe) { + LOGGER.error("IOException Occurred", ioe); + } catch (InterruptedException ie) { + LOGGER.error("InterruptedException Occurred", ie); } - return response; + return null; } } diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java index c5953dc30dc0..22369350a203 100644 --- a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java +++ b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.aggregator.microservices; /** @@ -27,5 +28,5 @@ */ public interface ProductInventoryClient { - int getProductInventories(); + Integer getProductInventories(); } diff --git a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java index 89f1a25e0580..e493c8040b30 100644 --- a/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java +++ b/aggregator-microservices/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,19 +20,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.aggregator.microservices; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.util.EntityUtils; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; -import java.io.IOException; - /** * An adapter to communicate with inventory micro-service. */ @@ -42,16 +41,26 @@ public class ProductInventoryClientImpl implements ProductInventoryClient { private static final Logger LOGGER = LoggerFactory.getLogger(ProductInventoryClientImpl.class); @Override - public int getProductInventories() { - String response = "0"; - try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpGet httpGet = new HttpGet("http://localhost:51516/inventories"); - try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) { - response = EntityUtils.toString(httpResponse.getEntity()); - } - } catch (IOException e) { - LOGGER.error("Exception caught.", e); + public Integer getProductInventories() { + var response = ""; + + var request = HttpRequest.newBuilder() + .GET() + .uri(URI.create("http://localhost:51516/inventories")) + .build(); + var client = HttpClient.newHttpClient(); + try { + var httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString()); + response = httpResponse.body(); + } catch (IOException ioe) { + LOGGER.error("IOException Occurred", ioe); + } catch (InterruptedException ie) { + LOGGER.error("InterruptedException Occurred", ie); + } + if ("".equalsIgnoreCase(response)) { + return null; + } else { + return Integer.parseInt(response); } - return Integer.parseInt(response); } } diff --git a/aggregator-microservices/aggregator-service/src/main/resources/application.properties b/aggregator-microservices/aggregator-service/src/main/resources/application.properties index ae91c5eb5625..f9e29f5a7234 100644 --- a/aggregator-microservices/aggregator-service/src/main/resources/application.properties +++ b/aggregator-microservices/aggregator-service/src/main/resources/application.properties @@ -1,6 +1,6 @@ # # The MIT License -# Copyright (c) 2014-2016 Ilkka Seppälä +# Copyright © 2014-2019 Ilkka Seppälä # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -20,5 +20,4 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # - server.port=50004 \ No newline at end of file diff --git a/aggregator-microservices/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java b/aggregator-microservices/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java index bc07ccc2117b..cb958ecf9cb2 100644 --- a/aggregator-microservices/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java +++ b/aggregator-microservices/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,17 +20,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.aggregator.microservices; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.when; - /** * Test Aggregation of domain objects */ @@ -55,13 +56,13 @@ public void setup() { */ @Test public void testGetProduct() { - String title = "The Product Title."; - int inventories = 5; + var title = "The Product Title."; + var inventories = 5; when(informationClient.getProductTitle()).thenReturn(title); when(inventoryClient.getProductInventories()).thenReturn(inventories); - Product testProduct = aggregator.getProduct(); + var testProduct = aggregator.getProduct(); assertEquals(title, testProduct.getTitle()); assertEquals(inventories, testProduct.getProductInventories()); diff --git a/aggregator-microservices/etc/aggregator-microservices.urm.puml b/aggregator-microservices/etc/aggregator-microservices.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/aggregator-microservices/etc/aggregator-microservices.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/aggregator-microservices/information-microservice/etc/information-microservice.urm.puml b/aggregator-microservices/information-microservice/etc/information-microservice.urm.puml new file mode 100644 index 000000000000..e0a2ccb24fb2 --- /dev/null +++ b/aggregator-microservices/information-microservice/etc/information-microservice.urm.puml @@ -0,0 +1,12 @@ +@startuml +package com.iluwatar.information.microservice { + class InformationApplication { + + InformationApplication() + + main(args : String[]) {static} + } + class InformationController { + + InformationController() + + getProductTitle() : String + } +} +@enduml \ No newline at end of file diff --git a/aggregator-microservices/information-microservice/pom.xml b/aggregator-microservices/information-microservice/pom.xml index bac47c19c73c..f99d26b65665 100644 --- a/aggregator-microservices/information-microservice/pom.xml +++ b/aggregator-microservices/information-microservice/pom.xml @@ -1,84 +1,76 @@ - + - - aggregator-microservices - com.iluwatar - 1.21.0-SNAPSHOT - - 4.0.0 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + aggregator-microservices + com.iluwatar + 1.23.0-SNAPSHOT + + 4.0.0 - information-microservice - jar + information-microservice + jar - - - - org.springframework.boot - spring-boot-dependencies - - - - - - org.springframework - spring-webmvc - - - org.springframework.boot - spring-boot-starter-web - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - + + + org.springframework + spring-webmvc + + + org.springframework.boot + spring-boot-starter-web + + + org.junit.jupiter + junit-jupiter-engine + test + + - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - - - repackage - - - - - - + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.information.microservices.InformationApplication + + + + + + + + diff --git a/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java b/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java index 67d568e17dc4..3815fffc4cc7 100644 --- a/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java +++ b/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.information.microservice; import org.springframework.boot.SpringApplication; diff --git a/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java b/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java index 63b6fb9d5c0b..2accc013d88c 100644 --- a/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java +++ b/aggregator-microservices/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.information.microservice; import org.springframework.web.bind.annotation.RequestMapping; @@ -27,7 +28,7 @@ import org.springframework.web.bind.annotation.RestController; /** - * Controller providing endpoints to retrieve information about products + * Controller providing endpoints to retrieve information about products. */ @RestController public class InformationController { diff --git a/aggregator-microservices/information-microservice/src/main/resources/application.properties b/aggregator-microservices/information-microservice/src/main/resources/application.properties index a921ea0fd100..b953a61da732 100644 --- a/aggregator-microservices/information-microservice/src/main/resources/application.properties +++ b/aggregator-microservices/information-microservice/src/main/resources/application.properties @@ -1,6 +1,6 @@ # # The MIT License -# Copyright (c) 2014-2016 Ilkka Seppälä +# Copyright © 2014-2019 Ilkka Seppälä # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -20,5 +20,4 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # - server.port=51515 \ No newline at end of file diff --git a/aggregator-microservices/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java b/aggregator-microservices/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java index d030cfcb4d0e..90388af1a9af 100644 --- a/aggregator-microservices/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java +++ b/aggregator-microservices/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,12 +20,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.information.microservice; -import org.junit.jupiter.api.Test; +package com.iluwatar.information.microservice; import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + /** * Test for Information Rest Controller */ @@ -33,10 +34,8 @@ public class InformationControllerTest { @Test public void shouldGetProductTitle() { - InformationController infoController = new InformationController(); - - String title = infoController.getProductTitle(); - + var infoController = new InformationController(); + var title = infoController.getProductTitle(); assertEquals("The Product Title.", title); } diff --git a/aggregator-microservices/inventory-microservice/etc/inventory-microservice.urm.puml b/aggregator-microservices/inventory-microservice/etc/inventory-microservice.urm.puml new file mode 100644 index 000000000000..90f327e07db8 --- /dev/null +++ b/aggregator-microservices/inventory-microservice/etc/inventory-microservice.urm.puml @@ -0,0 +1,12 @@ +@startuml +package com.iluwatar.inventory.microservice { + class InventoryApplication { + + InventoryApplication() + + main(args : String[]) {static} + } + class InventoryController { + + InventoryController() + + getProductInventories() : int + } +} +@enduml \ No newline at end of file diff --git a/aggregator-microservices/inventory-microservice/pom.xml b/aggregator-microservices/inventory-microservice/pom.xml index bf613916b3ae..f7899aa8f307 100644 --- a/aggregator-microservices/inventory-microservice/pom.xml +++ b/aggregator-microservices/inventory-microservice/pom.xml @@ -1,84 +1,75 @@ - + - - aggregator-microservices - com.iluwatar - 1.21.0-SNAPSHOT - - 4.0.0 - - inventory-microservice - jar + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + aggregator-microservices + com.iluwatar + 1.23.0-SNAPSHOT + + 4.0.0 + inventory-microservice - - - - org.springframework.boot - spring-boot-dependencies - - - - - - org.springframework - spring-webmvc - - - org.springframework.boot - spring-boot-starter-web - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - + jar + + + org.springframework + spring-webmvc + + + org.springframework.boot + spring-boot-starter-web + + + org.junit.jupiter + junit-jupiter-engine + test + + - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - - - repackage - - - - - - - \ No newline at end of file + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.inventory.microservices.InventoryApplication + + + + + + + + + diff --git a/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java b/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java index 73b4c0b18925..9a49518b536e 100644 --- a/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java +++ b/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.inventory.microservice; import org.springframework.boot.SpringApplication; diff --git a/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java b/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java index e6b2eca80863..e3c3838f832a 100644 --- a/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java +++ b/aggregator-microservices/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.inventory.microservice; import org.springframework.web.bind.annotation.RequestMapping; @@ -27,7 +28,7 @@ import org.springframework.web.bind.annotation.RestController; /** - * Controller providing endpoints to retrieve product inventories + * Controller providing endpoints to retrieve product inventories. */ @RestController public class InventoryController { diff --git a/aggregator-microservices/inventory-microservice/src/main/resources/application.properties b/aggregator-microservices/inventory-microservice/src/main/resources/application.properties index 46b49a8a1464..9d2021f442d4 100644 --- a/aggregator-microservices/inventory-microservice/src/main/resources/application.properties +++ b/aggregator-microservices/inventory-microservice/src/main/resources/application.properties @@ -1,6 +1,6 @@ # # The MIT License -# Copyright (c) 2014-2016 Ilkka Seppälä +# Copyright © 2014-2019 Ilkka Seppälä # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -20,5 +20,4 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # - server.port=51516 \ No newline at end of file diff --git a/aggregator-microservices/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java b/aggregator-microservices/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java index c53a86b21d26..e9961796b1ff 100644 --- a/aggregator-microservices/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java +++ b/aggregator-microservices/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,22 +20,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.inventory.microservice; -import org.junit.jupiter.api.Test; +package com.iluwatar.inventory.microservice; import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + /** * Test Inventory Rest Controller */ public class InventoryControllerTest { @Test public void testGetProductInventories() { - InventoryController inventoryController = new InventoryController(); - - int numberOfInventories = inventoryController.getProductInventories(); - + var inventoryController = new InventoryController(); + var numberOfInventories = inventoryController.getProductInventories(); assertEquals(5, numberOfInventories); } } diff --git a/aggregator-microservices/pom.xml b/aggregator-microservices/pom.xml index 20e912395a05..a63ec2f126a9 100644 --- a/aggregator-microservices/pom.xml +++ b/aggregator-microservices/pom.xml @@ -2,7 +2,7 @@ "-service" RemoteService +Client --> "-serviceAmbassador" ServiceAmbassador +RemoteService --> "-randomProvider" RandomProvider +RemoteService ..|> RemoteServiceInterface +ServiceAmbassador ..|> RemoteServiceInterface +@enduml \ No newline at end of file diff --git a/ambassador/pom.xml b/ambassador/pom.xml index 6bfbf50084b1..6d6a9894de20 100644 --- a/ambassador/pom.xml +++ b/ambassador/pom.xml @@ -1,48 +1,53 @@ - + - - java-design-patterns - com.iluwatar - 1.21.0-SNAPSHOT - - 4.0.0 - ambassador - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - \ No newline at end of file + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + java-design-patterns + com.iluwatar + 1.23.0-SNAPSHOT + + 4.0.0 + ambassador + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.ambassador.App + + + + + + + + + diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/App.java b/ambassador/src/main/java/com/iluwatar/ambassador/App.java index e4b4d392ab3c..087fc39c08f9 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/App.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,33 +20,33 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.ambassador; /** - * * The ambassador pattern creates a helper service that sends network requests on behalf of a * client. It is often used in cloud-based applications to offload features of a remote service. * - * An ambassador service can be thought of as an out-of-process proxy that is co-located with - * the client. Similar to the proxy design pattern, the ambassador service provides an interface - * for another remote service. In addition to the interface, the ambassador provides extra - * functionality and features, specifically offloaded common connectivity tasks. This usually - * consists of monitoring, logging, routing, security etc. This is extremely useful in - * legacy applications where the codebase is difficult to modify and allows for improvements - * in the application's networking capabilities. + *

An ambassador service can be thought of as an out-of-process proxy that is co-located with + * the client. Similar to the proxy design pattern, the ambassador service provides an interface for + * another remote service. In addition to the interface, the ambassador provides extra functionality + * and features, specifically offloaded common connectivity tasks. This usually consists of + * monitoring, logging, routing, security etc. This is extremely useful in legacy applications where + * the codebase is difficult to modify and allows for improvements in the application's networking + * capabilities. * - * In this example, we will the ({@link ServiceAmbassador}) class represents the ambassador while the + *

In this example, we will the ({@link ServiceAmbassador}) class represents the ambassador while + * the * ({@link RemoteService}) class represents a remote application. - * */ public class App { /** - * Entry point - */ + * Entry point. + */ public static void main(String[] args) { - Client host1 = new Client(); - Client host2 = new Client(); + var host1 = new Client(); + var host2 = new Client(); host1.useService(12); host2.useService(73); } diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/Client.java b/ambassador/src/main/java/com/iluwatar/ambassador/Client.java index 9832265303ec..70d52e799742 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/Client.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/Client.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,14 +20,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.ambassador; -import org.slf4j.LoggerFactory; +package com.iluwatar.ambassador; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * A simple Client + * A simple Client. */ public class Client { @@ -35,7 +35,7 @@ public class Client { private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador(); long useService(int value) { - long result = serviceAmbassador.doRemoteFunction(value); + var result = serviceAmbassador.doRemoteFunction(value); LOGGER.info("Service result: " + result); return result; } diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java index 52b792b5247c..a80806851e6b 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,19 +20,20 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.ambassador; +import static java.lang.Thread.sleep; + import com.iluwatar.ambassador.util.RandomProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static java.lang.Thread.sleep; - /** * A remote legacy application represented by a Singleton implementation. */ public class RemoteService implements RemoteServiceInterface { - static final int THRESHOLD = 200; + private static final int THRESHOLD = 200; private static final Logger LOGGER = LoggerFactory.getLogger(RemoteService.class); private static RemoteService service = null; private final RandomProvider randomProvider; @@ -49,14 +50,16 @@ private RemoteService() { } /** - * This constuctor is used for testing purposes only. + * This constructor is used for testing purposes only. */ RemoteService(RandomProvider randomProvider) { this.randomProvider = randomProvider; } + /** - * Remote function takes a value and multiplies it by 10 taking a random amount of time. - * Will sometimes return -1. This imitates connectivity issues a client might have to account for. + * Remote function takes a value and multiplies it by 10 taking a random amount of time. Will + * sometimes return -1. This imitates connectivity issues a client might have to account for. + * * @param value integer value to be multiplied. * @return if waitTime is less than {@link RemoteService#THRESHOLD}, it returns value * 10, * otherwise {@link RemoteServiceInterface#FAILURE}. diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java index e2282718e010..013015936957 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.ambassador; /** diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java b/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java index 52cd7df9ac6b..a9d34581c614 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,19 +20,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.ambassador; +import static java.lang.Thread.sleep; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static java.lang.Thread.sleep; - /** - * * ServiceAmbassador provides an interface for a ({@link Client}) to access ({@link RemoteService}). * The interface adds logging, latency testing and usage of the service in a safe way that will not * add stress to the remote service when connectivity issues occur. - * */ public class ServiceAmbassador implements RemoteServiceInterface { @@ -40,7 +39,8 @@ public class ServiceAmbassador implements RemoteServiceInterface { private static final int RETRIES = 3; private static final int DELAY_MS = 3000; - ServiceAmbassador() {} + ServiceAmbassador() { + } @Override public long doRemoteFunction(int value) { @@ -48,21 +48,19 @@ public long doRemoteFunction(int value) { } private long checkLatency(int value) { - long startTime = System.currentTimeMillis(); - long result = RemoteService.getRemoteService().doRemoteFunction(value); - long timeTaken = System.currentTimeMillis() - startTime; + var startTime = System.currentTimeMillis(); + var result = RemoteService.getRemoteService().doRemoteFunction(value); + var timeTaken = System.currentTimeMillis() - startTime; LOGGER.info("Time taken (ms): " + timeTaken); return result; } private long safeCall(int value) { - - int retries = 0; - long result = FAILURE; + var retries = 0; + var result = (long) FAILURE; for (int i = 0; i < RETRIES; i++) { - if (retries >= RETRIES) { return FAILURE; } diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java b/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java index 3fc38b1dac22..5948472c0062 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.ambassador.util; /** diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java index cb072c1eb406..c9a4d09b6965 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.ambassador; import org.junit.jupiter.api.Test; @@ -27,10 +28,10 @@ /** * Application test */ -public class AppTest { +class AppTest { @Test - public void test() { + void test() { App.main(new String[]{}); } } diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java index 948ecaae8e4c..12a93a1cbc53 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.ambassador; import org.junit.jupiter.api.Test; @@ -29,13 +30,12 @@ /** * Test for {@link Client} */ -public class ClientTest { +class ClientTest { @Test - public void test() { - + void test() { Client client = new Client(); - long result = client.useService(10); + var result = client.useService(10); assertTrue(result == 100 || result == RemoteService.FAILURE); } diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java index 7d014696ee1e..3cfea262375a 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,36 +20,34 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.ambassador; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.iluwatar.ambassador.util.RandomProvider; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - /** * Test for {@link RemoteService} */ -public class RemoteServiceTest { +class RemoteServiceTest { @Test - public void testFailedCall() { - RemoteService remoteService = new RemoteService( - new StaticRandomProvider(0.21)); - long result = remoteService.doRemoteFunction(10); + void testFailedCall() { + var remoteService = new RemoteService(new StaticRandomProvider(0.21)); + var result = remoteService.doRemoteFunction(10); assertEquals(RemoteServiceInterface.FAILURE, result); } @Test - public void testSuccessfulCall() { - RemoteService remoteService = new RemoteService( - new StaticRandomProvider(0.2)); - long result = remoteService.doRemoteFunction(10); + void testSuccessfulCall() { + var remoteService = new RemoteService(new StaticRandomProvider(0.2)); + var result = remoteService.doRemoteFunction(10); assertEquals(100, result); } - private class StaticRandomProvider implements RandomProvider { + private static class StaticRandomProvider implements RandomProvider { private double value; StaticRandomProvider(double value) { diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java index 72e710659f3d..8eb55b30a12f 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.ambassador; import org.junit.jupiter.api.Test; @@ -29,10 +30,10 @@ /** * Test for {@link ServiceAmbassador} */ -public class ServiceAmbassadorTest { +class ServiceAmbassadorTest { @Test - public void test() { + void test() { long result = new ServiceAmbassador().doRemoteFunction(10); assertTrue(result == 100 || result == RemoteServiceInterface.FAILURE); } diff --git a/api-gateway/README.md b/api-gateway/README.md index 23014ae0b8af..3a4f13e35e62 100644 --- a/api-gateway/README.md +++ b/api-gateway/README.md @@ -5,9 +5,8 @@ folder: api-gateway permalink: /patterns/api-gateway/ categories: Architectural tags: -- Java -- Difficulty-Intermediate -- Spring +- Cloud distributed +- Decoupling --- ## Intent @@ -15,6 +14,7 @@ tags: Aggregate calls to microservices in a single location: the API Gateway. The user makes a single call to the API Gateway, and the API Gateway then calls each relevant microservice. +## Class diagram ![alt text](./etc/api-gateway.png "API Gateway") ## Applicability diff --git a/api-gateway/api-gateway-service/etc/api-gateway-service.urm.puml b/api-gateway/api-gateway-service/etc/api-gateway-service.urm.puml new file mode 100644 index 000000000000..5fabc6a0fab4 --- /dev/null +++ b/api-gateway/api-gateway-service/etc/api-gateway-service.urm.puml @@ -0,0 +1,48 @@ +@startuml +package com.iluwatar.api.gateway { + class ApiGateway { + - imageClient : ImageClient + - priceClient : PriceClient + + ApiGateway() + + getProductDesktop() : DesktopProduct + + getProductMobile() : MobileProduct + } + class App { + + App() + + main(args : String[]) {static} + } + class DesktopProduct { + - imagePath : String + - price : String + + DesktopProduct() + + getImagePath() : String + + getPrice() : String + + setImagePath(imagePath : String) + + setPrice(price : String) + } + interface ImageClient { + + getImagePath() : String {abstract} + } + class ImageClientImpl { + + ImageClientImpl() + + getImagePath() : String + } + class MobileProduct { + - price : String + + MobileProduct() + + getPrice() : String + + setPrice(price : String) + } + interface PriceClient { + + getPrice() : String {abstract} + } + class PriceClientImpl { + + PriceClientImpl() + + getPrice() : String + } +} +ApiGateway --> "-imageClient" ImageClient +ApiGateway --> "-priceClient" PriceClient +ImageClientImpl ..|> ImageClient +PriceClientImpl ..|> PriceClient +@enduml \ No newline at end of file diff --git a/api-gateway/api-gateway-service/pom.xml b/api-gateway/api-gateway-service/pom.xml index f947049a757c..744023e900c2 100644 --- a/api-gateway/api-gateway-service/pom.xml +++ b/api-gateway/api-gateway-service/pom.xml @@ -2,7 +2,7 @@ - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.21.0-SNAPSHOT - - async-method-invocation - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.mockito - mockito-core - test - - + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + async-method-invocation + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + junit + junit + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.async.method.invocation.App + + + + + + + + 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 0a387d58296a..40c186704cd7 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 @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,37 +20,37 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.async.method.invocation; +import java.util.concurrent.Callable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -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. - *

- * 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. - *

- * 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. + * 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. + * + *

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. * * @see AsyncResult * @see AsyncCallback * @see AsyncExecutor - * * @see java.util.concurrent.FutureTask * @see java.util.concurrent.CompletableFuture * @see java.util.concurrent.ExecutorService @@ -60,27 +60,28 @@ public class App { private static final Logger LOGGER = LoggerFactory.getLogger(App.class); /** - * Program entry point + * Program entry point. */ public static void main(String[] args) throws Exception { // construct a new executor that will run async tasks - AsyncExecutor executor = new ThreadAsyncExecutor(); + var executor = new ThreadAsyncExecutor(); // start few async tasks with varying processing times, two last with callback handlers - 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")); + final var asyncResult1 = executor.startProcess(lazyval(10, 500)); + final var asyncResult2 = executor.startProcess(lazyval("test", 300)); + final var asyncResult3 = executor.startProcess(lazyval(50L, 700)); + final var asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4")); + final var 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 log("Some hard work done"); // wait for completion of the tasks - Integer result1 = executor.endProcess(asyncResult1); - String result2 = executor.endProcess(asyncResult2); - Long result3 = executor.endProcess(asyncResult3); + final var result1 = executor.endProcess(asyncResult1); + final var result2 = executor.endProcess(asyncResult2); + final var result3 = executor.endProcess(asyncResult3); asyncResult4.await(); asyncResult5.await(); @@ -93,10 +94,8 @@ public static void main(String[] args) throws Exception { /** * 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) { @@ -110,8 +109,7 @@ private static Callable lazyval(T value, long delayMillis) { /** * 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/AsyncCallback.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java index 5a291f996d9a..22b36134ff97 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,16 +20,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.async.method.invocation; import java.util.Optional; /** - * - * AsyncCallback interface + * AsyncCallback interface. * - * @param - * + * @param Type of Result */ public interface AsyncCallback { @@ -37,7 +36,7 @@ public interface AsyncCallback { * Complete handler which is executed when async task is completed or fails execution. * * @param value the evaluated value from async task, undefined when execution fails - * @param ex empty value if execution succeeds, some exception if executions fails + * @param ex empty value if execution succeeds, some exception if executions fails */ void onComplete(T value, Optional ex); } diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java index 800bc2728fad..819ffd2377ca 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,15 +20,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.async.method.invocation; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; /** - * - * AsyncExecutor interface - * + * AsyncExecutor interface. */ public interface AsyncExecutor { @@ -44,7 +43,7 @@ public interface AsyncExecutor { * Starts processing of an async task. Returns immediately with async result. Executes callback * when the task is completed. * - * @param task task to be executed asynchronously + * @param task task to be executed asynchronously * @param callback callback to be executed on task completion * @return async result for the task */ @@ -56,7 +55,7 @@ public interface AsyncExecutor { * * @param asyncResult async result of a task * @return evaluated value of the completed task - * @throws ExecutionException if execution has failed, containing the root cause + * @throws ExecutionException if execution has failed, containing the root cause * @throws InterruptedException if the execution is interrupted */ T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException; 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 f7fd9ab61e30..6aaf233b44f7 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 @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.async.method.invocation; import java.util.concurrent.ExecutionException; /** - * AsyncResult interface + * AsyncResult interface. + * * @param parameter returned when getValue is invoked */ public interface AsyncResult { @@ -41,7 +43,7 @@ public interface AsyncResult { * Gets the value of completed async task. * * @return evaluated value or throws ExecutionException if execution has failed - * @throws ExecutionException if execution has failed, containing the root cause + * @throws ExecutionException if execution has failed, containing the root cause * @throws IllegalStateException if execution is not completed */ T getValue() throws ExecutionException; 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 d12ebbe19297..7bdf841716d2 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 @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.async.method.invocation; import java.util.Optional; @@ -28,13 +29,13 @@ import java.util.concurrent.atomic.AtomicInteger; /** - * * Implementation of async executor that creates a new thread for every task. - * */ public class ThreadAsyncExecutor implements AsyncExecutor { - /** Index for thread naming */ + /** + * Index for thread naming. + */ private final AtomicInteger idx = new AtomicInteger(0); @Override @@ -44,19 +45,20 @@ public AsyncResult startProcess(Callable task) { @Override public AsyncResult startProcess(Callable task, AsyncCallback callback) { - CompletableResult result = new CompletableResult<>(callback); + var result = new CompletableResult<>(callback); new Thread(() -> { try { result.setValue(task.call()); } 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()) { asyncResult.await(); } @@ -64,8 +66,9 @@ public T endProcess(AsyncResult asyncResult) throws ExecutionException, I } /** - * 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 @@ -89,11 +92,10 @@ private static class CompletableResult implements AsyncResult { } /** - * 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; @@ -105,11 +107,10 @@ void setValue(T value) { } /** - * 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; diff --git a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java index a2744306e1a6..830e66a2d273 100644 --- a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java +++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,20 +20,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.async.method.invocation; import org.junit.jupiter.api.Test; /** - * * Application test - * */ -public class AppTest { - +class AppTest { @Test - public void test() throws Exception { - String[] args = {}; - App.main(args); + void test() throws Exception { + App.main(new String[]{}); } } 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 index b5c4fff674de..1e54747faf99 100644 --- 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 @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,15 +20,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.async.method.invocation; - -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Matchers; -import java.util.Optional; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; +package com.iluwatar.async.method.invocation; import static java.time.Duration.ofMillis; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -39,7 +32,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; 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; @@ -47,27 +39,51 @@ import static org.mockito.Mockito.when; import static org.mockito.internal.verification.VerificationModeFactory.times; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + /** * Date: 12/6/15 - 10:49 AM * * @author Jeroen Meulemeester */ -public class ThreadAsyncExecutorTest { +class ThreadAsyncExecutorTest { + + @Captor + private ArgumentCaptor> optionalCaptor; + + @Mock + private Callable task; + + @Mock + private AsyncCallback callback; + + @BeforeEach + void setUp() { + MockitoAnnotations.initMocks(this); + } /** * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)} */ @Test - public void testSuccessfulTaskWithoutCallback() throws Exception { + void testSuccessfulTaskWithoutCallback() throws Exception { assertTimeout(ofMillis(3000), () -> { // Instantiate a new executor and start a new 'null' task ... - final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); + final var executor = new ThreadAsyncExecutor(); - final Object result = new Object(); - final Callable task = mock(Callable.class); + final var result = new Object(); when(task.call()).thenReturn(result); - final AsyncResult asyncResult = executor.startProcess(task); + final var asyncResult = executor.startProcess(task); assertNotNull(asyncResult); asyncResult.await(); // Prevent timing issues, and wait until the result is available assertTrue(asyncResult.isCompleted()); @@ -81,20 +97,19 @@ public void testSuccessfulTaskWithoutCallback() throws Exception { } /** - * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)} + * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable, + * AsyncCallback)} */ @Test - public void testSuccessfulTaskWithCallback() throws Exception { + void testSuccessfulTaskWithCallback() throws Exception { assertTimeout(ofMillis(3000), () -> { // Instantiate a new executor and start a new 'null' task ... - final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); + final var executor = new ThreadAsyncExecutor(); - final Object result = new Object(); - final Callable task = mock(Callable.class); + final var result = new Object(); when(task.call()).thenReturn(result); - final AsyncCallback callback = mock(AsyncCallback.class); - final AsyncResult asyncResult = executor.startProcess(task, callback); + final var asyncResult = executor.startProcess(task, callback); assertNotNull(asyncResult); asyncResult.await(); // Prevent timing issues, and wait until the result is available assertTrue(asyncResult.isCompleted()); @@ -103,10 +118,9 @@ public void testSuccessfulTaskWithCallback() throws Exception { 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(); + final var optionalException = optionalCaptor.getValue(); assertNotNull(optionalException); assertFalse(optionalException.isPresent()); @@ -116,23 +130,22 @@ public void testSuccessfulTaskWithCallback() throws Exception { } /** - * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)} when a task takes a while - * to execute + * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)} when a + * task takes a while to execute */ @Test - public void testLongRunningTaskWithoutCallback() throws Exception { + void testLongRunningTaskWithoutCallback() throws Exception { assertTimeout(ofMillis(5000), () -> { // Instantiate a new executor and start a new 'null' task ... - final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); + final var executor = new ThreadAsyncExecutor(); - final Object result = new Object(); - final Callable task = mock(Callable.class); + final var result = new Object(); when(task.call()).thenAnswer(i -> { Thread.sleep(1500); return result; }); - final AsyncResult asyncResult = executor.startProcess(task); + final var asyncResult = executor.startProcess(task); assertNotNull(asyncResult); assertFalse(asyncResult.isCompleted()); @@ -157,24 +170,22 @@ public void testLongRunningTaskWithoutCallback() throws Exception { } /** - * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)} when a task - * takes a while to execute + * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable, + * AsyncCallback)} when a task takes a while to execute */ @Test - public void testLongRunningTaskWithCallback() throws Exception { + void testLongRunningTaskWithCallback() throws Exception { assertTimeout(ofMillis(5000), () -> { // Instantiate a new executor and start a new 'null' task ... - final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); + final var executor = new ThreadAsyncExecutor(); - final Object result = new Object(); - final Callable task = mock(Callable.class); + final var result = new Object(); when(task.call()).thenAnswer(i -> { Thread.sleep(1500); return result; }); - final AsyncCallback callback = mock(AsyncCallback.class); - final AsyncResult asyncResult = executor.startProcess(task, callback); + final var asyncResult = executor.startProcess(task, callback); assertNotNull(asyncResult); assertFalse(asyncResult.isCompleted()); @@ -189,11 +200,9 @@ public void testLongRunningTaskWithCallback() throws Exception { // 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(); + final var optionalException = optionalCaptor.getValue(); assertNotNull(optionalException); assertFalse(optionalException.isPresent()); @@ -208,23 +217,23 @@ public void testLongRunningTaskWithCallback() throws Exception { } /** - * 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 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 - public void testEndProcess() throws Exception { + void testEndProcess() throws Exception { assertTimeout(ofMillis(5000), () -> { // Instantiate a new executor and start a new 'null' task ... - final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); + final var executor = new ThreadAsyncExecutor(); - final Object result = new Object(); - final Callable task = mock(Callable.class); + final var result = new Object(); when(task.call()).thenAnswer(i -> { Thread.sleep(1500); return result; }); - final AsyncResult asyncResult = executor.startProcess(task); + final var asyncResult = executor.startProcess(task); assertNotNull(asyncResult); assertFalse(asyncResult.isCompleted()); @@ -246,14 +255,15 @@ public void testEndProcess() throws Exception { } /** - * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable)} when the callable is 'null' + * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable)} when + * the callable is 'null' */ @Test - public void testNullTask() throws Exception { + void testNullTask() throws Exception { assertTimeout(ofMillis(3000), () -> { // Instantiate a new executor and start a new 'null' task ... - final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); - final AsyncResult asyncResult = executor.startProcess(null); + final var executor = new ThreadAsyncExecutor(); + final var asyncResult = executor.startProcess(null); assertNotNull(asyncResult, "The AsyncResult should not be 'null', even though the task was 'null'."); asyncResult.await(); // Prevent timing issues, and wait until the result is available @@ -272,29 +282,26 @@ public void testNullTask() throws Exception { } /** - * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)} when the - * callable is 'null', but the asynchronous callback is provided + * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable, + * AsyncCallback)} when the callable is 'null', but the asynchronous callback is provided */ @Test - public void testNullTaskWithCallback() throws Exception { + void testNullTaskWithCallback() throws Exception { assertTimeout(ofMillis(3000), () -> { // 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); + final var executor = new ThreadAsyncExecutor(); + final var asyncResult = executor.startProcess(null, callback); assertNotNull(asyncResult, "The AsyncResult should not be 'null', even though the task was 'null'."); 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(); + final var optionalException = optionalCaptor.getValue(); assertNotNull(optionalException); assertTrue(optionalException.isPresent()); - final Exception exception = optionalException.get(); + final var exception = optionalException.get(); assertNotNull(exception); assertEquals(NullPointerException.class, exception.getClass()); @@ -311,15 +318,15 @@ public void testNullTaskWithCallback() throws Exception { } /** - * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)} when both - * the callable and the asynchronous callback are 'null' + * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable, + * AsyncCallback)} when both the callable and the asynchronous callback are 'null' */ @Test - public void testNullTaskWithNullCallback() throws Exception { + void testNullTaskWithNullCallback() throws Exception { assertTimeout(ofMillis(3000), () -> { // Instantiate a new executor and start a new 'null' task ... - final ThreadAsyncExecutor executor = new ThreadAsyncExecutor(); - final AsyncResult asyncResult = executor.startProcess(null, null); + final var executor = new ThreadAsyncExecutor(); + final var asyncResult = executor.startProcess(null, null); assertNotNull( asyncResult, diff --git a/balking/README.md b/balking/README.md index fb5c83d5b412..22257ac7bb52 100644 --- a/balking/README.md +++ b/balking/README.md @@ -5,14 +5,14 @@ folder: balking permalink: /patterns/balking/ categories: Concurrency tags: - - Java - - Difficulty-Beginner + - Decoupling --- ## Intent Balking Pattern is used to prevent an object from executing certain code if it is an incomplete or inappropriate state +## Class diagram ![alt text](./etc/balking.png "Balking") ## Applicability diff --git a/balking/etc/balking.urm.puml b/balking/etc/balking.urm.puml new file mode 100644 index 000000000000..191fd350bcd8 --- /dev/null +++ b/balking/etc/balking.urm.puml @@ -0,0 +1,30 @@ +@startuml +package com.iluwatar.balking { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + interface DelayProvider { + + executeAfterDelay(long, TimeUnit, Runnable) {abstract} + } + class WashingMachine { + - LOGGER : Logger {static} + - delayProvider : DelayProvider + - washingMachineState : WashingMachineState + + WashingMachine() + + WashingMachine(delayProvider : DelayProvider) + + endOfWashing() + + getWashingMachineState() : WashingMachineState + + wash() + } + enum WashingMachineState { + + ENABLED {static} + + WASHING {static} + + valueOf(name : String) : WashingMachineState {static} + + values() : WashingMachineState[] {static} + } +} +WashingMachine --> "-washingMachineState" WashingMachineState +WashingMachine --> "-delayProvider" DelayProvider +@enduml \ No newline at end of file diff --git a/balking/pom.xml b/balking/pom.xml index 500a11c82f6b..96453169265b 100644 --- a/balking/pom.xml +++ b/balking/pom.xml @@ -1,51 +1,54 @@ - + - - java-design-patterns - com.iluwatar - 1.21.0-SNAPSHOT - - 4.0.0 - - balking - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - - - \ No newline at end of file + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + java-design-patterns + com.iluwatar + 1.23.0-SNAPSHOT + + 4.0.0 + + balking + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.balking.App + + + + + + + + + diff --git a/balking/src/main/java/com/iluwatar/balking/App.java b/balking/src/main/java/com/iluwatar/balking/App.java index 47b10cd4606d..3e72acc59efc 100644 --- a/balking/src/main/java/com/iluwatar/balking/App.java +++ b/balking/src/main/java/com/iluwatar/balking/App.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,25 +20,25 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.balking; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +package com.iluwatar.balking; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * In Balking Design Pattern if an object’s method is invoked when it is in an inappropriate state, - * then the method will return without doing anything. Objects that use this pattern are generally only in a - * state that is prone to balking temporarily but for an unknown amount of time + * then the method will return without doing anything. Objects that use this pattern are generally + * only in a state that is prone to balking temporarily but for an unknown amount of time * - * In this example implementation WashingMachine is an object that has two states - * in which it can be: ENABLED and WASHING. If the machine is ENABLED - * the state is changed into WASHING that any other thread can't invoke this action on this and then do the job. - * On the other hand if it have been already washing and any other thread execute wash() - * it can't do that once again and returns doing nothing. + *

In this example implementation WashingMachine is an object that has two states in which it + * can be: ENABLED and WASHING. If the machine is ENABLED the state is changed into WASHING that any + * other thread can't invoke this action on this and then do the job. On the other hand if it have + * been already washing and any other thread execute wash() it can't do that once again and returns + * doing nothing. */ public class App { @@ -46,11 +46,13 @@ public class App { private static final Logger LOGGER = LoggerFactory.getLogger(App.class); /** + * Entry Point. + * * @param args the command line arguments - not used */ public static void main(String... args) { - final WashingMachine washingMachine = new WashingMachine(); - ExecutorService executorService = Executors.newFixedThreadPool(3); + final var washingMachine = new WashingMachine(); + var executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 3; i++) { executorService.execute(washingMachine::wash); } diff --git a/balking/src/main/java/com/iluwatar/balking/DelayProvider.java b/balking/src/main/java/com/iluwatar/balking/DelayProvider.java index 24b92d12e632..ed05cd292f6f 100644 --- a/balking/src/main/java/com/iluwatar/balking/DelayProvider.java +++ b/balking/src/main/java/com/iluwatar/balking/DelayProvider.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.balking; import java.util.concurrent.TimeUnit; diff --git a/balking/src/main/java/com/iluwatar/balking/WashingMachine.java b/balking/src/main/java/com/iluwatar/balking/WashingMachine.java index 5aa39c66ca89..b35bd99dfe61 100644 --- a/balking/src/main/java/com/iluwatar/balking/WashingMachine.java +++ b/balking/src/main/java/com/iluwatar/balking/WashingMachine.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,15 +20,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.balking; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.TimeUnit; - /** - * Washing machine class + * Washing machine class. */ public class WashingMachine { @@ -37,7 +37,7 @@ public class WashingMachine { private WashingMachineState washingMachineState; /** - * Creates a new instance of WashingMachine + * Creates a new instance of WashingMachine. */ public WashingMachine() { this((interval, timeUnit, task) -> { @@ -51,8 +51,8 @@ public WashingMachine() { } /** - * Creates a new instance of WashingMachine using provided delayProvider. This constructor is used only for - * unit testing purposes. + * Creates a new instance of WashingMachine using provided delayProvider. This constructor is used + * only for unit testing purposes. */ public WashingMachine(DelayProvider delayProvider) { this.delayProvider = delayProvider; @@ -64,17 +64,17 @@ public WashingMachineState getWashingMachineState() { } /** - * Method responsible for washing - * if the object is in appropriate state + * Method responsible for washing if the object is in appropriate state. */ public void wash() { synchronized (this) { - LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), getWashingMachineState()); - if (washingMachineState == WashingMachineState.WASHING) { + var machineState = getWashingMachineState(); + LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState); + if (this.washingMachineState == WashingMachineState.WASHING) { LOGGER.error("ERROR: Cannot wash if the machine has been already washing!"); return; } - washingMachineState = WashingMachineState.WASHING; + this.washingMachineState = WashingMachineState.WASHING; } LOGGER.info("{}: Doing the washing", Thread.currentThread().getName()); @@ -82,8 +82,7 @@ public void wash() { } /** - * Method responsible of ending the washing - * by changing machine state + * Method responsible of ending the washing by changing machine state. */ public synchronized void endOfWashing() { washingMachineState = WashingMachineState.ENABLED; diff --git a/balking/src/main/java/com/iluwatar/balking/WashingMachineState.java b/balking/src/main/java/com/iluwatar/balking/WashingMachineState.java index 40a5b2f38e30..664a4c0c9941 100644 --- a/balking/src/main/java/com/iluwatar/balking/WashingMachineState.java +++ b/balking/src/main/java/com/iluwatar/balking/WashingMachineState.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,13 +20,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.balking; /** - * WashingMachineState enum describes in which state machine is, - * it can be enabled and ready to work as well as during washing + * WashingMachineState enum describes in which state machine is, it can be enabled and ready to work + * as well as during washing. */ - public enum WashingMachineState { ENABLED, WASHING } diff --git a/balking/src/test/java/com/iluwatar/balking/AppTest.java b/balking/src/test/java/com/iluwatar/balking/AppTest.java index 87941dc27d91..8c75a1f62f69 100644 --- a/balking/src/test/java/com/iluwatar/balking/AppTest.java +++ b/balking/src/test/java/com/iluwatar/balking/AppTest.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -28,12 +28,11 @@ /** * Application test */ -public class AppTest { +class AppTest { @Test - public void main() throws Exception { - String[] args = {}; - App.main(args); + void main() { + App.main(); } } \ No newline at end of file diff --git a/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java b/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java index 100bf61a6341..9e218e3f0b76 100644 --- a/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java +++ b/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,29 +20,29 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.balking; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.concurrent.TimeUnit; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; /** * Tests for {@link WashingMachine} */ -public class WashingMachineTest { +class WashingMachineTest { - private FakeDelayProvider fakeDelayProvider = new FakeDelayProvider(); + private final FakeDelayProvider fakeDelayProvider = new FakeDelayProvider(); @Test - public void wash() { - WashingMachine washingMachine = new WashingMachine(fakeDelayProvider); + void wash() { + var washingMachine = new WashingMachine(fakeDelayProvider); washingMachine.wash(); washingMachine.wash(); - WashingMachineState machineStateGlobal = washingMachine.getWashingMachineState(); + var machineStateGlobal = washingMachine.getWashingMachineState(); fakeDelayProvider.task.run(); @@ -54,13 +54,13 @@ public void wash() { } @Test - public void endOfWashing() { - WashingMachine washingMachine = new WashingMachine(); + void endOfWashing() { + var washingMachine = new WashingMachine(); washingMachine.wash(); assertEquals(WashingMachineState.ENABLED, washingMachine.getWashingMachineState()); } - private class FakeDelayProvider implements DelayProvider { + private static class FakeDelayProvider implements DelayProvider { private Runnable task; @Override diff --git a/bridge/README.md b/bridge/README.md index cfbbcf0d95a4..ebd6e5b41e20 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -5,9 +5,7 @@ folder: bridge permalink: /patterns/bridge/ categories: Structural tags: - - Java - - Gang Of Four - - Difficulty-Intermediate + - Gang of Four --- ## Also known as @@ -156,7 +154,7 @@ public class SoulEatingEnchantment implements Enchantment { And both the hierarchies in action ```java -Sword enchantedSword = new Sword(new SoulEatingEnchantment()); +var enchantedSword = new Sword(new SoulEatingEnchantment()); enchantedSword.wield(); enchantedSword.swing(); enchantedSword.unwield(); @@ -167,7 +165,7 @@ enchantedSword.unwield(); // The sword is unwielded. // Bloodlust slowly disappears. -Hammer hammer = new Hammer(new FlyingEnchantment()); +var hammer = new Hammer(new FlyingEnchantment()); hammer.wield(); hammer.swing(); hammer.unwield(); @@ -179,6 +177,9 @@ hammer.unwield(); // The item's glow fades. ``` +## Class diagram +![alt text](./etc/bridge.urm.png "Bridge class diagram") + ## Applicability Use the Bridge pattern when diff --git a/bridge/etc/bridge.urm.png b/bridge/etc/bridge.urm.png new file mode 100644 index 000000000000..785585bf8163 Binary files /dev/null and b/bridge/etc/bridge.urm.png differ diff --git a/bridge/etc/bridge.urm.puml b/bridge/etc/bridge.urm.puml new file mode 100644 index 000000000000..d5d6a38a91d9 --- /dev/null +++ b/bridge/etc/bridge.urm.puml @@ -0,0 +1,58 @@ +@startuml +package com.iluwatar.bridge { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + interface Enchantment { + + apply() {abstract} + + onActivate() {abstract} + + onDeactivate() {abstract} + } + class FlyingEnchantment { + - LOGGER : Logger {static} + + FlyingEnchantment() + + apply() + + onActivate() + + onDeactivate() + } + class Hammer { + - LOGGER : Logger {static} + - enchantment : Enchantment + + Hammer(enchantment : Enchantment) + + getEnchantment() : Enchantment + + swing() + + unwield() + + wield() + } + class SoulEatingEnchantment { + - LOGGER : Logger {static} + + SoulEatingEnchantment() + + apply() + + onActivate() + + onDeactivate() + } + class Sword { + - LOGGER : Logger {static} + - enchantment : Enchantment + + Sword(enchantment : Enchantment) + + getEnchantment() : Enchantment + + swing() + + unwield() + + wield() + } + interface Weapon { + + getEnchantment() : Enchantment {abstract} + + swing() {abstract} + + unwield() {abstract} + + wield() {abstract} + } +} +Sword --> "-enchantment" Enchantment +Hammer --> "-enchantment" Enchantment +FlyingEnchantment ..|> Enchantment +Hammer ..|> Weapon +SoulEatingEnchantment ..|> Enchantment +Sword ..|> Weapon +@enduml \ No newline at end of file diff --git a/bridge/pom.xml b/bridge/pom.xml index a86c5bf22c4d..0664bc9b5977 100644 --- a/bridge/pom.xml +++ b/bridge/pom.xml @@ -1,52 +1,59 @@ - - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.21.0-SNAPSHOT - - bridge - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.mockito - mockito-core - test - - + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + bridge + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.bridge.App + + + + + + + + diff --git a/bridge/src/main/java/com/iluwatar/bridge/App.java b/bridge/src/main/java/com/iluwatar/bridge/App.java index c986de6565dd..3e89ef6f67ba 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/App.java +++ b/bridge/src/main/java/com/iluwatar/bridge/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,42 +20,43 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.bridge; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * - * Composition over inheritance. The Bridge pattern can also be thought of as two layers of abstraction. - * With Bridge, you can decouple an abstraction from its implementation so that the two can vary independently. - *

- * In Bridge pattern both abstraction ({@link Weapon}) and implementation ( - * {@link Enchantment}) have their own class hierarchies. The interface of the implementations - * can be changed without affecting the clients. - *

- * In this example we have two class hierarchies. One of weapons and another one of enchantments. We can easily - * combine any weapon with any enchantment using composition instead of creating deep class hierarchy. - * + * Composition over inheritance. The Bridge pattern can also be thought of as two layers of + * abstraction. With Bridge, you can decouple an abstraction from its implementation so that the two + * can vary independently. + * + *

In Bridge pattern both abstraction ({@link Weapon}) and implementation ( {@link Enchantment}) + * have their own class hierarchies. The interface of the implementations can be changed without + * affecting the clients. + * + *

In this example we have two class hierarchies. One of weapons and another one of + * enchantments. We can easily combine any weapon with any enchantment using composition instead of + * creating deep class hierarchy. */ public class App { private static final Logger LOGGER = LoggerFactory.getLogger(App.class); /** - * Program entry point - * + * Program entry point. + * * @param args command line args */ public static void main(String[] args) { LOGGER.info("The knight receives an enchanted sword."); - Sword enchantedSword = new Sword(new SoulEatingEnchantment()); + var enchantedSword = new Sword(new SoulEatingEnchantment()); enchantedSword.wield(); enchantedSword.swing(); enchantedSword.unwield(); LOGGER.info("The valkyrie receives an enchanted hammer."); - Hammer hammer = new Hammer(new FlyingEnchantment()); + var hammer = new Hammer(new FlyingEnchantment()); hammer.wield(); hammer.swing(); hammer.unwield(); diff --git a/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java b/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java index dd0c172059cf..8388fe91eb2b 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java +++ b/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.bridge; /** - * - * Enchantment - * + * Enchantment. */ public interface Enchantment { diff --git a/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java b/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java index 8b12c6114e88..772456b88881 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java +++ b/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,15 +20,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.bridge; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * - * FlyingEnchantment - * + * FlyingEnchantment. */ public class FlyingEnchantment implements Enchantment { diff --git a/bridge/src/main/java/com/iluwatar/bridge/Hammer.java b/bridge/src/main/java/com/iluwatar/bridge/Hammer.java index 51bfda2a1b97..ffab542cb169 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Hammer.java +++ b/bridge/src/main/java/com/iluwatar/bridge/Hammer.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,15 +20,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.bridge; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * - * Hammer - * + * Hammer. */ public class Hammer implements Weapon { diff --git a/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java b/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java index 8b08d155c083..ede98d2cbd08 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java +++ b/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,15 +20,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.bridge; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * - * SoulEatingEnchantment - * + * SoulEatingEnchantment. */ public class SoulEatingEnchantment implements Enchantment { diff --git a/bridge/src/main/java/com/iluwatar/bridge/Sword.java b/bridge/src/main/java/com/iluwatar/bridge/Sword.java index 6f52943a6ead..71f87a55dcd8 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Sword.java +++ b/bridge/src/main/java/com/iluwatar/bridge/Sword.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,15 +20,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.bridge; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * - * Sword - * + * Sword. */ public class Sword implements Weapon { diff --git a/bridge/src/main/java/com/iluwatar/bridge/Weapon.java b/bridge/src/main/java/com/iluwatar/bridge/Weapon.java index a2d21b88f5aa..76272332e76d 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Weapon.java +++ b/bridge/src/main/java/com/iluwatar/bridge/Weapon.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.bridge; /** - * - * Weapon - * + * Weapon. */ public interface Weapon { diff --git a/bridge/src/test/java/com/iluwatar/bridge/AppTest.java b/bridge/src/test/java/com/iluwatar/bridge/AppTest.java index 233f1d481e33..d3edbb27ce00 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/AppTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,20 +20,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.bridge; import org.junit.jupiter.api.Test; /** - * * Application test - * */ -public class AppTest { - +class AppTest { @Test - public void test() { - String[] args = {}; - App.main(args); + void test() { + App.main(new String[]{}); } } diff --git a/bridge/src/test/java/com/iluwatar/bridge/HammerTest.java b/bridge/src/test/java/com/iluwatar/bridge/HammerTest.java index 905e94aa48d8..b91f9f402763 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/HammerTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/HammerTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,25 +20,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.bridge; -import org.junit.jupiter.api.Test; +package com.iluwatar.bridge; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import org.junit.jupiter.api.Test; + /** * Tests for hammer */ -public class HammerTest extends WeaponTest { +class HammerTest extends WeaponTest { /** * Invoke all possible actions on the weapon and check if the actions are executed on the actual * underlying weapon implementation. */ @Test - public void testHammer() { - final Hammer hammer = spy(new Hammer(mock(FlyingEnchantment.class))); + void testHammer() { + final var hammer = spy(new Hammer(mock(FlyingEnchantment.class))); testBasicWeaponActions(hammer); } } \ No newline at end of file diff --git a/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java b/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java index 61cb59f571aa..95a6bc3d33ce 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,25 +20,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.bridge; -import org.junit.jupiter.api.Test; +package com.iluwatar.bridge; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import org.junit.jupiter.api.Test; + /** * Tests for sword */ -public class SwordTest extends WeaponTest { +class SwordTest extends WeaponTest { /** * Invoke all possible actions on the weapon and check if the actions are executed on the actual * underlying weapon implementation. */ @Test - public void testSword() { - final Sword sword = spy(new Sword(mock(FlyingEnchantment.class))); + void testSword() { + final var sword = spy(new Sword(mock(FlyingEnchantment.class))); testBasicWeaponActions(sword); } } \ No newline at end of file diff --git a/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java b/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java index e8518eb50b06..620e0104b20c 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,9 +20,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.bridge; -import org.junit.jupiter.api.Disabled; +package com.iluwatar.bridge; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.verify; @@ -31,16 +30,15 @@ /** * Base class for weapon tests */ -public abstract class WeaponTest { +abstract class WeaponTest { /** - * Invoke the basic actions of the given weapon, and test if the underlying enchantment implementation - * is invoked - * + * Invoke the basic actions of the given weapon, and test if the underlying enchantment + * implementation is invoked */ - protected final void testBasicWeaponActions(final Weapon weapon) { + final void testBasicWeaponActions(final Weapon weapon) { assertNotNull(weapon); - Enchantment enchantment = weapon.getEnchantment(); + var enchantment = weapon.getEnchantment(); assertNotNull(enchantment); assertNotNull(weapon.getEnchantment()); diff --git a/builder/README.md b/builder/README.md index b775f1064a1e..ccfc378d6e36 100644 --- a/builder/README.md +++ b/builder/README.md @@ -5,9 +5,7 @@ folder: builder permalink: /patterns/builder/ categories: Creational tags: - - Java - - Gang Of Four - - Difficulty-Intermediate + - Gang of Four --- ## Intent @@ -110,9 +108,12 @@ And then we have the builder And then it can be used as: ```java -Hero mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build(); +var mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build(); ``` +## Class diagram +![alt text](./etc/builder.urm.png "Builder class diagram") + ## Applicability Use the Builder pattern when @@ -126,6 +127,7 @@ Use the Builder pattern when * [java.lang.StringBuffer](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html#append-boolean-) * All implementations of [java.lang.Appendable](http://docs.oracle.com/javase/8/docs/api/java/lang/Appendable.html) * [Apache Camel builders](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder) +* [Apache Commons Option.Builder](https://commons.apache.org/proper/commons-cli/apidocs/org/apache/commons/cli/Option.Builder.html) ## Credits diff --git a/builder/etc/builder.urm.png b/builder/etc/builder.urm.png new file mode 100644 index 000000000000..d77808d36097 Binary files /dev/null and b/builder/etc/builder.urm.png differ diff --git a/builder/etc/builder.urm.puml b/builder/etc/builder.urm.puml new file mode 100644 index 000000000000..43d595a176fc --- /dev/null +++ b/builder/etc/builder.urm.puml @@ -0,0 +1,100 @@ +@startuml +package com.iluwatar.builder { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + enum Armor { + + CHAIN_MAIL {static} + + CLOTHES {static} + + LEATHER {static} + + PLATE_MAIL {static} + - title : String + + toString() : String + + valueOf(name : String) : Armor {static} + + values() : Armor[] {static} + } + enum HairColor { + + BLACK {static} + + BLOND {static} + + BROWN {static} + + RED {static} + + WHITE {static} + + toString() : String + + valueOf(name : String) : HairColor {static} + + values() : HairColor[] {static} + } + enum HairType { + + BALD {static} + + CURLY {static} + + LONG_CURLY {static} + + LONG_STRAIGHT {static} + + SHORT {static} + - title : String + + toString() : String + + valueOf(name : String) : HairType {static} + + values() : HairType[] {static} + } + class Hero { + - armor : Armor + - hairColor : HairColor + - hairType : HairType + - name : String + - profession : Profession + - weapon : Weapon + - Hero(builder : Builder) + + getArmor() : Armor + + getHairColor() : HairColor + + getHairType() : HairType + + getName() : String + + getProfession() : Profession + + getWeapon() : Weapon + + toString() : String + } + class Builder { + - armor : Armor + - hairColor : HairColor + - hairType : HairType + - name : String + - profession : Profession + - weapon : Weapon + + Builder(profession : Profession, name : String) + + build() : Hero + + withArmor(armor : Armor) : Builder + + withHairColor(hairColor : HairColor) : Builder + + withHairType(hairType : HairType) : Builder + + withWeapon(weapon : Weapon) : Builder + } + enum Profession { + + MAGE {static} + + PRIEST {static} + + THIEF {static} + + WARRIOR {static} + + toString() : String + + valueOf(name : String) : Profession {static} + + values() : Profession[] {static} + } + enum Weapon { + + AXE {static} + + BOW {static} + + DAGGER {static} + + SWORD {static} + + WARHAMMER {static} + + toString() : String + + valueOf(name : String) : Weapon {static} + + values() : Weapon[] {static} + } +} +Hero --> "-profession" Profession +Builder ..+ Hero +Hero --> "-armor" Armor +Builder --> "-hairColor" HairColor +Builder --> "-weapon" Weapon +Builder --> "-hairType" HairType +Hero --> "-hairColor" HairColor +Builder --> "-profession" Profession +Hero --> "-weapon" Weapon +Hero --> "-hairType" HairType +Builder --> "-armor" Armor +@enduml \ No newline at end of file diff --git a/builder/pom.xml b/builder/pom.xml index 9130209fffd2..dab9c66a7f5b 100644 --- a/builder/pom.xml +++ b/builder/pom.xml @@ -1,47 +1,54 @@ - - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.21.0-SNAPSHOT - - builder - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + builder + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.builder.App + + + + + + + + diff --git a/builder/src/main/java/com/iluwatar/builder/App.java b/builder/src/main/java/com/iluwatar/builder/App.java index 9fcfdb65ac57..76e514749285 100644 --- a/builder/src/main/java/com/iluwatar/builder/App.java +++ b/builder/src/main/java/com/iluwatar/builder/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.builder; import com.iluwatar.builder.Hero.Builder; @@ -27,54 +28,53 @@ import org.slf4j.LoggerFactory; /** - * * The intention of the Builder pattern is to find a solution to the telescoping constructor * anti-pattern. The telescoping constructor anti-pattern occurs when the increase of object * constructor parameter combination leads to an exponential list of constructors. Instead of using * numerous constructors, the builder pattern uses another object, a builder, that receives each * initialization parameter step by step and then returns the resulting constructed object at once. - *

- * The Builder pattern has another benefit. It can be used for objects that contain flat data (html - * code, SQL query, X.509 certificate...), that is to say, data that can't be easily edited. This - * type of data cannot be edited step by step and must be edited at once. The best way to construct - * such an object is to use a builder class. - *

- * In this example we have the Builder pattern variation as described by Joshua Bloch in Effective - * Java 2nd Edition. - *

- * We want to build {@link Hero} objects, but its construction is complex because of the many - * parameters needed. To aid the user we introduce {@link Builder} class. {@link Hero.Builder} - * takes the minimum parameters to build {@link Hero} object in its constructor. After that - * additional configuration for the {@link Hero} object can be done using the fluent - * {@link Builder} interface. When configuration is ready the build method is called to receive - * the final {@link Hero} object. - * + * + *

The Builder pattern has another benefit. It can be used for objects that contain flat data + * (html code, SQL query, X.509 certificate...), that is to say, data that can't be easily edited. + * This type of data cannot be edited step by step and must be edited at once. The best way to + * construct such an object is to use a builder class. + * + *

In this example we have the Builder pattern variation as described by Joshua Bloch in + * Effective Java 2nd Edition. + * + *

We want to build {@link Hero} objects, but its construction is complex because of the many + * parameters needed. To aid the user we introduce {@link Builder} class. {@link Hero.Builder} takes + * the minimum parameters to build {@link Hero} object in its constructor. After that additional + * configuration for the {@link Hero} object can be done using the fluent {@link Builder} interface. + * When configuration is ready the build method is called to receive the final {@link Hero} object. */ public class App { private static final Logger LOGGER = LoggerFactory.getLogger(App.class); /** - * Program entry point - * + * Program entry point. + * * @param args command line args */ public static void main(String[] args) { - Hero mage = - new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK) - .withWeapon(Weapon.DAGGER).build(); + var mage = new Hero.Builder(Profession.MAGE, "Riobard") + .withHairColor(HairColor.BLACK) + .withWeapon(Weapon.DAGGER) + .build(); LOGGER.info(mage.toString()); - Hero warrior = - new Hero.Builder(Profession.WARRIOR, "Amberjill").withHairColor(HairColor.BLOND) - .withHairType(HairType.LONG_CURLY).withArmor(Armor.CHAIN_MAIL).withWeapon(Weapon.SWORD) - .build(); + var warrior = new Hero.Builder(Profession.WARRIOR, "Amberjill") + .withHairColor(HairColor.BLOND) + .withHairType(HairType.LONG_CURLY).withArmor(Armor.CHAIN_MAIL).withWeapon(Weapon.SWORD) + .build(); LOGGER.info(warrior.toString()); - Hero thief = - new Hero.Builder(Profession.THIEF, "Desmond").withHairType(HairType.BALD) - .withWeapon(Weapon.BOW).build(); + var thief = new Hero.Builder(Profession.THIEF, "Desmond") + .withHairType(HairType.BALD) + .withWeapon(Weapon.BOW) + .build(); LOGGER.info(thief.toString()); } diff --git a/builder/src/main/java/com/iluwatar/builder/Armor.java b/builder/src/main/java/com/iluwatar/builder/Armor.java index 0deb9712c2bc..5a20a0824318 100644 --- a/builder/src/main/java/com/iluwatar/builder/Armor.java +++ b/builder/src/main/java/com/iluwatar/builder/Armor.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,18 +20,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.builder; /** - * - * Armor enumeration - * + * Armor enumeration. */ public enum Armor { CLOTHES("clothes"), LEATHER("leather"), CHAIN_MAIL("chain mail"), PLATE_MAIL("plate mail"); - private String title; + private final String title; Armor(String title) { this.title = title; diff --git a/builder/src/main/java/com/iluwatar/builder/HairColor.java b/builder/src/main/java/com/iluwatar/builder/HairColor.java index a3f03abfc952..1beccff5e5b8 100644 --- a/builder/src/main/java/com/iluwatar/builder/HairColor.java +++ b/builder/src/main/java/com/iluwatar/builder/HairColor.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.builder; /** - * - * HairColor enumeration - * + * HairColor enumeration. */ public enum HairColor { diff --git a/builder/src/main/java/com/iluwatar/builder/HairType.java b/builder/src/main/java/com/iluwatar/builder/HairType.java index 604a0836d394..950f02a1b857 100644 --- a/builder/src/main/java/com/iluwatar/builder/HairType.java +++ b/builder/src/main/java/com/iluwatar/builder/HairType.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,19 +20,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.builder; /** - * - * HairType enumeration - * + * HairType enumeration. */ public enum HairType { BALD("bald"), SHORT("short"), CURLY("curly"), LONG_STRAIGHT("long straight"), LONG_CURLY( "long curly"); - private String title; + private final String title; HairType(String title) { this.title = title; diff --git a/builder/src/main/java/com/iluwatar/builder/Hero.java b/builder/src/main/java/com/iluwatar/builder/Hero.java index e7f0b3cabbec..86dc11034c5a 100644 --- a/builder/src/main/java/com/iluwatar/builder/Hero.java +++ b/builder/src/main/java/com/iluwatar/builder/Hero.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.builder; /** - * * Hero, the class with many parameters. - * */ public final class Hero { @@ -72,11 +71,11 @@ public Weapon getWeapon() { @Override public String toString() { - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); sb.append("This is a ") - .append(profession) - .append(" named ") - .append(name); + .append(profession) + .append(" named ") + .append(name); if (hairColor != null || hairType != null) { sb.append(" with "); if (hairColor != null) { @@ -98,9 +97,7 @@ public String toString() { } /** - * * The builder class. - * */ public static class Builder { @@ -112,7 +109,7 @@ public static class Builder { private Weapon weapon; /** - * Constructor + * Constructor. */ public Builder(Profession profession, String name) { if (profession == null || name == null) { diff --git a/builder/src/main/java/com/iluwatar/builder/Profession.java b/builder/src/main/java/com/iluwatar/builder/Profession.java index 579eed787baf..23b1ac220c92 100644 --- a/builder/src/main/java/com/iluwatar/builder/Profession.java +++ b/builder/src/main/java/com/iluwatar/builder/Profession.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.builder; /** - * - * Profession enumeration - * + * Profession enumeration. */ public enum Profession { diff --git a/builder/src/main/java/com/iluwatar/builder/Weapon.java b/builder/src/main/java/com/iluwatar/builder/Weapon.java index 86c910a44634..4ca78b95ff20 100644 --- a/builder/src/main/java/com/iluwatar/builder/Weapon.java +++ b/builder/src/main/java/com/iluwatar/builder/Weapon.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.builder; /** - * - * Weapon enumeration - * + * Weapon enumeration. */ public enum Weapon { diff --git a/builder/src/test/java/com/iluwatar/builder/AppTest.java b/builder/src/test/java/com/iluwatar/builder/AppTest.java index abaaa034f734..941f62f75392 100644 --- a/builder/src/test/java/com/iluwatar/builder/AppTest.java +++ b/builder/src/test/java/com/iluwatar/builder/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,20 +20,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.builder; import org.junit.jupiter.api.Test; /** - * * Application test - * */ -public class AppTest { - +class AppTest { @Test - public void test() { - String[] args = {}; - App.main(args); + void test() { + App.main(new String[]{}); } } diff --git a/builder/src/test/java/com/iluwatar/builder/HeroTest.java b/builder/src/test/java/com/iluwatar/builder/HeroTest.java index aa2e1490f72d..97c984a70092 100644 --- a/builder/src/test/java/com/iluwatar/builder/HeroTest.java +++ b/builder/src/test/java/com/iluwatar/builder/HeroTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,26 +20,27 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.builder; -import org.junit.jupiter.api.Test; +package com.iluwatar.builder; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.Test; + /** * Date: 12/6/15 - 11:01 PM * * @author Jeroen Meulemeester */ -public class HeroTest { +class HeroTest { /** * Test if we get the expected exception when trying to create a hero without a profession */ @Test - public void testMissingProfession() throws Exception { + void testMissingProfession() { assertThrows(IllegalArgumentException.class, () -> new Hero.Builder(null, "Sir without a job")); } @@ -47,7 +48,7 @@ public void testMissingProfession() throws Exception { * Test if we get the expected exception when trying to create a hero without a name */ @Test - public void testMissingName() throws Exception { + void testMissingName() { assertThrows(IllegalArgumentException.class, () -> new Hero.Builder(Profession.THIEF, null)); } @@ -55,10 +56,10 @@ public void testMissingName() throws Exception { * Test if the hero build by the builder has the correct attributes, as requested */ @Test - public void testBuildHero() throws Exception { + void testBuildHero() { final String heroName = "Sir Lancelot"; - final Hero hero = new Hero.Builder(Profession.WARRIOR, heroName) + final var hero = new Hero.Builder(Profession.WARRIOR, heroName) .withArmor(Armor.CHAIN_MAIL) .withWeapon(Weapon.SWORD) .withHairType(HairType.LONG_CURLY) diff --git a/business-delegate/README.md b/business-delegate/README.md index e6e2491222e0..38eb591b8549 100644 --- a/business-delegate/README.md +++ b/business-delegate/README.md @@ -3,10 +3,9 @@ layout: pattern title: Business Delegate folder: business-delegate permalink: /patterns/business-delegate/ -categories: Business Tier +categories: Structural tags: - - Java - - Difficulty-Intermediate + - Decoupling --- ## Intent @@ -15,6 +14,7 @@ presentation and business tiers. By using the pattern we gain loose coupling between the tiers and encapsulate knowledge about how to locate, connect to, and interact with the business objects that make up the application. +## Class diagram ![alt text](./etc/business-delegate.png "Business Delegate") ## Applicability diff --git a/business-delegate/etc/business-delegate.urm.puml b/business-delegate/etc/business-delegate.urm.puml new file mode 100644 index 000000000000..40aa2d6f0997 --- /dev/null +++ b/business-delegate/etc/business-delegate.urm.puml @@ -0,0 +1,57 @@ +@startuml +package com.iluwatar.business.delegate { + class App { + + App() + + main(args : String[]) {static} + } + class BusinessDelegate { + - businessService : BusinessService + - lookupService : BusinessLookup + - serviceType : ServiceType + + BusinessDelegate() + + doTask() + + setLookupService(businessLookup : BusinessLookup) + + setServiceType(serviceType : ServiceType) + } + class BusinessLookup { + - ejbService : EjbService + - jmsService : JmsService + + BusinessLookup() + + getBusinessService(serviceType : ServiceType) : BusinessService + + setEjbService(ejbService : EjbService) + + setJmsService(jmsService : JmsService) + } + interface BusinessService { + + doProcessing() {abstract} + } + class Client { + - businessDelegate : BusinessDelegate + + Client(businessDelegate : BusinessDelegate) + + doTask() + } + class EjbService { + - LOGGER : Logger {static} + + EjbService() + + doProcessing() + } + class JmsService { + - LOGGER : Logger {static} + + JmsService() + + doProcessing() + } + enum ServiceType { + + EJB {static} + + JMS {static} + + valueOf(name : String) : ServiceType {static} + + values() : ServiceType[] {static} + } +} +BusinessLookup --> "-ejbService" EjbService +BusinessDelegate --> "-serviceType" ServiceType +Client --> "-businessDelegate" BusinessDelegate +BusinessDelegate --> "-businessService" BusinessService +BusinessDelegate --> "-lookupService" BusinessLookup +BusinessLookup --> "-jmsService" JmsService +EjbService ..|> BusinessService +JmsService ..|> BusinessService +@enduml \ No newline at end of file diff --git a/business-delegate/pom.xml b/business-delegate/pom.xml index 70ad2a1137b3..26987c73aef1 100644 --- a/business-delegate/pom.xml +++ b/business-delegate/pom.xml @@ -1,44 +1,31 @@ - + + xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 com.iluwatar java-design-patterns - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT business-delegate - - org.junit.jupiter - junit-jupiter-api - test - org.junit.jupiter junit-jupiter-engine @@ -50,4 +37,23 @@ test + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.business.delegate.App + + + + + + + + diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java index 945f09c0a52f..115234df7123 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.business.delegate; /** @@ -27,11 +28,11 @@ * tiers. By using the pattern we gain loose coupling between the tiers. The Business Delegate * encapsulates knowledge about how to locate, connect to, and interact with the business objects * that make up the application. - * + * *

Some of the services the Business Delegate uses are instantiated directly, and some can be * retrieved through service lookups. The Business Delegate itself may contain business logic too * potentially tying together multiple service calls, exception handling, retrying etc. - * + * *

In this example the client ({@link Client}) utilizes a business delegate ( * {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate * service and makes the service call. @@ -45,15 +46,15 @@ public class App { */ public static void main(String[] args) { - BusinessDelegate businessDelegate = new BusinessDelegate(); - BusinessLookup businessLookup = new BusinessLookup(); + var businessDelegate = new BusinessDelegate(); + var businessLookup = new BusinessLookup(); businessLookup.setEjbService(new EjbService()); businessLookup.setJmsService(new JmsService()); businessDelegate.setLookupService(businessLookup); businessDelegate.setServiceType(ServiceType.EJB); - Client client = new Client(businessDelegate); + var client = new Client(businessDelegate); client.doTask(); businessDelegate.setServiceType(ServiceType.JMS); 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 32b2bc770c36..c39a2ad39372 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 @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,10 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.business.delegate; /** - * BusinessDelegate separates the presentation and business tiers + * BusinessDelegate separates the presentation and business tiers. */ public class BusinessDelegate { diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java index ae3c54b864d9..489caa23c407 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.business.delegate; /** @@ -32,6 +33,8 @@ public class BusinessLookup { private JmsService jmsService; /** + * Gets service instance based on service type. + * * @param serviceType Type of service instance to be returned. * @return Service instance. */ diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessService.java index fbda6c67eabc..3094d3f6e95f 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessService.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessService.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.business.delegate; /** - * - * Interface for service implementations - * + * Interface for service implementations. */ public interface BusinessService { diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/Client.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/Client.java index e521426aec71..dcf4ce6b29d3 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/Client.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/Client.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.business.delegate; /** - * - * Client utilizes BusinessDelegate to call the business tier - * + * Client utilizes BusinessDelegate to call the business tier. */ public class Client { diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/EjbService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/EjbService.java index afe53600aaab..6f39abb1ae42 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/EjbService.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/EjbService.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,15 +20,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.business.delegate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * - * Service EJB implementation - * + * Service EJB implementation. */ public class EjbService implements BusinessService { diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/JmsService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/JmsService.java index 4b112906efba..2317d783af0f 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/JmsService.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/JmsService.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,15 +20,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.business.delegate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * - * Service JMS implementation - * + * Service JMS implementation. */ public class JmsService implements BusinessService { diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/ServiceType.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/ServiceType.java index ac82d7daf628..87fd1562da66 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/ServiceType.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/ServiceType.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.business.delegate; /** - * - * Enumeration for service types - * + * Enumeration for service types. */ public enum ServiceType { diff --git a/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java b/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java index 415f855d1d33..48e756acb816 100644 --- a/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java +++ b/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.business.delegate; import org.junit.jupiter.api.Test; diff --git a/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java b/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java index ccdaaae4ea4b..10815ad3a26d 100644 --- a/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java +++ b/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.business.delegate; import org.junit.jupiter.api.BeforeEach; @@ -75,7 +76,7 @@ public void setup() { public void testBusinessDelegate() { // setup a client object - Client client = new Client(businessDelegate); + var client = new Client(businessDelegate); // set the service type businessDelegate.setServiceType(ServiceType.EJB); diff --git a/bytecode/README.md b/bytecode/README.md new file mode 100644 index 000000000000..ee3f96ed8ac3 --- /dev/null +++ b/bytecode/README.md @@ -0,0 +1,27 @@ +--- +layout: pattern +title: Bytecode +folder: bytecode +permalink: /patterns/bytecode/ +categories: Behavioral +tags: + - Game programming +--- + +## Intent +Allows to encode behaviour as instructions for virtual machine. + +## Class diagram +![alt text](./etc/bytecode.urm.png "Bytecode class diagram") + +## Applicability +Use the Bytecode pattern when you have a lot of behavior you need to define and your +game’s implementation language isn’t a good fit because: + +* it’s too low-level, making it tedious or error-prone to program in. +* iterating on it takes too long due to slow compile times or other tooling issues. +* it has too much trust. If you want to ensure the behavior being defined can’t break the game, you need to sandbox it from the rest of the codebase. + +## Credits + +* [Game programming patterns](http://gameprogrammingpatterns.com/bytecode.html) diff --git a/bytecode/etc/bytecode.png b/bytecode/etc/bytecode.png new file mode 100644 index 000000000000..31b6bc6edba6 Binary files /dev/null and b/bytecode/etc/bytecode.png differ diff --git a/bytecode/etc/bytecode.ucls b/bytecode/etc/bytecode.ucls new file mode 100644 index 000000000000..3ec390458956 --- /dev/null +++ b/bytecode/etc/bytecode.ucls @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bytecode/etc/bytecode.urm.png b/bytecode/etc/bytecode.urm.png new file mode 100644 index 000000000000..82036a78a9b1 Binary files /dev/null and b/bytecode/etc/bytecode.urm.png differ diff --git a/bytecode/etc/bytecode.urm.puml b/bytecode/etc/bytecode.urm.puml new file mode 100644 index 000000000000..d675ae398c6c --- /dev/null +++ b/bytecode/etc/bytecode.urm.puml @@ -0,0 +1,69 @@ +@startuml +package com.iluwatar.bytecode { + class App { + - LOGGER : Logger {static} + + App() + - interpretInstruction(instruction : String, vm : VirtualMachine) {static} + + main(args : String[]) {static} + } + enum Instruction { + + ADD {static} + + DIVIDE {static} + + GET_AGILITY {static} + + GET_HEALTH {static} + + GET_WISDOM {static} + + LITERAL {static} + + PLAY_SOUND {static} + + SET_AGILITY {static} + + SET_HEALTH {static} + + SET_WISDOM {static} + + SPAWN_PARTICLES {static} + - value : int + + getInstruction(value : int) : Instruction {static} + + getIntValue() : int + + valueOf(name : String) : Instruction {static} + + values() : Instruction[] {static} + } + class VirtualMachine { + - stack : Stack + - wizards : Wizard[] + + VirtualMachine() + + execute(bytecode : int[]) + + getAgility(wizard : int) : int + + getHealth(wizard : int) : int + + getStack() : Stack + + getWisdom(wizard : int) : int + + getWizards() : Wizard[] + + setAgility(wizard : int, amount : int) + + setHealth(wizard : int, amount : int) + + setWisdom(wizard : int, amount : int) + } + class Wizard { + - LOGGER : Logger {static} + - agility : int + - health : int + - numberOfPlayedSounds : int + - numberOfSpawnedParticles : int + - wisdom : int + + Wizard() + + getAgility() : int + + getHealth() : int + + getNumberOfPlayedSounds() : int + + getNumberOfSpawnedParticles() : int + + getWisdom() : int + + playSound() + + setAgility(agility : int) + + setHealth(health : int) + + setWisdom(wisdom : int) + + spawnParticles() + } +} +package com.iluwatar.bytecode.util { + class InstructionConverterUtil { + + InstructionConverterUtil() + + convertToByteCode(instructions : String) : int[] {static} + - isValidInstruction(instruction : String) : boolean {static} + - isValidInt(value : String) : boolean {static} + } +} +@enduml \ No newline at end of file diff --git a/bytecode/pom.xml b/bytecode/pom.xml new file mode 100644 index 000000000000..f6be69ceede7 --- /dev/null +++ b/bytecode/pom.xml @@ -0,0 +1,56 @@ + + + + + java-design-patterns + com.iluwatar + 1.23.0-SNAPSHOT + + 4.0.0 + + bytecode + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.bytecode.App + + + + + + + + + + \ No newline at end of file diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/App.java b/bytecode/src/main/java/com/iluwatar/bytecode/App.java new file mode 100644 index 000000000000..04f473ceeac2 --- /dev/null +++ b/bytecode/src/main/java/com/iluwatar/bytecode/App.java @@ -0,0 +1,80 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.bytecode; + +import com.iluwatar.bytecode.util.InstructionConverterUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The intention of Bytecode pattern is to give behavior the flexibility of data by encoding it as + * instructions for a virtual machine. An instruction set defines the low-level operations that can + * be performed. A series of instructions is encoded as a sequence of bytes. A virtual machine + * executes these instructions one at a time, using a stack for intermediate values. By combining + * instructions, complex high-level behavior can be defined. + * + *

This pattern should be used when there is a need to define high number of behaviours and + * implementation engine is not a good choice because It is too lowe level Iterating on it takes too + * long due to slow compile times or other tooling issues. It has too much trust. If you want to + * ensure the behavior being defined can’t break the game, you need to sandbox it from the rest of + * the codebase. + */ +public class App { + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Main app method. + * + * @param args command line args + */ + public static void main(String[] args) { + + var wizard = new Wizard(); + wizard.setHealth(45); + wizard.setAgility(7); + wizard.setWisdom(11); + + var vm = new VirtualMachine(); + vm.getWizards()[0] = wizard; + + interpretInstruction("LITERAL 0", vm); + interpretInstruction("LITERAL 0", vm); + interpretInstruction("GET_HEALTH", vm); + interpretInstruction("LITERAL 0", vm); + interpretInstruction("GET_AGILITY", vm); + interpretInstruction("LITERAL 0", vm); + interpretInstruction("GET_WISDOM ", vm); + interpretInstruction("ADD", vm); + interpretInstruction("LITERAL 2", vm); + interpretInstruction("DIVIDE", vm); + interpretInstruction("ADD", vm); + interpretInstruction("SET_HEALTH", vm); + } + + private static void interpretInstruction(String instruction, VirtualMachine vm) { + vm.execute(InstructionConverterUtil.convertToByteCode(instruction)); + var stack = vm.getStack(); + LOGGER.info(instruction + String.format("%" + (12 - instruction.length()) + "s", "") + stack); + } +} diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java b/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java new file mode 100644 index 000000000000..eeaf16846087 --- /dev/null +++ b/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java @@ -0,0 +1,67 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.bytecode; + +/** + * Representation of instructions understandable by virtual machine. + */ +public enum Instruction { + + LITERAL(1), + SET_HEALTH(2), + SET_WISDOM(3), + SET_AGILITY(4), + PLAY_SOUND(5), + SPAWN_PARTICLES(6), + GET_HEALTH(7), + GET_AGILITY(8), + GET_WISDOM(9), + ADD(10), + DIVIDE(11); + + private final int value; + + Instruction(int value) { + this.value = value; + } + + public int getIntValue() { + return value; + } + + /** + * Converts integer value to Instruction. + * + * @param value value of instruction + * @return representation of the instruction + */ + public static Instruction getInstruction(int value) { + for (var i = 0; i < Instruction.values().length; i++) { + if (Instruction.values()[i].getIntValue() == value) { + return Instruction.values()[i]; + } + } + throw new IllegalArgumentException("Invalid instruction value"); + } +} diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java b/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java new file mode 100644 index 000000000000..5afc2fb93469 --- /dev/null +++ b/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java @@ -0,0 +1,142 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.bytecode; + +import java.util.Stack; + +/** + * Implementation of virtual machine. + */ +public class VirtualMachine { + + private Stack stack = new Stack<>(); + + private Wizard[] wizards = new Wizard[2]; + + /** + * Constructor. + */ + public VirtualMachine() { + for (var i = 0; i < wizards.length; i++) { + wizards[i] = new Wizard(); + } + } + + /** + * Executes provided bytecode. + * + * @param bytecode to execute + */ + public void execute(int[] bytecode) { + for (var i = 0; i < bytecode.length; i++) { + Instruction instruction = Instruction.getInstruction(bytecode[i]); + switch (instruction) { + case LITERAL: + // Read the next byte from the bytecode. + int value = bytecode[++i]; + stack.push(value); + break; + case SET_AGILITY: + var amount = stack.pop(); + var wizard = stack.pop(); + setAgility(wizard, amount); + break; + case SET_WISDOM: + amount = stack.pop(); + wizard = stack.pop(); + setWisdom(wizard, amount); + break; + case SET_HEALTH: + amount = stack.pop(); + wizard = stack.pop(); + setHealth(wizard, amount); + break; + case GET_HEALTH: + wizard = stack.pop(); + stack.push(getHealth(wizard)); + break; + case GET_AGILITY: + wizard = stack.pop(); + stack.push(getAgility(wizard)); + break; + case GET_WISDOM: + wizard = stack.pop(); + stack.push(getWisdom(wizard)); + break; + case ADD: + var a = stack.pop(); + var b = stack.pop(); + stack.push(a + b); + break; + case DIVIDE: + a = stack.pop(); + b = stack.pop(); + stack.push(b / a); + break; + case PLAY_SOUND: + wizard = stack.pop(); + getWizards()[wizard].playSound(); + break; + case SPAWN_PARTICLES: + wizard = stack.pop(); + getWizards()[wizard].spawnParticles(); + break; + default: + throw new IllegalArgumentException("Invalid instruction value"); + } + } + } + + public Stack getStack() { + return stack; + } + + public void setHealth(int wizard, int amount) { + wizards[wizard].setHealth(amount); + } + + public void setWisdom(int wizard, int amount) { + wizards[wizard].setWisdom(amount); + } + + public void setAgility(int wizard, int amount) { + wizards[wizard].setAgility(amount); + } + + public int getHealth(int wizard) { + return wizards[wizard].getHealth(); + } + + public int getWisdom(int wizard) { + return wizards[wizard].getWisdom(); + } + + public int getAgility(int wizard) { + return wizards[wizard].getAgility(); + } + + public Wizard[] getWizards() { + return wizards; + } +} diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/Wizard.java b/bytecode/src/main/java/com/iluwatar/bytecode/Wizard.java new file mode 100644 index 000000000000..5153969d9145 --- /dev/null +++ b/bytecode/src/main/java/com/iluwatar/bytecode/Wizard.java @@ -0,0 +1,85 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.bytecode; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class represent game objects which properties can be changed by instructions interpreted by + * virtual machine. + */ +public class Wizard { + private static final Logger LOGGER = LoggerFactory.getLogger(Wizard.class); + + private int health; + + private int agility; + private int wisdom; + + private int numberOfPlayedSounds; + private int numberOfSpawnedParticles; + + public int getHealth() { + return health; + } + + public void setHealth(int health) { + this.health = health; + } + + public int getAgility() { + return agility; + } + + public void setAgility(int agility) { + this.agility = agility; + } + + public int getWisdom() { + return wisdom; + } + + public void setWisdom(int wisdom) { + this.wisdom = wisdom; + } + + public void playSound() { + LOGGER.info("Playing sound"); + numberOfPlayedSounds++; + } + + public void spawnParticles() { + LOGGER.info("Spawning particles"); + numberOfSpawnedParticles++; + } + + public int getNumberOfPlayedSounds() { + return numberOfPlayedSounds; + } + + public int getNumberOfSpawnedParticles() { + return numberOfSpawnedParticles; + } +} diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java b/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java new file mode 100644 index 000000000000..b0baf326e461 --- /dev/null +++ b/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java @@ -0,0 +1,78 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.bytecode.util; + +import com.iluwatar.bytecode.Instruction; + +/** + * Utility class used for instruction validation and conversion. + */ +public class InstructionConverterUtil { + /** + * Converts instructions represented as String. + * + * @param instructions to convert + * @return array of int representing bytecode + */ + public static int[] convertToByteCode(String instructions) { + if (instructions == null || instructions.trim().length() == 0) { + return new int[0]; + } + + var splitedInstructions = instructions.trim().split(" "); + var bytecode = new int[splitedInstructions.length]; + for (var i = 0; i < splitedInstructions.length; i++) { + if (isValidInstruction(splitedInstructions[i])) { + bytecode[i] = Instruction.valueOf(splitedInstructions[i]).getIntValue(); + } else if (isValidInt(splitedInstructions[i])) { + bytecode[i] = Integer.parseInt(splitedInstructions[i]); + } else { + var errorMessage = "Invalid instruction or number: " + splitedInstructions[i]; + throw new IllegalArgumentException(errorMessage); + } + } + + return bytecode; + } + + private static boolean isValidInstruction(String instruction) { + try { + Instruction.valueOf(instruction); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + private static boolean isValidInt(String value) { + try { + Integer.parseInt(value); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + +} diff --git a/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java b/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java new file mode 100644 index 000000000000..59962d39e54f --- /dev/null +++ b/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java @@ -0,0 +1,37 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.bytecode; + +import org.junit.jupiter.api.Test; + +/** + * Application test + */ +public class AppTest { + + @Test + public void test() { + App.main(new String[]{}); + } +} diff --git a/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java b/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java new file mode 100644 index 000000000000..61a316f5a67b --- /dev/null +++ b/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java @@ -0,0 +1,155 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.bytecode; + +import org.junit.jupiter.api.Test; + +import static com.iluwatar.bytecode.Instruction.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Test for {@Link VirtualMachine} + */ +public class VirtualMachineTest { + + @Test + public void testLiteral() { + var bytecode = new int[2]; + bytecode[0] = LITERAL.getIntValue(); + bytecode[1] = 10; + + var vm = new VirtualMachine(); + vm.execute(bytecode); + + assertEquals(1, vm.getStack().size()); + assertEquals(Integer.valueOf(10), vm.getStack().pop()); + } + + @Test + public void testSetHealth() { + var wizardNumber = 0; + var bytecode = new int[5]; + bytecode[0] = LITERAL.getIntValue(); + bytecode[1] = wizardNumber; + bytecode[2] = LITERAL.getIntValue(); + bytecode[3] = 50; // health amount + bytecode[4] = SET_HEALTH.getIntValue(); + + var vm = new VirtualMachine(); + vm.execute(bytecode); + + assertEquals(50, vm.getWizards()[wizardNumber].getHealth()); + } + + @Test + public void testSetAgility() { + var wizardNumber = 0; + var bytecode = new int[5]; + bytecode[0] = LITERAL.getIntValue(); + bytecode[1] = wizardNumber; + bytecode[2] = LITERAL.getIntValue(); + bytecode[3] = 50; // agility amount + bytecode[4] = SET_AGILITY.getIntValue(); + + var vm = new VirtualMachine(); + vm.execute(bytecode); + + assertEquals(50, vm.getWizards()[wizardNumber].getAgility()); + } + + @Test + public void testSetWisdom() { + var wizardNumber = 0; + var bytecode = new int[5]; + bytecode[0] = LITERAL.getIntValue(); + bytecode[1] = wizardNumber; + bytecode[2] = LITERAL.getIntValue(); + bytecode[3] = 50; // wisdom amount + bytecode[4] = SET_WISDOM.getIntValue(); + + var vm = new VirtualMachine(); + vm.execute(bytecode); + + assertEquals(50, vm.getWizards()[wizardNumber].getWisdom()); + } + + @Test + public void testGetHealth() { + var wizardNumber = 0; + var bytecode = new int[8]; + bytecode[0] = LITERAL.getIntValue(); + bytecode[1] = wizardNumber; + bytecode[2] = LITERAL.getIntValue(); + bytecode[3] = 50; // health amount + bytecode[4] = SET_HEALTH.getIntValue(); + bytecode[5] = LITERAL.getIntValue();; + bytecode[6] = wizardNumber; + bytecode[7] = GET_HEALTH.getIntValue(); + + var vm = new VirtualMachine(); + vm.execute(bytecode); + + assertEquals(Integer.valueOf(50), vm.getStack().pop()); + } + + @Test + public void testPlaySound() { + var wizardNumber = 0; + var bytecode = new int[3]; + bytecode[0] = LITERAL.getIntValue(); + bytecode[1] = wizardNumber; + bytecode[2] = PLAY_SOUND.getIntValue(); + + var vm = new VirtualMachine(); + vm.execute(bytecode); + + assertEquals(0, vm.getStack().size()); + assertEquals(1, vm.getWizards()[0].getNumberOfPlayedSounds()); + } + + @Test + public void testSpawnParticles() { + var wizardNumber = 0; + var bytecode = new int[3]; + bytecode[0] = LITERAL.getIntValue(); + bytecode[1] = wizardNumber; + bytecode[2] = SPAWN_PARTICLES.getIntValue(); + + var vm = new VirtualMachine(); + vm.execute(bytecode); + + assertEquals(0, vm.getStack().size()); + assertEquals(1, vm.getWizards()[0].getNumberOfSpawnedParticles()); + } + + @Test + public void testInvalidInstruction() { + var bytecode = new int[1]; + bytecode[0] = 999; + var vm = new VirtualMachine(); + + assertThrows(IllegalArgumentException.class, () -> vm.execute(bytecode)); + } +} diff --git a/bytecode/src/test/java/com/iluwatar/bytecode/util/InstructionConverterUtilTest.java b/bytecode/src/test/java/com/iluwatar/bytecode/util/InstructionConverterUtilTest.java new file mode 100644 index 000000000000..e7438fce16a8 --- /dev/null +++ b/bytecode/src/test/java/com/iluwatar/bytecode/util/InstructionConverterUtilTest.java @@ -0,0 +1,64 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.bytecode.util; + +import com.iluwatar.bytecode.Instruction; +import com.iluwatar.bytecode.util.InstructionConverterUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Test for {@Link InstructionConverterUtil} + */ +public class InstructionConverterUtilTest { + @Test + public void testEmptyInstruction() { + var instruction = ""; + + var bytecode = InstructionConverterUtil.convertToByteCode(instruction); + + Assertions.assertEquals(0, bytecode.length); + } + + @Test + public void testInstructions() { + var instructions = "LITERAL 35 SET_HEALTH SET_WISDOM SET_AGILITY PLAY_SOUND" + + " SPAWN_PARTICLES GET_HEALTH ADD DIVIDE"; + + var bytecode = InstructionConverterUtil.convertToByteCode(instructions); + + Assertions.assertEquals(10, bytecode.length); + Assertions.assertEquals(Instruction.LITERAL.getIntValue(), bytecode[0]); + Assertions.assertEquals(35, bytecode[1]); + Assertions.assertEquals(Instruction.SET_HEALTH.getIntValue(), bytecode[2]); + Assertions.assertEquals(Instruction.SET_WISDOM.getIntValue(), bytecode[3]); + Assertions.assertEquals(Instruction.SET_AGILITY.getIntValue(), bytecode[4]); + Assertions.assertEquals(Instruction.PLAY_SOUND.getIntValue(), bytecode[5]); + Assertions.assertEquals(Instruction.SPAWN_PARTICLES.getIntValue(), bytecode[6]); + Assertions.assertEquals(Instruction.GET_HEALTH.getIntValue(), bytecode[7]); + Assertions.assertEquals(Instruction.ADD.getIntValue(), bytecode[8]); + Assertions.assertEquals(Instruction.DIVIDE.getIntValue(), bytecode[9]); + } + +} diff --git a/caching/README.md b/caching/README.md index 7184d51d486d..4172cc72ab62 100644 --- a/caching/README.md +++ b/caching/README.md @@ -3,10 +3,8 @@ layout: pattern title: Caching folder: caching permalink: /patterns/caching/ -categories: Other +categories: Behavioral tags: - - Java - - Difficulty-Intermediate - Performance --- @@ -15,6 +13,7 @@ To avoid expensive re-acquisition of resources by not releasing the resources immediately after their use. The resources retain their identity, are kept in some fast-access storage, and are re-used to avoid having to acquire them again. +## Class diagram ![alt text](./etc/caching.png "Caching") ## Applicability diff --git a/caching/etc/caching.urm.puml b/caching/etc/caching.urm.puml new file mode 100644 index 000000000000..a9dae801eb20 --- /dev/null +++ b/caching/etc/caching.urm.puml @@ -0,0 +1,119 @@ +@startuml +package com.iluwatar.caching.constants { + class CachingConstants { + + ADD_INFO : String {static} + + USER_ACCOUNT : String {static} + + USER_ID : String {static} + + USER_NAME : String {static} + + CachingConstants() + } +} +package com.iluwatar.caching { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + + useCacheAsideStategy() + + useReadAndWriteThroughStrategy() + + useReadThroughAndWriteAroundStrategy() + + useReadThroughAndWriteBehindStrategy() + } + class AppManager { + - cachingPolicy : CachingPolicy {static} + - AppManager() + + find(userId : String) : UserAccount {static} + - findAside(userId : String) : UserAccount {static} + + initCacheCapacity(capacity : int) {static} + + initCachingPolicy(policy : CachingPolicy) {static} + + initDb(useMongoDb : boolean) {static} + + printCacheContent() : String {static} + + save(userAccount : UserAccount) {static} + - saveAside(userAccount : UserAccount) {static} + } + class CacheStore { + - LOGGER : Logger {static} + - cache : LruCache {static} + - CacheStore() + + clearCache() {static} + + flushCache() {static} + + get(userId : String) : UserAccount {static} + + initCapacity(capacity : int) {static} + + invalidate(userId : String) {static} + + print() : String {static} + + readThrough(userId : String) : UserAccount {static} + + readThroughWithWriteBackPolicy(userId : String) : UserAccount {static} + + set(userId : String, userAccount : UserAccount) {static} + + writeAround(userAccount : UserAccount) {static} + + writeBehind(userAccount : UserAccount) {static} + + writeThrough(userAccount : UserAccount) {static} + } + enum CachingPolicy { + + AROUND {static} + + ASIDE {static} + + BEHIND {static} + + THROUGH {static} + - policy : String + + getPolicy() : String + + valueOf(name : String) : CachingPolicy {static} + + values() : CachingPolicy[] {static} + } + class DbManager { + - db : MongoDatabase {static} + - mongoClient : MongoClient {static} + - useMongoDB : boolean {static} + - virtualDB : Map {static} + - DbManager() + + connect() {static} + + createVirtualDb() {static} + + readFromDb(userId : String) : UserAccount {static} + + updateDb(userAccount : UserAccount) {static} + + upsertDb(userAccount : UserAccount) {static} + + writeToDb(userAccount : UserAccount) {static} + } + class LruCache { + - LOGGER : Logger {static} + ~ cache : Map + ~ capacity : int + ~ end : Node + ~ head : Node + + LruCache(capacity : int) + + clear() + + contains(userId : String) : boolean + + get(userId : String) : UserAccount + + getCacheDataInListForm() : List + + getLruData() : UserAccount + + invalidate(userId : String) + + isFull() : boolean + + remove(node : Node) + + set(userId : String, userAccount : UserAccount) + + setCapacity(newCapacity : int) + + setHead(node : Node) + } + ~class Node { + ~ next : Node + ~ previous : Node + ~ userAccount : UserAccount + ~ userId : String + + Node(this$0 : String, userId : UserAccount) + } + class UserAccount { + - additionalInfo : String + - userId : String + - userName : String + + UserAccount(userId : String, userName : String, additionalInfo : String) + + getAdditionalInfo() : String + + getUserId() : String + + getUserName() : String + + setAdditionalInfo(additionalInfo : String) + + setUserId(userId : String) + + setUserName(userName : String) + + toString() : String + } +} +Node --+ LruCache +LruCache --> "-head" Node +Node --> "-previous" Node +AppManager --> "-cachingPolicy" CachingPolicy +Node --> "-userAccount" UserAccount +CacheStore --> "-cache" LruCache +@enduml \ No newline at end of file diff --git a/caching/pom.xml b/caching/pom.xml index f86f8b2bab2f..79bde5c951b4 100644 --- a/caching/pom.xml +++ b/caching/pom.xml @@ -2,7 +2,7 @@ "-chain" RequestHandler +RequestHandler --> "-next" RequestHandler +Request --> "-requestType" RequestType +OrcCommander --|> RequestHandler +OrcOfficer --|> RequestHandler +OrcSoldier --|> RequestHandler +@enduml \ No newline at end of file diff --git a/chain/pom.xml b/chain/pom.xml index 7ff678a154ca..cf70ad1e89f0 100644 --- a/chain/pom.xml +++ b/chain/pom.xml @@ -2,7 +2,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/circuit-breaker/README.md b/circuit-breaker/README.md new file mode 100644 index 000000000000..e0ef7d1fb7eb --- /dev/null +++ b/circuit-breaker/README.md @@ -0,0 +1,190 @@ +--- +layout: pattern +title: Circuit Breaker +folder: circuit-breaker +permalink: /patterns/circuit-breaker/ +categories: Behavioral +tags: + - Performance + - Decoupling +--- + +## Intent + +Handle costly remote *procedure/service* calls in such a way that the failure of a **single** service/component cannot bring the whole application down, and we can reconnect to the service as soon as possible. + +## Explanation + +Real world example + +> Imagine a Web App that has both local (example: files and images) and remote (example: database entries) to serve. The database might not be responding due to a variety of reasons, so if the application keeps trying to read from the database using multiple threads/processes, soon all of them will hang and our entire web application will crash. We should be able to detect this situation and show the user an appropriate message so that he/she can explore other parts of the app unaffected by the database failure without any problem. + +In plain words + +> Allows us to save resources when we know a remote service failed. Useful when all parts of our application are highly decoupled from each other, and failure of one component doesn't mean the other parts will stop working. + +Wikipedia says + +> **Circuit breaker** is a design pattern used in modern software development. It is used to detect failures and encapsulates the logic of preventing a failure from constantly recurring, during maintenance, temporary external system failure or unexpected system difficulties. + +So, how does this all come together? + +## Programmatic Example +With the above example in mind we will imitate the functionality in a simple manner. We have two services: A *monitoring service* which will mimic the web app and will make both **local** and **remote** calls. + +The service architecture is as follows: + +![alt text](./etc/ServiceDiagram.PNG "Service Diagram") + +In terms of code, the End user application is: + +```java +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + public static void main(String[] args) { + var obj = new MonitoringService(); + var circuitBreaker = new CircuitBreaker(3000, 1, 2000 * 1000 * 1000); + var serverStartTime = System.nanoTime(); + while (true) { + LOGGER.info(obj.localResourceResponse()); + LOGGER.info(obj.remoteResourceResponse(circuitBreaker, serverStartTime)); + LOGGER.info(circuitBreaker.getState()); + try { + Thread.sleep(5 * 1000); + } catch (InterruptedException e) { + LOGGER.error(e.getMessage()); + } + } + } +} +``` + +The monitoring service is: + +``` java +public class MonitoringService { + + public String localResourceResponse() { + return "Local Service is working"; + } + + public String remoteResourceResponse(CircuitBreaker circuitBreaker, long serverStartTime) { + try { + return circuitBreaker.call("delayedService", serverStartTime); + } catch (Exception e) { + return e.getMessage(); + } + } +} +``` +As it can be seen, it does the call to get local resources directly, but it wraps the call to remote (costly) service in a circuit breaker object, which prevents faults as follows: + +```java +public class CircuitBreaker { + private final long timeout; + private final long retryTimePeriod; + long lastFailureTime; + int failureCount; + private final int failureThreshold; + private State state; + private final long futureTime = 1000 * 1000 * 1000 * 1000; + + CircuitBreaker(long timeout, int failureThreshold, long retryTimePeriod) { + this.state = State.CLOSED; + this.failureThreshold = failureThreshold; + this.timeout = timeout; + this.retryTimePeriod = retryTimePeriod; + this.lastFailureTime = System.nanoTime() + futureTime; + this.failureCount = 0; + } + + private void reset() { + this.failureCount = 0; + this.lastFailureTime = System.nanoTime() + futureTime; + this.state = State.CLOSED; + } + + private void recordFailure() { + failureCount = failureCount + 1; + this.lastFailureTime = System.nanoTime(); + } + + protected void setState() { + if (failureCount > failureThreshold) { + if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) { + state = State.HALF_OPEN; + } else { + state = State.OPEN; + } + } else { + state = State.CLOSED; + } + } + + public String getState() { + return state.name(); + } + + public void setStateForBypass(State state) { + this.state = state; + } + + public String call(String serviceToCall, long serverStartTime) throws Exception { + setState(); + if (state == State.OPEN) { + return "This is stale response from API"; + } else { + if (serviceToCall.equals("delayedService")) { + var delayedService = new DelayedService(20); + var response = delayedService.response(serverStartTime); + if (response.split(" ")[3].equals("working")) { + reset(); + return response; + } else { + recordFailure(); + throw new Exception("Remote service not responding"); + } + } else { + throw new Exception("Unknown Service Name"); + } + } + } +} +``` + +How does the above pattern prevent failures? Let's understand via this finite state machine implemented by it. + +![alt text](./etc/StateDiagram.PNG "State Diagram") + +- We initialize the Circuit Breaker object with certain parameters: **timeout**, **failureThreshold** and **retryTimePeriod** which help determine how resilient the API is. +- Initially, we are in the **closed** state and the remote call to API happens. +- Every time the call succeeds, we reset the state to as it was in the beginning. +- If the number of failures cross a certain threshold, we move to the **open** state, which acts just like an open circuit and prevents remote service calls from being made, thus saving resources. (Here, we return the response called ```stale response from API```) +- Once we exceed the retry timeout period, we move to the **half-open** state and make another call to the remote service again to check if the service is working so that we can serve fresh content. A *failure* sets it back to **open** state and another attempt is made after retry timeout period, while a *success* sets it to **closed** state so that everything starts working normally again. + +## Class diagram +![alt text](./etc/circuit-breaker.urm.png "Circuit Breaker class diagram") + +## Applicability +Use the Circuit Breaker pattern when + +- Building a fault-tolerant application where failure of some services shouldn't bring the entire application down. +- Building an continuously incremental/continuous delivery application, as some of it's components can be upgraded without shutting it down entirely. + +## Related Patterns + +- [Retry Pattern](https://github.com/iluwatar/java-design-patterns/tree/master/retry) + +## Real world examples + +* [Spring Circuit Breaker module](https://spring.io/guides/gs/circuit-breaker) +* [Netflix Hystrix API](https://github.com/Netflix/Hystrix) + +## Credits + +* [Understanding Circuit Breaker Pattern](https://itnext.io/understand-circuitbreaker-design-pattern-with-simple-practical-example-92a752615b42) +* [Martin Fowler on Circuit Breaker](https://martinfowler.com/bliki/CircuitBreaker.html) +* [Fault tolerance in a high volume, distributed system](https://medium.com/netflix-techblog/fault-tolerance-in-a-high-volume-distributed-system-91ab4faae74a) +* [Microsoft docs](https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker) diff --git a/circuit-breaker/etc/ServiceDiagram.PNG b/circuit-breaker/etc/ServiceDiagram.PNG new file mode 100644 index 000000000000..661f1a105818 Binary files /dev/null and b/circuit-breaker/etc/ServiceDiagram.PNG differ diff --git a/circuit-breaker/etc/StateDiagram.PNG b/circuit-breaker/etc/StateDiagram.PNG new file mode 100644 index 000000000000..38485526d342 Binary files /dev/null and b/circuit-breaker/etc/StateDiagram.PNG differ diff --git a/circuit-breaker/etc/circuit-breaker.urm.png b/circuit-breaker/etc/circuit-breaker.urm.png new file mode 100644 index 000000000000..9278ce216665 Binary files /dev/null and b/circuit-breaker/etc/circuit-breaker.urm.png differ diff --git a/circuit-breaker/etc/circuit-breaker.urm.puml b/circuit-breaker/etc/circuit-breaker.urm.puml new file mode 100644 index 000000000000..21471900283a --- /dev/null +++ b/circuit-breaker/etc/circuit-breaker.urm.puml @@ -0,0 +1,44 @@ +@startuml +package com.iluwatar.circuitbreaker { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + class CircuitBreaker { + ~ failureCount : int + - failureThreshold : int + - futureTime : long + ~ lastFailureTime : long + - retryTimePeriod : long + - state : State + - timeout : long + ~ CircuitBreaker(timeout : long, failureThreshold : int, retryTimePeriod : long) + + call(serviceToCall : String, serverStartTime : long) : String + + getState() : String + - recordFailure() + - reset() + # setState() + + setStateForBypass(state : State) + } + class DelayedService { + - delay : int + + DelayedService() + + DelayedService(delay : int) + + response(serverStartTime : long) : String + } + class MonitoringService { + + MonitoringService() + + localResourceResponse() : String + + remoteResourceResponse(circuitBreaker : CircuitBreaker, serverStartTime : long) : String + } + enum State { + + CLOSED {static} + + HALF_OPEN {static} + + OPEN {static} + + valueOf(name : String) : State {static} + + values() : State[] {static} + } +} +CircuitBreaker --> "-state" State +@enduml \ No newline at end of file diff --git a/circuit-breaker/pom.xml b/circuit-breaker/pom.xml new file mode 100644 index 000000000000..fd9f85675650 --- /dev/null +++ b/circuit-breaker/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + circuit-breaker + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.circuitbreaker.App + + + + + + + + + diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java new file mode 100644 index 000000000000..c3465d801905 --- /dev/null +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java @@ -0,0 +1,84 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.circuitbreaker; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

+ * The intention of the Circuit Builder pattern is to handle remote failures robustly, which is to + * mean that if a service is dependant on n number of other services, and m of them fail, we should + * be able to recover from that failure by ensuring that the user can still use the services that + * are actually functional, and resources are not tied up by uselessly by the services which are not + * working. However, we should also be able to detect when any of the m failing services become + * operational again, so that we can use it + *

+ *

+ * In this example, the circuit breaker pattern is demonstrated by using two services: {@link + * MonitoringService} and {@link DelayedService}. The monitoring service is responsible for calling + * two services: a local service and a remote service {@link DelayedService} , and by using the + * circuit breaker construction we ensure that if the call to remote service is going to fail, we + * are going to save our resources and not make the function call at all, by wrapping our call to + * the remote service in the circuit breaker object. + *

+ *

+ * This works as follows: The {@link CircuitBreaker} object can be in one of three states: + * Open, Closed and Half-Open, which represents the real world circuits. If the + * state is closed (initial), we assume everything is alright and perform the function call. + * However, every time the call fails, we note it and once it crosses a threshold, we set the state + * to Open, preventing any further calls to the remote server. Then, after a certain retry period + * (during which we expect thee service to recover), we make another call to the remote server and + * this state is called the Half-Open state, where it stays till the service is down, and once it + * recovers, it goes back to the closed state and the cycle continues. + *

+ */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Program entry point. + * + * @param args command line args + */ + @SuppressWarnings("squid:S2189") + public static void main(String[] args) { + //Create an object of monitoring service which makes both local and remote calls + var obj = new MonitoringService(); + //Set the circuit Breaker parameters + var circuitBreaker = new CircuitBreaker(3000, 1, 2000 * 1000 * 1000); + var serverStartTime = System.nanoTime(); + while (true) { + LOGGER.info(obj.localResourceResponse()); + LOGGER.info(obj.remoteResourceResponse(circuitBreaker, serverStartTime)); + LOGGER.info(circuitBreaker.getState()); + try { + Thread.sleep(5 * 1000); + } catch (InterruptedException e) { + LOGGER.error(e.getMessage()); + } + } + } +} diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java new file mode 100644 index 000000000000..18268b1ce107 --- /dev/null +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java @@ -0,0 +1,137 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.circuitbreaker; + +/** + * The circuit breaker class with all configurations. + */ +public class CircuitBreaker { + private final long timeout; + private final long retryTimePeriod; + long lastFailureTime; + int failureCount; + private final int failureThreshold; + private State state; + private final long futureTime = 1000 * 1000 * 1000 * 1000; + + /** + * Constructor to create an instance of Circuit Breaker. + * + * @param timeout Timeout for the API request. Not necessary for this simple example + * @param failureThreshold Number of failures we receive from the depended service before changing + * state to 'OPEN' + * @param retryTimePeriod Time period after which a new request is made to remote service for + * status check. + */ + CircuitBreaker(long timeout, int failureThreshold, long retryTimePeriod) { + // We start in a closed state hoping that everything is fine + this.state = State.CLOSED; + this.failureThreshold = failureThreshold; + // Timeout for the API request. + // Used to break the calls made to remote resource if it exceeds the limit + this.timeout = timeout; + this.retryTimePeriod = retryTimePeriod; + //An absurd amount of time in future which basically indicates the last failure never happened + this.lastFailureTime = System.nanoTime() + futureTime; + this.failureCount = 0; + } + + //Reset everything to defaults + private void reset() { + this.failureCount = 0; + this.lastFailureTime = System.nanoTime() + futureTime; + this.state = State.CLOSED; + } + + private void recordFailure() { + failureCount = failureCount + 1; + this.lastFailureTime = System.nanoTime(); + } + + protected void setState() { + if (failureCount > failureThreshold) { //Then something is wrong with remote service + if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) { + //We have waited long enough and should try checking if service is up + state = State.HALF_OPEN; + } else { + //Service would still probably be down + state = State.OPEN; + } + } else { + //Everything is working fine + state = State.CLOSED; + } + } + + public String getState() { + return state.name(); + } + + /** + * Break the circuit beforehand if it is known service is down Or connect the circuit manually if + * service comes online before expected. + * + * @param state State at which circuit is in + */ + public void setStateForBypass(State state) { + this.state = state; + } + + /** + * Executes service call. + * + * @param serviceToCall The name of the service in String. Can be changed to data URLs in case + * of web applications + * @param serverStartTime Time at which actual server was started which makes calls to this + * service + * @return Value from the remote resource, stale response or a custom exception + */ + public String call(String serviceToCall, long serverStartTime) throws Exception { + setState(); + if (state == State.OPEN) { + // return cached response if no the circuit is in OPEN state + return "This is stale response from API"; + } else { + // Make the API request if the circuit is not OPEN + if (serviceToCall.equals("delayedService")) { + var delayedService = new DelayedService(20); + var response = delayedService.response(serverStartTime); + //In a real application, this would be run in a thread and the timeout + //parameter of the circuit breaker would be utilized to know if service + //is working. Here, we simulate that based on server response itself + if (response.split(" ")[3].equals("working")) { + // Yay!! the API responded fine. Let's reset everything. + reset(); + return response; + } else { + // Uh-oh!! the call still failed. Let's update that in our records. + recordFailure(); + throw new Exception("Remote service not responding"); + } + } else { + throw new Exception("Unknown Service Name"); + } + } + } +} \ No newline at end of file diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedService.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedService.java new file mode 100644 index 000000000000..13861923b7a4 --- /dev/null +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedService.java @@ -0,0 +1,66 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.circuitbreaker; + +/** + * This simulates the remote service It responds only after a certain timeout period (default set to + * 20 seconds). + */ +public class DelayedService { + private final int delay; + + /** + * Constructor to create an instance of DelayedService, which is down for first few seconds. + * + * @param delay the delay after which service would behave properly, in seconds + */ + public DelayedService(int delay) { + this.delay = delay; + } + + public DelayedService() { + this.delay = 60; + } + + /** + * Responds based on delay, current time and server start time if the service is down / working. + * + * @param serverStartTime Time at which actual server was started which makes calls to this + * service + * @return The state of the service + */ + public String response(long serverStartTime) { + var currentTime = System.nanoTime(); + //Since currentTime and serverStartTime are both in nanoseconds, we convert it to + //seconds by diving by 10e9 and ensure floating point division by multiplying it + //with 1.0 first. We then check if it is greater or less than specified delay and then + //send the reply + if ((currentTime - serverStartTime) * 1.0 / (1000 * 1000 * 1000) < delay) { + //Can use Thread.sleep() here to block and simulate a hung server + return "Delayed service is down"; + } else { + return "Delayed service is working"; + } + } +} diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java new file mode 100644 index 000000000000..e91367175a66 --- /dev/null +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java @@ -0,0 +1,52 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.circuitbreaker; + +/** + * The service class which makes local and remote calls Uses {@link CircuitBreaker} object to ensure + * remote calls don't use up resources. + */ +public class MonitoringService { + + //Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic + public String localResourceResponse() { + return "Local Service is working"; + } + + /** + * Try to get result from remote server. + * + * @param circuitBreaker The circuitBreaker object with all parameters + * @param serverStartTime Time at which actual server was started which makes calls to this + * service + * @return result from the remote response or exception raised by it. + */ + public String remoteResourceResponse(CircuitBreaker circuitBreaker, long serverStartTime) { + try { + return circuitBreaker.call("delayedService", serverStartTime); + } catch (Exception e) { + return e.getMessage(); + } + } +} \ No newline at end of file diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/State.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/State.java new file mode 100644 index 000000000000..95fdb08d65ca --- /dev/null +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/State.java @@ -0,0 +1,33 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.circuitbreaker; + +/** + * Enumeration for states the circuit breaker could be in. + */ +public enum State { + CLOSED, + OPEN, + HALF_OPEN +} \ No newline at end of file diff --git a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/CircuitBreakerTest.java b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/CircuitBreakerTest.java new file mode 100644 index 000000000000..98b59a6aeb4d --- /dev/null +++ b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/CircuitBreakerTest.java @@ -0,0 +1,80 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.circuitbreaker; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Circuit Breaker test + */ +public class CircuitBreakerTest { + + //long timeout, int failureThreshold, long retryTimePeriod + @Test + public void testSetState() { + var circuitBreaker = new CircuitBreaker(1, 1, 100); + //Right now, failureCountfailureThreshold, and lastFailureTime is nearly equal to current time, + //state should be half-open + assertEquals(circuitBreaker.getState(), "HALF_OPEN"); + //Since failureCount>failureThreshold, and lastFailureTime is much lesser current time, + //state should be open + circuitBreaker.lastFailureTime = System.nanoTime() - 1000 * 1000 * 1000 * 1000; + circuitBreaker.setState(); + assertEquals(circuitBreaker.getState(), "OPEN"); + //Now set it back again to closed to test idempotency + circuitBreaker.failureCount = 0; + circuitBreaker.setState(); + assertEquals(circuitBreaker.getState(), "CLOSED"); + } + + @Test + public void testSetStateForBypass() { + var circuitBreaker = new CircuitBreaker(1, 1, 100); + //Right now, failureCount {static} + } + enum Category { + + CONVERTIBLE {static} + + JEEP {static} + + SEDAN {static} + + valueOf(name : String) : Category {static} + + values() : Category[] {static} + } + class FunctionalProgramming { + - FunctionalProgramming() + + getGroupingOfCarsByCategory(cars : List) : Map> {static} + + getModelsAfter2000(cars : List) : List {static} + + getSedanCarsOwnedSortedByDate(persons : List) : List {static} + } + class ImperativeProgramming { + - ImperativeProgramming() + + getGroupingOfCarsByCategory(cars : List) : Map> {static} + + getModelsAfter2000(cars : List) : List {static} + + getSedanCarsOwnedSortedByDate(persons : List) : List {static} + } + class Person { + - cars : List + + Person(cars : List) + + getCars() : List + } +} +Person --> "-cars" Car +Car --> "-category" Category +@enduml \ No newline at end of file diff --git a/collection-pipeline/pom.xml b/collection-pipeline/pom.xml index 8c4aad2dd392..6d8d467ad151 100644 --- a/collection-pipeline/pom.xml +++ b/collection-pipeline/pom.xml @@ -1,7 +1,7 @@ - - - - - - - - - - - - - - - \ No newline at end of file + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + + combinator + + + junit + junit + test + + + + diff --git a/combinator/src/main/java/com/iluwatar/combinator/CombinatorApp.java b/combinator/src/main/java/com/iluwatar/combinator/CombinatorApp.java new file mode 100644 index 000000000000..578c87bfe760 --- /dev/null +++ b/combinator/src/main/java/com/iluwatar/combinator/CombinatorApp.java @@ -0,0 +1,92 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.combinator; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The functional pattern representing a style of organizing libraries + * centered around the idea of combining functions. + * Putting it simply, there is some type T, some functions + * for constructing "primitive" values of type T, + * and some "combinators" which can combine values of type T + * in various ways to build up more complex values of type T. + * The class {@link Finder} defines a simple function {@link Finder#find(String)} + * and connected functions + * {@link Finder#or(Finder)}, + * {@link Finder#not(Finder)}, + * {@link Finder#and(Finder)} + * Using them the became possible to get more complex functions {@link Finders} + */ +public class CombinatorApp { + + /** + * Logger. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(CombinatorApp.class); + + /** + * main. + * @param args args + */ + public static void main(String[] args) { + var queriesOr = new String[]{"many", "Annabel"}; + var finder = Finders.expandedFinder(queriesOr); + var res = finder.find(text()); + LOGGER.info("the result of expanded(or) query[{}] is {}", queriesOr, res); + + var queriesAnd = new String[]{"Annabel", "my"}; + finder = Finders.specializedFinder(queriesAnd); + res = finder.find(text()); + LOGGER.info("the result of specialized(and) query[{}] is {}", queriesAnd, res); + + finder = Finders.advancedFinder("it was","kingdom","sea"); + res = finder.find(text()); + LOGGER.info("the result of advanced query is {}", res); + + res = Finders.filteredFinder(" was ", "many", "child").find(text()); + LOGGER.info("the result of filtered query is {}", res); + + + } + + private static String text() { + return + "It was many and many a year ago,\n" + + "In a kingdom by the sea,\n" + + "That a maiden there lived whom you may know\n" + + "By the name of ANNABEL LEE;\n" + + "And this maiden she lived with no other thought\n" + + "Than to love and be loved by me.\n" + + "I was a child and she was a child,\n" + + "In this kingdom by the sea;\n" + + "But we loved with a love that was more than love-\n" + + "I and my Annabel Lee;\n" + + "With a love that the winged seraphs of heaven\n" + + "Coveted her and me."; + } + +} diff --git a/combinator/src/main/java/com/iluwatar/combinator/Finder.java b/combinator/src/main/java/com/iluwatar/combinator/Finder.java new file mode 100644 index 000000000000..37cecbd0c155 --- /dev/null +++ b/combinator/src/main/java/com/iluwatar/combinator/Finder.java @@ -0,0 +1,93 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.combinator; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Functional interface to find lines in text. + */ +public interface Finder { + + /** + * The function to find lines in text. + * @param text full tet + * @return result of searching + */ + List find(String text); + + /** + * Simple implementation of function {@link #find(String)}. + * @param word for searching + * @return this + */ + static Finder contains(String word) { + return txt -> Stream.of(txt.split("\n")) + .filter(line -> line.toLowerCase().contains(word.toLowerCase())) + .collect(Collectors.toList()); + } + + /** + * combinator not. + * @param notFinder finder to combine + * @return new finder including previous finders + */ + default Finder not(Finder notFinder) { + return txt -> { + List res = this.find(txt); + res.removeAll(notFinder.find(txt)); + return res; + }; + } + + /** + * combinator or. + * @param orFinder finder to combine + * @return new finder including previous finders + */ + default Finder or(Finder orFinder) { + return txt -> { + List res = this.find(txt); + res.addAll(orFinder.find(txt)); + return res; + }; + } + + /** + * combinator or. + * @param andFinder finder to combine + * @return new finder including previous finders + */ + default Finder and(Finder andFinder) { + return + txt -> this + .find(txt) + .stream() + .flatMap(line -> andFinder.find(line).stream()) + .collect(Collectors.toList()); + } + +} diff --git a/combinator/src/main/java/com/iluwatar/combinator/Finders.java b/combinator/src/main/java/com/iluwatar/combinator/Finders.java new file mode 100644 index 000000000000..f91c07f4c4ad --- /dev/null +++ b/combinator/src/main/java/com/iluwatar/combinator/Finders.java @@ -0,0 +1,103 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.combinator; + +import java.util.ArrayList; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Complex finders consisting of simple finder. + */ +public class Finders { + private Finders() { + } + + + /** + * Finder to find a complex query. + * @param query to find + * @param orQuery alternative to find + * @param notQuery exclude from search + * @return new finder + */ + public static Finder advancedFinder(String query, String orQuery, String notQuery) { + return + Finder.contains(query) + .or(Finder.contains(orQuery)) + .not(Finder.contains(notQuery)); + } + + /** + * Filtered finder looking a query with excluded queries as well. + * @param query to find + * @param excludeQueries to exclude + * @return new finder + */ + public static Finder filteredFinder(String query, String... excludeQueries) { + var finder = Finder.contains(query); + + for (String q : excludeQueries) { + finder = finder.not(Finder.contains(q)); + } + return finder; + + } + + /** + * Specialized query. Every next query is looked in previous result. + * @param queries array with queries + * @return new finder + */ + public static Finder specializedFinder(String... queries) { + var finder = identMult(); + + for (String query : queries) { + finder = finder.and(Finder.contains(query)); + } + return finder; + } + + /** + * Expanded query. Looking for alternatives. + * @param queries array with queries. + * @return new finder + */ + public static Finder expandedFinder(String... queries) { + var finder = identSum(); + + for (String query : queries) { + finder = finder.or(Finder.contains(query)); + } + return finder; + } + + private static Finder identMult() { + return txt -> Stream.of(txt.split("\n")).collect(Collectors.toList()); + } + + private static Finder identSum() { + return txt -> new ArrayList<>(); + } +} diff --git a/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java b/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java new file mode 100644 index 000000000000..f42b46c1407f --- /dev/null +++ b/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java @@ -0,0 +1,36 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.combinator; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class CombinatorAppTest { + + @Test + public void main() { + CombinatorApp.main(new String[]{}); + } +} \ No newline at end of file diff --git a/combinator/src/test/java/com/iluwatar/combinator/FinderTest.java b/combinator/src/test/java/com/iluwatar/combinator/FinderTest.java new file mode 100644 index 000000000000..67314903da50 --- /dev/null +++ b/combinator/src/test/java/com/iluwatar/combinator/FinderTest.java @@ -0,0 +1,44 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.combinator; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.*; + +public class FinderTest { + + @Test + public void contains() { + var example = "the first one \nthe second one \n"; + + var result = Finder.contains("second").find(example); + Assert.assertEquals(result.size(),1); + Assert.assertEquals(result.get(0),"the second one "); + } + +} \ No newline at end of file diff --git a/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java b/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java new file mode 100644 index 000000000000..5753ec92a222 --- /dev/null +++ b/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java @@ -0,0 +1,83 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.combinator; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +import static com.iluwatar.combinator.Finders.*; +import static org.junit.Assert.*; + +public class FindersTest { + + @Test + public void advancedFinderTest() { + var res = advancedFinder("it was","kingdom","sea").find(text()); + Assert.assertEquals(res.size(),1); + Assert.assertEquals(res.get(0),"It was many and many a year ago,"); + } + + @Test + public void filteredFinderTest() { + var res = filteredFinder(" was ", "many", "child").find(text()); + Assert.assertEquals(res.size(),1); + Assert.assertEquals(res.get(0),"But we loved with a love that was more than love-"); + } + + @Test + public void specializedFinderTest() { + var res = specializedFinder("love","heaven").find(text()); + Assert.assertEquals(res.size(),1); + Assert.assertEquals(res.get(0),"With a love that the winged seraphs of heaven"); + } + + @Test + public void expandedFinderTest() { + var res = expandedFinder("It was","kingdom").find(text()); + Assert.assertEquals(res.size(),3); + Assert.assertEquals(res.get(0),"It was many and many a year ago,"); + Assert.assertEquals(res.get(1),"In a kingdom by the sea,"); + Assert.assertEquals(res.get(2),"In this kingdom by the sea;"); + } + + + private String text(){ + return + "It was many and many a year ago,\n" + + "In a kingdom by the sea,\n" + + "That a maiden there lived whom you may know\n" + + "By the name of ANNABEL LEE;\n" + + "And this maiden she lived with no other thought\n" + + "Than to love and be loved by me.\n" + + "I was a child and she was a child,\n" + + "In this kingdom by the sea;\n" + + "But we loved with a love that was more than love-\n" + + "I and my Annabel Lee;\n" + + "With a love that the winged seraphs of heaven\n" + + "Coveted her and me."; + } + +} \ No newline at end of file diff --git a/command/README.md b/command/README.md index cb9bd48a9608..bab95b90ce1d 100644 --- a/command/README.md +++ b/command/README.md @@ -5,10 +5,7 @@ folder: command permalink: /patterns/command/ categories: Behavioral tags: - - Java - - Gang Of Four - - Difficulty-Intermediate - - Functional + - Gang of Four --- ## Also known as @@ -19,6 +16,7 @@ Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. +## Class diagram ![alt text](./etc/command.png "Command") ## Applicability @@ -36,10 +34,6 @@ Use the Command pattern when you want to * implement callback functionality * implement the undo functionality -## Presentations - -* [Command Pattern](etc/presentation.html) - ## Real world examples * [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) diff --git a/command/etc/command.urm.puml b/command/etc/command.urm.puml new file mode 100644 index 000000000000..a8b773418ee3 --- /dev/null +++ b/command/etc/command.urm.puml @@ -0,0 +1,83 @@ +@startuml +package com.iluwatar.command { + class App { + + App() + + main(args : String[]) {static} + } + abstract class Command { + + Command() + + execute(Target) {abstract} + + redo() {abstract} + + toString() : String {abstract} + + undo() {abstract} + } + class Goblin { + + Goblin() + + toString() : String + } + class InvisibilitySpell { + - target : Target + + InvisibilitySpell() + + execute(target : Target) + + redo() + + toString() : String + + undo() + } + class ShrinkSpell { + - oldSize : Size + - target : Target + + ShrinkSpell() + + execute(target : Target) + + redo() + + toString() : String + + undo() + } + enum Size { + + NORMAL {static} + + SMALL {static} + - title : String + + toString() : String + + valueOf(name : String) : Size {static} + + values() : Size[] {static} + } + abstract class Target { + - LOGGER : Logger {static} + - size : Size + - visibility : Visibility + + Target() + + getSize() : Size + + getVisibility() : Visibility + + printStatus() + + setSize(size : Size) + + setVisibility(visibility : Visibility) + + toString() : String {abstract} + } + enum Visibility { + + INVISIBLE {static} + + VISIBLE {static} + - title : String + + toString() : String + + valueOf(name : String) : Visibility {static} + + values() : Visibility[] {static} + } + class Wizard { + - LOGGER : Logger {static} + - redoStack : Deque + - undoStack : Deque + + Wizard() + + castSpell(command : Command, target : Target) + + redoLastSpell() + + toString() : String + + undoLastSpell() + } +} +Target --> "-size" Size +Wizard --> "-undoStack" Command +ShrinkSpell --> "-oldSize" Size +InvisibilitySpell --> "-target" Target +ShrinkSpell --> "-target" Target +Target --> "-visibility" Visibility +Goblin --|> Target +InvisibilitySpell --|> Command +ShrinkSpell --|> Command +@enduml \ No newline at end of file diff --git a/command/etc/diagram.png b/command/etc/diagram.png deleted file mode 100644 index 1d3494292b82..000000000000 Binary files a/command/etc/diagram.png and /dev/null differ diff --git a/command/etc/presentation.html b/command/etc/presentation.html deleted file mode 100644 index 6a2ce7471cca..000000000000 --- a/command/etc/presentation.html +++ /dev/null @@ -1,243 +0,0 @@ - - - - - Design Patterns - Command Presentation - - - - - - - - - \ No newline at end of file diff --git a/command/pom.xml b/command/pom.xml index 6807764561ef..50a14c45f00d 100644 --- a/command/pom.xml +++ b/command/pom.xml @@ -2,7 +2,7 @@ "-messageSent" MessageSent +MessageSent ..+ Order +MessageToSend ..+ MessagingService +Retry --> "-op" Operation +Operation ..+ Retry +Service --> "-database" Database +Node --> "-next" Node +PaymentRequest --+ PaymentService +Commander --> "-messagingService" MessagingService +ShippingRequest ..+ ShippingService +Commander --> "-shippingService" ShippingService +Commander --> "-paymentService" PaymentService +MessageRequest --+ MessagingService +Commander --> "-employeeDb" EmployeeHandle +HandleErrorIssue ..+ Retry +Retry --> "-handleError" HandleErrorIssue +QueueTask --> "-taskType" TaskType +TaskType ..+ QueueTask +Order --> "-user" User +MessageRequest --> "-msg" MessageToSend +QueueTask --> "-order" Order +Commander --> "-queue" QueueDatabase +QueueDatabase --> "-data" Queue +Queue --> "-front" Node +Node ..+ Queue +Order --> "-paid" PaymentStatus +PaymentStatus ..+ Order +EmployeeDatabase --|> Database +EmployeeHandle --|> Service +MessagingDatabase --|> Database +MessagingService --|> Service +PaymentDatabase --|> Database +PaymentService --|> Service +QueueDatabase --|> Database +ShippingDatabase --|> Database +ShippingService --|> Service +@enduml \ No newline at end of file diff --git a/commander/pom.xml b/commander/pom.xml new file mode 100644 index 000000000000..7ab29e421c60 --- /dev/null +++ b/commander/pom.xml @@ -0,0 +1,105 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + commander + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + Employee + + + + com.iluwatar.commander.AppEmployeeDbFailCases + + + ${project.artifactId}-EmployeeDBFailCase + + + + Message + + + + com.iluwatar.commander.AppMessagingFailCases + + + ${project.artifactId}-MessagingFailCase + + + + Payment + + + + com.iluwatar.commander.AppPaymentFailCases + + + ${project.artifactId}-PaymentFailCase + + + + Queue + + + + com.iluwatar.commander.AppQueueFailCases + + + ${project.artifactId}-QueueFailCase + + + + Shipping + + + + com.iluwatar.commander.AppShippingFailCases + + + ${project.artifactId}-ShippingFailCase + + + + + + + diff --git a/commander/src/main/java/com/iluwatar/commander/AppEmployeeDbFailCases.java b/commander/src/main/java/com/iluwatar/commander/AppEmployeeDbFailCases.java new file mode 100644 index 000000000000..7c48dee4ccec --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/AppEmployeeDbFailCases.java @@ -0,0 +1,98 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander; + +import com.iluwatar.commander.employeehandle.EmployeeDatabase; +import com.iluwatar.commander.employeehandle.EmployeeHandle; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import com.iluwatar.commander.exceptions.ItemUnavailableException; +import com.iluwatar.commander.messagingservice.MessagingDatabase; +import com.iluwatar.commander.messagingservice.MessagingService; +import com.iluwatar.commander.paymentservice.PaymentDatabase; +import com.iluwatar.commander.paymentservice.PaymentService; +import com.iluwatar.commander.queue.QueueDatabase; +import com.iluwatar.commander.shippingservice.ShippingDatabase; +import com.iluwatar.commander.shippingservice.ShippingService; + +/** + * AppEmployeeDbFailCases class looks at possible cases when Employee handle service is + * available/unavailable. + */ +public class AppEmployeeDbFailCases { + private final int numOfRetries = 3; + private final long retryDuration = 30000; + private final long queueTime = 240000; //4 mins + private final long queueTaskTime = 60000; //1 min + private final long paymentTime = 120000; //2 mins + private final long messageTime = 150000; //2.5 mins + private final long employeeTime = 240000; //4 mins + + void employeeDatabaseUnavailableCase() throws Exception { + var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = new EmployeeHandle(new EmployeeDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = + new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException()); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void employeeDbSuccessCase() throws Exception { + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = new EmployeeHandle(new EmployeeDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + /** + * Program entry point. + * + * @param args command line args + */ + + public static void main(String[] args) throws Exception { + var aefc = new AppEmployeeDbFailCases(); + //aefc.employeeDatabaseUnavailableCase(); + aefc.employeeDbSuccessCase(); + } +} diff --git a/commander/src/main/java/com/iluwatar/commander/AppMessagingFailCases.java b/commander/src/main/java/com/iluwatar/commander/AppMessagingFailCases.java new file mode 100644 index 000000000000..cbb6e6c7841b --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/AppMessagingFailCases.java @@ -0,0 +1,148 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander; + +import com.iluwatar.commander.employeehandle.EmployeeDatabase; +import com.iluwatar.commander.employeehandle.EmployeeHandle; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import com.iluwatar.commander.messagingservice.MessagingDatabase; +import com.iluwatar.commander.messagingservice.MessagingService; +import com.iluwatar.commander.paymentservice.PaymentDatabase; +import com.iluwatar.commander.paymentservice.PaymentService; +import com.iluwatar.commander.queue.QueueDatabase; +import com.iluwatar.commander.shippingservice.ShippingDatabase; +import com.iluwatar.commander.shippingservice.ShippingService; + +/** + * AppMessagingFailCases class looks at possible cases when Messaging service is + * available/unavailable. + */ + +public class AppMessagingFailCases { + private final int numOfRetries = 3; + private final long retryDuration = 30000; + private final long queueTime = 240000; //4 mins + private final long queueTaskTime = 60000; //1 min + private final long paymentTime = 120000; //2 mins + private final long messageTime = 150000; //2.5 mins + private final long employeeTime = 240000; //4 mins + + void messagingDatabaseUnavailableCasePaymentSuccess() throws Exception { + //rest is successful + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void messagingDatabaseUnavailableCasePaymentError() throws Exception { + //rest is successful + var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void messagingDatabaseUnavailableCasePaymentFailure() throws Exception { + //rest is successful + var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = + new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException()); + var c = + new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, queueTime, queueTaskTime, + paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void messagingSuccessCase() throws Exception { + //done here + var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + /** + * Program entry point. + * + * @param args command line args + */ + + public static void main(String[] args) throws Exception { + var amfc = new AppMessagingFailCases(); + //amfc.messagingDatabaseUnavailableCasePaymentSuccess(); + //amfc.messagingDatabaseUnavailableCasePaymentError(); + //amfc.messagingDatabaseUnavailableCasePaymentFailure(); + amfc.messagingSuccessCase(); + } +} diff --git a/commander/src/main/java/com/iluwatar/commander/AppPaymentFailCases.java b/commander/src/main/java/com/iluwatar/commander/AppPaymentFailCases.java new file mode 100644 index 000000000000..8dbe7327bb04 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/AppPaymentFailCases.java @@ -0,0 +1,110 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander; + +import com.iluwatar.commander.employeehandle.EmployeeDatabase; +import com.iluwatar.commander.employeehandle.EmployeeHandle; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import com.iluwatar.commander.exceptions.PaymentDetailsErrorException; +import com.iluwatar.commander.messagingservice.MessagingDatabase; +import com.iluwatar.commander.messagingservice.MessagingService; +import com.iluwatar.commander.paymentservice.PaymentDatabase; +import com.iluwatar.commander.paymentservice.PaymentService; +import com.iluwatar.commander.queue.QueueDatabase; +import com.iluwatar.commander.shippingservice.ShippingDatabase; +import com.iluwatar.commander.shippingservice.ShippingService; + +/** + * AppPaymentFailCases class looks at possible cases when Payment service is available/unavailable. + */ + +public class AppPaymentFailCases { + private final int numOfRetries = 3; + private final long retryDuration = 30000; + private final long queueTime = 240000; //4 mins + private final long queueTaskTime = 60000; //1 min + private final long paymentTime = 120000; //2 mins + private final long messageTime = 150000; //2.5 mins + private final long employeeTime = 240000; //4 mins + + void paymentNotPossibleCase() throws Exception { + var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), + new PaymentDetailsErrorException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(new DatabaseUnavailableException()); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void paymentDatabaseUnavailableCase() throws Exception { + //rest is successful + var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void paymentSuccessCase() throws Exception { + //goes to message after 2 retries maybe - rest is successful for now + var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = + new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(new DatabaseUnavailableException()); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + /** + * Program entry point. + * + * @param args command line args + */ + + public static void main(String[] args) throws Exception { + var apfc = new AppPaymentFailCases(); + //apfc.paymentNotPossibleCase(); + //apfc.paymentDatabaseUnavailableCase(); + apfc.paymentSuccessCase(); + } +} diff --git a/commander/src/main/java/com/iluwatar/commander/AppQueueFailCases.java b/commander/src/main/java/com/iluwatar/commander/AppQueueFailCases.java new file mode 100644 index 000000000000..b108701a250e --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/AppQueueFailCases.java @@ -0,0 +1,146 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander; + +import com.iluwatar.commander.employeehandle.EmployeeDatabase; +import com.iluwatar.commander.employeehandle.EmployeeHandle; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import com.iluwatar.commander.exceptions.ItemUnavailableException; +import com.iluwatar.commander.messagingservice.MessagingDatabase; +import com.iluwatar.commander.messagingservice.MessagingService; +import com.iluwatar.commander.paymentservice.PaymentDatabase; +import com.iluwatar.commander.paymentservice.PaymentService; +import com.iluwatar.commander.queue.QueueDatabase; +import com.iluwatar.commander.shippingservice.ShippingDatabase; +import com.iluwatar.commander.shippingservice.ShippingService; + +/** + * AppQueueFailCases class looks at possible cases when Queue Database is available/unavailable. + */ + +public class AppQueueFailCases { + private final int numOfRetries = 3; + private final long retryDuration = 30000; + private final long queueTime = 240000; //4 mins + private final long queueTaskTime = 60000; //1 min + private final long paymentTime = 120000; //2 mins + private final long messageTime = 150000; //2.5 mins + private final long employeeTime = 240000; //4 mins + + void queuePaymentTaskDatabaseUnavailableCase() throws Exception { + var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = + new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException()); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void queueMessageTaskDatabaseUnavailableCase() throws Exception { + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = + new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException()); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void queueEmployeeDbTaskDatabaseUnavailableCase() throws Exception { + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = new EmployeeHandle(new EmployeeDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = + new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException()); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void queueSuccessCase() throws Exception { + var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase()); + var ms = + new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = + new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException()); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + /** + * Program entry point. + * + * @param args command line args + */ + + public static void main(String[] args) throws Exception { + var aqfc = new AppQueueFailCases(); + //aqfc.queuePaymentTaskDatabaseUnavailableCase(); + //aqfc.queueMessageTaskDatabaseUnavailableCase(); + //aqfc.queueEmployeeDbTaskDatabaseUnavailableCase(); + aqfc.queueSuccessCase(); + } +} diff --git a/commander/src/main/java/com/iluwatar/commander/AppShippingFailCases.java b/commander/src/main/java/com/iluwatar/commander/AppShippingFailCases.java new file mode 100644 index 000000000000..36bb06042c4e --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/AppShippingFailCases.java @@ -0,0 +1,124 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander; + +import com.iluwatar.commander.employeehandle.EmployeeDatabase; +import com.iluwatar.commander.employeehandle.EmployeeHandle; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import com.iluwatar.commander.exceptions.ItemUnavailableException; +import com.iluwatar.commander.exceptions.ShippingNotPossibleException; +import com.iluwatar.commander.messagingservice.MessagingDatabase; +import com.iluwatar.commander.messagingservice.MessagingService; +import com.iluwatar.commander.paymentservice.PaymentDatabase; +import com.iluwatar.commander.paymentservice.PaymentService; +import com.iluwatar.commander.queue.QueueDatabase; +import com.iluwatar.commander.shippingservice.ShippingDatabase; +import com.iluwatar.commander.shippingservice.ShippingService; + +/** + * AppShippingFailCases class looks at possible cases when Shipping service is + * available/unavailable. + */ + +public class AppShippingFailCases { + private final int numOfRetries = 3; + private final long retryDuration = 30000; + private final long queueTime = 240000; //4 mins + private final long queueTaskTime = 60000; //1 min + private final long paymentTime = 120000; //2 mins + private final long messageTime = 150000; //2.5 mins + private final long employeeTime = 240000; //4 mins + + void itemUnavailableCase() throws Exception { + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void shippingNotPossibleCase() throws Exception { + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase(), new ShippingNotPossibleException()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void shippingDatabaseUnavailableCase() throws Exception { + //rest is successful + var ps = new PaymentService(new PaymentDatabase()); + var ss = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ms = new MessagingService(new MessagingDatabase()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + void shippingSuccessCase() throws Exception { + //goes to payment after 2 retries maybe - rest is successful for now + var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException()); + var ss = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); + var eh = new EmployeeHandle(new EmployeeDatabase()); + var qdb = new QueueDatabase(); + var c = new Commander(eh, ps, ss, ms, qdb, numOfRetries, retryDuration, + queueTime, queueTaskTime, paymentTime, messageTime, employeeTime); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + c.placeOrder(order); + } + + /** + * Program entry point. + * + * @param args command line args + */ + + public static void main(String[] args) throws Exception { + var asfc = new AppShippingFailCases(); + //asfc.itemUnavailableCase(); + //asfc.shippingNotPossibleCase(); + //asfc.shippingDatabaseUnavailableCase(); + asfc.shippingSuccessCase(); + } +} diff --git a/commander/src/main/java/com/iluwatar/commander/Commander.java b/commander/src/main/java/com/iluwatar/commander/Commander.java new file mode 100644 index 000000000000..41779c076325 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/Commander.java @@ -0,0 +1,571 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander; + +import com.iluwatar.commander.Order.MessageSent; +import com.iluwatar.commander.Order.PaymentStatus; +import com.iluwatar.commander.employeehandle.EmployeeHandle; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import com.iluwatar.commander.exceptions.ItemUnavailableException; +import com.iluwatar.commander.exceptions.PaymentDetailsErrorException; +import com.iluwatar.commander.exceptions.ShippingNotPossibleException; +import com.iluwatar.commander.messagingservice.MessagingService; +import com.iluwatar.commander.paymentservice.PaymentService; +import com.iluwatar.commander.queue.QueueDatabase; +import com.iluwatar.commander.queue.QueueTask; +import com.iluwatar.commander.queue.QueueTask.TaskType; +import com.iluwatar.commander.shippingservice.ShippingService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

Commander pattern is used to handle all issues that can come up while making a + * distributed transaction. The idea is to have a commander, which coordinates the execution of all + * instructions and ensures proper completion using retries and taking care of idempotence. By + * queueing instructions while they haven't been done, we can ensure a state of 'eventual + * consistency'.

+ *

In our example, we have an e-commerce application. When the user places an order, + * the shipping service is intimated first. If the service does not respond for some reason, the + * order is not placed. If response is received, the commander then calls for the payment service to + * be intimated. If this fails, the shipping still takes place (order converted to COD) and the item + * is queued. If the queue is also found to be unavailable, the payment is noted to be not done and + * this is added to an employee database. Three types of messages are sent to the user - one, if + * payment succeeds; two, if payment fails definitively; and three, if payment fails in the first + * attempt. If the message is not sent, this is also queued and is added to employee db. We also + * have a time limit for each instruction to be completed, after which, the instruction is not + * executed, thereby ensuring that resources are not held for too long. In the rare occasion in + * which everything fails, an individual would have to step in to figure out how to solve the + * issue.

+ *

We have abstract classes {@link Database} and {@link Service} which are extended + * by all the databases and services. Each service has a database to be updated, and receives + * request from an outside user (the {@link Commander} class here). There are 5 microservices - + * {@link ShippingService}, {@link PaymentService}, {@link MessagingService}, {@link EmployeeHandle} + * and a {@link QueueDatabase}. We use retries to execute any instruction using {@link Retry} class, + * and idempotence is ensured by going through some checks before making requests to services and + * making change in {@link Order} class fields if request succeeds or definitively fails. There are + * 5 classes - {@link AppShippingFailCases}, {@link AppPaymentFailCases}, {@link + * AppMessagingFailCases}, {@link AppQueueFailCases} and {@link AppEmployeeDbFailCases}, which look + * at the different scenarios that may be encountered during the placing of an order.

+ */ + +public class Commander { + + private final QueueDatabase queue; + private final EmployeeHandle employeeDb; + private final PaymentService paymentService; + private final ShippingService shippingService; + private final MessagingService messagingService; + private int queueItems = 0; //keeping track here only so don't need access to queue db to get this + private final int numOfRetries; + private final long retryDuration; + private final long queueTime; + private final long queueTaskTime; + private final long paymentTime; + private final long messageTime; + private final long employeeTime; + private boolean finalSiteMsgShown; + private static final Logger LOG = LoggerFactory.getLogger(Commander.class); + //we could also have another db where it stores all orders + + Commander(EmployeeHandle empDb, PaymentService paymentService, ShippingService shippingService, + MessagingService messagingService, QueueDatabase qdb, int numOfRetries, + long retryDuration, long queueTime, long queueTaskTime, long paymentTime, + long messageTime, long employeeTime) { + this.paymentService = paymentService; + this.shippingService = shippingService; + this.messagingService = messagingService; + this.employeeDb = empDb; + this.queue = qdb; + this.numOfRetries = numOfRetries; + this.retryDuration = retryDuration; + this.queueTime = queueTime; + this.queueTaskTime = queueTaskTime; + this.paymentTime = paymentTime; + this.messageTime = messageTime; + this.employeeTime = employeeTime; + this.finalSiteMsgShown = false; + } + + void placeOrder(Order order) throws Exception { + sendShippingRequest(order); + } + + private void sendShippingRequest(Order order) throws Exception { + var list = shippingService.exceptionsList; + Retry.Operation op = (l) -> { + if (!l.isEmpty()) { + if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { + LOG.debug("Order " + order.id + ": Error in connecting to shipping service, " + + "trying again.."); + } else { + LOG.debug("Order " + order.id + ": Error in creating shipping request.."); + } + throw l.remove(0); + } + String transactionId = shippingService.receiveRequest(order.item, order.user.address); + //could save this transaction id in a db too + LOG.info("Order " + order.id + ": Shipping placed successfully, transaction id: " + + transactionId); + LOG.info("Order has been placed and will be shipped to you. Please wait while we make your" + + " payment... "); + sendPaymentRequest(order); + }; + Retry.HandleErrorIssue handleError = (o, err) -> { + if (ShippingNotPossibleException.class.isAssignableFrom(err.getClass())) { + LOG.info("Shipping is currently not possible to your address. We are working on the problem" + + " and will get back to you asap."); + finalSiteMsgShown = true; + LOG.info("Order " + order.id + ": Shipping not possible to address, trying to add problem " + + "to employee db.."); + employeeHandleIssue(o); + } else if (ItemUnavailableException.class.isAssignableFrom(err.getClass())) { + LOG.info("This item is currently unavailable. We will inform you as soon as the item " + + "becomes available again."); + finalSiteMsgShown = true; + LOG.info("Order " + order.id + ": Item " + order.item + " unavailable, trying to add " + + "problem to employee handle.."); + employeeHandleIssue(o); + } else { + LOG.info("Sorry, there was a problem in creating your order. Please try later."); + LOG.error("Order " + order.id + ": Shipping service unavailable, order not placed.."); + finalSiteMsgShown = true; + } + }; + var r = new Retry<>(op, handleError, numOfRetries, retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + r.perform(list, order); + } + + private void sendPaymentRequest(Order order) { + if (System.currentTimeMillis() - order.createdTime >= this.paymentTime) { + if (order.paid.equals(PaymentStatus.Trying)) { + order.paid = PaymentStatus.NotDone; + sendPaymentFailureMessage(order); + LOG.error("Order " + order.id + ": Payment time for order over, failed and returning.."); + } //if succeeded or failed, would have been dequeued, no attempt to make payment + return; + } + var list = paymentService.exceptionsList; + var t = new Thread(() -> { + Retry.Operation op = (l) -> { + if (!l.isEmpty()) { + if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { + LOG.debug("Order " + order.id + ": Error in connecting to payment service," + + " trying again.."); + } else { + LOG.debug("Order " + order.id + ": Error in creating payment request.."); + } + throw l.remove(0); + } + if (order.paid.equals(PaymentStatus.Trying)) { + var transactionId = paymentService.receiveRequest(order.price); + order.paid = PaymentStatus.Done; + LOG.info("Order " + order.id + ": Payment successful, transaction Id: " + transactionId); + if (!finalSiteMsgShown) { + LOG.info("Payment made successfully, thank you for shopping with us!!"); + finalSiteMsgShown = true; + } + sendSuccessMessage(order); + } + }; + Retry.HandleErrorIssue handleError = (o, err) -> { + if (PaymentDetailsErrorException.class.isAssignableFrom(err.getClass())) { + if (!finalSiteMsgShown) { + LOG.info("There was an error in payment. Your account/card details " + + "may have been incorrect. " + + "Meanwhile, your order has been converted to COD and will be shipped."); + finalSiteMsgShown = true; + } + LOG.error("Order " + order.id + ": Payment details incorrect, failed.."); + o.paid = PaymentStatus.NotDone; + sendPaymentFailureMessage(o); + } else { + if (o.messageSent.equals(MessageSent.NoneSent)) { + if (!finalSiteMsgShown) { + LOG.info("There was an error in payment. We are on it, and will get back to you " + + "asap. Don't worry, your order has been placed and will be shipped."); + finalSiteMsgShown = true; + } + LOG.warn("Order " + order.id + ": Payment error, going to queue.."); + sendPaymentPossibleErrorMsg(o); + } + if (o.paid.equals(PaymentStatus.Trying) && System + .currentTimeMillis() - o.createdTime < paymentTime) { + var qt = new QueueTask(o, TaskType.Payment, -1); + updateQueue(qt); + } + } + }; + var r = new Retry<>(op, handleError, numOfRetries, retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + e1.printStackTrace(); + } + }); + t.start(); + } + + private void updateQueue(QueueTask qt) { + if (System.currentTimeMillis() - qt.order.createdTime >= this.queueTime) { + // since payment time is lesser than queuetime it would have already failed.. + // additional check not needed + LOG.trace("Order " + qt.order.id + ": Queue time for order over, failed.."); + return; + } else if (qt.taskType.equals(TaskType.Payment) && !qt.order.paid.equals(PaymentStatus.Trying) + || qt.taskType.equals(TaskType.Messaging) && (qt.messageType == 1 + && !qt.order.messageSent.equals(MessageSent.NoneSent) + || qt.order.messageSent.equals(MessageSent.PaymentFail) + || qt.order.messageSent.equals(MessageSent.PaymentSuccessful)) + || qt.taskType.equals(TaskType.EmployeeDb) && qt.order.addedToEmployeeHandle) { + LOG.trace("Order " + qt.order.id + ": Not queueing task since task already done.."); + return; + } + var list = queue.exceptionsList; + Thread t = new Thread(() -> { + Retry.Operation op = (list1) -> { + if (!list1.isEmpty()) { + LOG.warn("Order " + qt.order.id + ": Error in connecting to queue db, trying again.."); + throw list1.remove(0); + } + queue.add(qt); + queueItems++; + LOG.info("Order " + qt.order.id + ": " + qt.getType() + " task enqueued.."); + tryDoingTasksInQueue(); + }; + Retry.HandleErrorIssue handleError = (qt1, err) -> { + if (qt1.taskType.equals(TaskType.Payment)) { + qt1.order.paid = PaymentStatus.NotDone; + sendPaymentFailureMessage(qt1.order); + LOG.error("Order " + qt1.order.id + ": Unable to enqueue payment task," + + " payment failed.."); + } + LOG.error("Order " + qt1.order.id + ": Unable to enqueue task of type " + qt1.getType() + + ", trying to add to employee handle.."); + employeeHandleIssue(qt1.order); + }; + var r = new Retry<>(op, handleError, numOfRetries, retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, qt); + } catch (Exception e1) { + e1.printStackTrace(); + } + }); + t.start(); + } + + private void tryDoingTasksInQueue() { //commander controls operations done to queue + var list = queue.exceptionsList; + var t2 = new Thread(() -> { + Retry.Operation op = (list1) -> { + if (!list1.isEmpty()) { + LOG.warn("Error in accessing queue db to do tasks, trying again.."); + throw list1.remove(0); + } + doTasksInQueue(); + }; + Retry.HandleErrorIssue handleError = (o, err) -> { + }; + var r = new Retry<>(op, handleError, numOfRetries, retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, null); + } catch (Exception e1) { + e1.printStackTrace(); + } + }); + t2.start(); + } + + private void tryDequeue() { + var list = queue.exceptionsList; + var t3 = new Thread(() -> { + Retry.Operation op = (list1) -> { + if (!list1.isEmpty()) { + LOG.warn("Error in accessing queue db to dequeue task, trying again.."); + throw list1.remove(0); + } + queue.dequeue(); + queueItems--; + }; + Retry.HandleErrorIssue handleError = (o, err) -> { + }; + var r = new Retry(op, handleError, numOfRetries, retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, null); + } catch (Exception e1) { + e1.printStackTrace(); + } + }); + t3.start(); + } + + private void sendSuccessMessage(Order order) { + if (System.currentTimeMillis() - order.createdTime >= this.messageTime) { + LOG.trace("Order " + order.id + ": Message time for order over, returning.."); + return; + } + var list = messagingService.exceptionsList; + Thread t = new Thread(() -> { + Retry.Operation op = (l) -> { + if (!l.isEmpty()) { + if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { + LOG.debug("Order " + order.id + ": Error in connecting to messaging service " + + "(Payment Success msg), trying again.."); + } else { + LOG.debug("Order " + order.id + ": Error in creating Payment Success" + + " messaging request.."); + } + throw l.remove(0); + } + if (!order.messageSent.equals(MessageSent.PaymentFail) + && !order.messageSent.equals(MessageSent.PaymentSuccessful)) { + var requestId = messagingService.receiveRequest(2); + order.messageSent = MessageSent.PaymentSuccessful; + LOG.info("Order " + order.id + ": Payment Success message sent," + + " request Id: " + requestId); + } + }; + Retry.HandleErrorIssue handleError = (o, err) -> { + if ((o.messageSent.equals(MessageSent.NoneSent) || o.messageSent + .equals(MessageSent.PaymentTrying)) + && System.currentTimeMillis() - o.createdTime < messageTime) { + var qt = new QueueTask(order, TaskType.Messaging, 2); + updateQueue(qt); + LOG.info("Order " + order.id + ": Error in sending Payment Success message, trying to" + + " queue task and add to employee handle.."); + employeeHandleIssue(order); + } + }; + var r = new Retry<>(op, handleError, numOfRetries, retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + e1.printStackTrace(); + } + }); + t.start(); + } + + private void sendPaymentFailureMessage(Order order) { + if (System.currentTimeMillis() - order.createdTime >= this.messageTime) { + LOG.trace("Order " + order.id + ": Message time for order over, returning.."); + return; + } + var list = messagingService.exceptionsList; + var t = new Thread(() -> { + Retry.Operation op = (l) -> { + if (!l.isEmpty()) { + if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { + LOG.debug("Order " + order.id + ": Error in connecting to messaging service " + + "(Payment Failure msg), trying again.."); + } else { + LOG.debug("Order " + order.id + ": Error in creating Payment Failure" + + " message request.."); + } + throw l.remove(0); + } + if (!order.messageSent.equals(MessageSent.PaymentFail) + && !order.messageSent.equals(MessageSent.PaymentSuccessful)) { + var requestId = messagingService.receiveRequest(0); + order.messageSent = MessageSent.PaymentFail; + LOG.info("Order " + order.id + ": Payment Failure message sent successfully," + + " request Id: " + requestId); + } + }; + Retry.HandleErrorIssue handleError = (o, err) -> { + if ((o.messageSent.equals(MessageSent.NoneSent) || o.messageSent + .equals(MessageSent.PaymentTrying)) + && System.currentTimeMillis() - o.createdTime < messageTime) { + var qt = new QueueTask(order, TaskType.Messaging, 0); + updateQueue(qt); + LOG.warn("Order " + order.id + ": Error in sending Payment Failure message, " + + "trying to queue task and add to employee handle.."); + employeeHandleIssue(o); + } + }; + var r = new Retry<>(op, handleError, numOfRetries, retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + e1.printStackTrace(); + } + }); + t.start(); + } + + private void sendPaymentPossibleErrorMsg(Order order) { + if (System.currentTimeMillis() - order.createdTime >= this.messageTime) { + LOG.trace("Message time for order over, returning.."); + return; + } + var list = messagingService.exceptionsList; + var t = new Thread(() -> { + Retry.Operation op = (l) -> { + if (!l.isEmpty()) { + if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { + LOG.debug("Order " + order.id + ": Error in connecting to messaging service " + + "(Payment Error msg), trying again.."); + } else { + LOG.debug("Order " + order.id + ": Error in creating Payment Error" + + " messaging request.."); + } + throw l.remove(0); + } + if (order.paid.equals(PaymentStatus.Trying) && order.messageSent + .equals(MessageSent.NoneSent)) { + var requestId = messagingService.receiveRequest(1); + order.messageSent = MessageSent.PaymentTrying; + LOG.info("Order " + order.id + ": Payment Error message sent successfully," + + " request Id: " + requestId); + } + }; + Retry.HandleErrorIssue handleError = (o, err) -> { + if (o.messageSent.equals(MessageSent.NoneSent) && order.paid + .equals(PaymentStatus.Trying) + && System.currentTimeMillis() - o.createdTime < messageTime) { + var qt = new QueueTask(order, TaskType.Messaging, 1); + updateQueue(qt); + LOG.warn("Order " + order.id + ": Error in sending Payment Error message, " + + "trying to queue task and add to employee handle.."); + employeeHandleIssue(o); + } + }; + var r = new Retry<>(op, handleError, numOfRetries, retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + e1.printStackTrace(); + } + }); + t.start(); + } + + private void employeeHandleIssue(Order order) { + if (System.currentTimeMillis() - order.createdTime >= this.employeeTime) { + LOG.trace("Order " + order.id + ": Employee handle time for order over, returning.."); + return; + } + var list = employeeDb.exceptionsList; + var t = new Thread(() -> { + Retry.Operation op = (l) -> { + if (!l.isEmpty()) { + LOG.warn("Order " + order.id + ": Error in connecting to employee handle," + + " trying again.."); + throw l.remove(0); + } + if (!order.addedToEmployeeHandle) { + employeeDb.receiveRequest(order); + order.addedToEmployeeHandle = true; + LOG.info("Order " + order.id + ": Added order to employee database"); + } + }; + Retry.HandleErrorIssue handleError = (o, err) -> { + if (!o.addedToEmployeeHandle && System + .currentTimeMillis() - order.createdTime < employeeTime) { + var qt = new QueueTask(order, TaskType.EmployeeDb, -1); + updateQueue(qt); + LOG.warn("Order " + order.id + ": Error in adding to employee db," + + " trying to queue task.."); + } + }; + var r = new Retry<>(op, handleError, numOfRetries, retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + e1.printStackTrace(); + } + }); + t.start(); + } + + private void doTasksInQueue() throws Exception { + if (queueItems != 0) { + var qt = queue.peek(); //this should probably be cloned here + //this is why we have retry for doTasksInQueue + LOG.trace("Order " + qt.order.id + ": Started doing task of type " + qt.getType()); + if (qt.firstAttemptTime == -1) { + qt.firstAttemptTime = System.currentTimeMillis(); + } + if (System.currentTimeMillis() - qt.firstAttemptTime >= queueTaskTime) { + tryDequeue(); + LOG.trace("Order " + qt.order.id + ": This queue task of type " + qt.getType() + + " does not need to be done anymore (timeout), dequeue.."); + } else { + if (qt.taskType.equals(TaskType.Payment)) { + if (!qt.order.paid.equals(PaymentStatus.Trying)) { + tryDequeue(); + LOG.trace("Order " + qt.order.id + ": This payment task already done, dequeueing.."); + } else { + sendPaymentRequest(qt.order); + LOG.debug("Order " + qt.order.id + ": Trying to connect to payment service.."); + } + } else if (qt.taskType.equals(TaskType.Messaging)) { + if (qt.order.messageSent.equals(MessageSent.PaymentFail) + || qt.order.messageSent.equals(MessageSent.PaymentSuccessful)) { + tryDequeue(); + LOG.trace("Order " + qt.order.id + ": This messaging task already done, dequeue.."); + } else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NoneSent) + || !qt.order.paid.equals(PaymentStatus.Trying))) { + tryDequeue(); + LOG.trace("Order " + qt.order.id + ": This messaging task does not need to be done," + + " dequeue.."); + } else if (qt.messageType == 0) { + sendPaymentFailureMessage(qt.order); + LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service.."); + } else if (qt.messageType == 1) { + sendPaymentPossibleErrorMsg(qt.order); + LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service.."); + } else if (qt.messageType == 2) { + sendSuccessMessage(qt.order); + LOG.debug("Order " + qt.order.id + ": Trying to connect to messaging service.."); + } + } else if (qt.taskType.equals(TaskType.EmployeeDb)) { + if (qt.order.addedToEmployeeHandle) { + tryDequeue(); + LOG.trace("Order " + qt.order.id + ": This employee handle task already done," + + " dequeue.."); + } else { + employeeHandleIssue(qt.order); + LOG.debug("Order " + qt.order.id + ": Trying to connect to employee handle.."); + } + } + } + } + if (queueItems == 0) { + LOG.trace("Queue is empty, returning.."); + } else { + Thread.sleep(queueTaskTime / 3); + tryDoingTasksInQueue(); + } + } + +} diff --git a/commander/src/main/java/com/iluwatar/commander/Database.java b/commander/src/main/java/com/iluwatar/commander/Database.java new file mode 100644 index 000000000000..ebd333e021c7 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/Database.java @@ -0,0 +1,39 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander; + +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; + +/** + * Database abstract class is extended by all databases in our example. The add and get methods are + * used by the respective service to add to database or get from database. + * + * @param T is the type of object being held by database. + */ + +public abstract class Database { + public abstract T add(T obj) throws DatabaseUnavailableException; + + public abstract T get(String id) throws DatabaseUnavailableException; +} diff --git a/commander/src/main/java/com/iluwatar/commander/Order.java b/commander/src/main/java/com/iluwatar/commander/Order.java new file mode 100644 index 000000000000..87a9f794a969 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/Order.java @@ -0,0 +1,82 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander; + +import java.util.Hashtable; +import java.util.Random; + +/** + * Order class holds details of the order. + */ + +public class Order { //can store all transactions ids also + + enum PaymentStatus { + NotDone, Trying, Done + } + + enum MessageSent { + NoneSent, PaymentFail, PaymentTrying, PaymentSuccessful + } + + final User user; + final String item; + public final String id; + final float price; + final long createdTime; + private static final Random RANDOM = new Random(); + private static final String ALL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + private static final Hashtable USED_IDS = new Hashtable(); + PaymentStatus paid; + MessageSent messageSent; //to avoid sending error msg on page and text more than once + boolean addedToEmployeeHandle; //to avoid creating more to enqueue + + Order(User user, String item, float price) { + this.createdTime = System.currentTimeMillis(); + this.user = user; + this.item = item; + this.price = price; + String id = createUniqueId(); + if (USED_IDS.get(id) != null) { + while (USED_IDS.get(id)) { + id = createUniqueId(); + } + } + this.id = id; + USED_IDS.put(this.id, true); + this.paid = PaymentStatus.Trying; + this.messageSent = MessageSent.NoneSent; + this.addedToEmployeeHandle = false; + } + + private String createUniqueId() { + StringBuilder random = new StringBuilder(); + while (random.length() < 12) { // length of the random string. + int index = (int) (RANDOM.nextFloat() * ALL_CHARS.length()); + random.append(ALL_CHARS.charAt(index)); + } + return random.toString(); + } + +} diff --git a/commander/src/main/java/com/iluwatar/commander/Retry.java b/commander/src/main/java/com/iluwatar/commander/Retry.java new file mode 100644 index 000000000000..be93a821cb64 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/Retry.java @@ -0,0 +1,110 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; + +/** + * Retry pattern. + * + * @param is the type of object passed into HandleErrorIssue as a parameter. + */ + +public class Retry { + + /** + * Operation Interface will define method to be implemented. + */ + + public interface Operation { + void operation(List list) throws Exception; + } + + /** + * HandleErrorIssue defines how to handle errors. + * + * @param is the type of object to be passed into the method as parameter. + */ + + public interface HandleErrorIssue { + void handleIssue(T obj, Exception e); + } + + private static final Random RANDOM = new Random(); + + private final Operation op; + private final HandleErrorIssue handleError; + private final int maxAttempts; + private final long maxDelay; + private final AtomicInteger attempts; + private final Predicate test; + private final List errors; + + Retry(Operation op, HandleErrorIssue handleError, int maxAttempts, + long maxDelay, Predicate... ignoreTests) { + this.op = op; + this.handleError = handleError; + this.maxAttempts = maxAttempts; + this.maxDelay = maxDelay; + this.attempts = new AtomicInteger(); + this.test = Arrays.stream(ignoreTests).reduce(Predicate::or).orElse(e -> false); + this.errors = new ArrayList<>(); + } + + /** + * Performing the operation with retries. + * + * @param list is the exception list + * @param obj is the parameter to be passed into handleIsuue method + */ + + public void perform(List list, T obj) { + do { + try { + op.operation(list); + return; + } catch (Exception e) { + this.errors.add(e); + if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) { + this.handleError.handleIssue(obj, e); + return; //return here...dont go further + } + try { + long testDelay = + (long) Math.pow(2, this.attempts.intValue()) * 1000 + RANDOM.nextInt(1000); + long delay = Math.min(testDelay, this.maxDelay); + Thread.sleep(delay); + } catch (InterruptedException f) { + //ignore + } + } + } while (true); + } + +} diff --git a/commander/src/main/java/com/iluwatar/commander/Service.java b/commander/src/main/java/com/iluwatar/commander/Service.java new file mode 100644 index 000000000000..ddcc2d5bf4e9 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/Service.java @@ -0,0 +1,72 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander; + +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import java.util.Random; + +/** + * Service class is an abstract class extended by all services in this example. They all have a + * public receiveRequest method to receive requests, which could also contain details of the user + * other than the implementation details (though we are not doing that here) and updateDb method + * which adds to their respective databases. There is a method to generate transaction/request id + * for the transactions/requests, which are then sent back. These could be stored by the {@link + * Commander} class in a separate database for reference (though we are not doing that here). + */ + +public abstract class Service { + + protected final Database database; + public ArrayList exceptionsList; + private static final Random RANDOM = new Random(); + private static final String ALL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + private static final Hashtable USED_IDS = new Hashtable<>(); + + protected Service(Database db, Exception... exc) { + this.database = db; + this.exceptionsList = new ArrayList<>(List.of(exc)); + } + + public abstract String receiveRequest(Object... parameters) throws DatabaseUnavailableException; + + protected abstract String updateDb(Object... parameters) throws DatabaseUnavailableException; + + protected String generateId() { + StringBuilder random = new StringBuilder(); + while (random.length() < 12) { // length of the random string. + int index = (int) (RANDOM.nextFloat() * ALL_CHARS.length()); + random.append(ALL_CHARS.charAt(index)); + } + String id = random.toString(); + if (USED_IDS.get(id) != null) { + while (USED_IDS.get(id)) { + id = generateId(); + } + } + return id; + } +} diff --git a/commander/src/main/java/com/iluwatar/commander/User.java b/commander/src/main/java/com/iluwatar/commander/User.java new file mode 100644 index 000000000000..b1389ab4caf8 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/User.java @@ -0,0 +1,39 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander; + +/** + * User class contains details of user who places order. + */ + +public class User { + String name; + String address; + + User(String name, String address) { + this.name = name; + this.address = address; + } + +} diff --git a/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java b/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java new file mode 100644 index 000000000000..496bb545add6 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java @@ -0,0 +1,51 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.employeehandle; + +import com.iluwatar.commander.Database; +import com.iluwatar.commander.Order; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import java.util.Hashtable; + +/** + * The Employee Database is where orders which have encountered some issue(s) are added. + */ + +public class EmployeeDatabase extends Database { + private Hashtable data; + + public EmployeeDatabase() { + this.data = new Hashtable<>(); + } + + @Override + public Order add(Order o) throws DatabaseUnavailableException { + return data.put(o.id, o); + } + + @Override + public Order get(String orderId) throws DatabaseUnavailableException { + return data.get(orderId); + } +} diff --git a/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeHandle.java b/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeHandle.java new file mode 100644 index 000000000000..3c9c3cb97159 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeHandle.java @@ -0,0 +1,54 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.employeehandle; + +import com.iluwatar.commander.Order; +import com.iluwatar.commander.Service; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; + +/** + * The EmployeeHandle class is the middle-man between {@link com.iluwatar.commander.Commander} and + * {@link EmployeeDatabase}. + */ + +public class EmployeeHandle extends Service { + + public EmployeeHandle(EmployeeDatabase db, Exception... exc) { + super(db, exc); + } + + public String receiveRequest(Object... parameters) throws DatabaseUnavailableException { + return updateDb(parameters[0]); + } + + protected String updateDb(Object... parameters) throws DatabaseUnavailableException { + var o = (Order) parameters[0]; + if (database.get(o.id) == null) { + database.add(o); + return o.id; //true rcvd - change addedToEmployeeHandle to true else dont do anything + } + return null; + } + +} diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/DatabaseUnavailableException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/DatabaseUnavailableException.java new file mode 100644 index 000000000000..8997b1a10a2d --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/DatabaseUnavailableException.java @@ -0,0 +1,33 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.exceptions; + +/** + * DatabaseUnavailableException is thrown when database is unavailable and nothing can be added or + * retrieved. + */ + +public class DatabaseUnavailableException extends Exception { + private static final long serialVersionUID = 2459603L; +} diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/IsEmptyException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/IsEmptyException.java new file mode 100644 index 000000000000..98c3cfd90647 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/IsEmptyException.java @@ -0,0 +1,32 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.exceptions; + +/** + * IsEmptyException is thrown when it is attempted to dequeue from an empty queue. + */ + +public class IsEmptyException extends Exception { + private static final long serialVersionUID = 123546L; +} diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/ItemUnavailableException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/ItemUnavailableException.java new file mode 100644 index 000000000000..7ba5b2a13980 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/ItemUnavailableException.java @@ -0,0 +1,32 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.exceptions; + +/** + * ItemUnavailableException is thrown when item is not available for shipping. + */ + +public class ItemUnavailableException extends Exception { + private static final long serialVersionUID = 575940L; +} diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/PaymentDetailsErrorException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/PaymentDetailsErrorException.java new file mode 100644 index 000000000000..eb7c0fb3fd35 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/PaymentDetailsErrorException.java @@ -0,0 +1,33 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.exceptions; + +/** + * PaymentDetailsErrorException is thrown when the details entered are incorrect or payment cannot + * be made with the details given. + */ + +public class PaymentDetailsErrorException extends Exception { + private static final long serialVersionUID = 867203L; +} diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/ShippingNotPossibleException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/ShippingNotPossibleException.java new file mode 100644 index 000000000000..b374f2a7d65c --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/ShippingNotPossibleException.java @@ -0,0 +1,33 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.exceptions; + +/** + * ShippingNotPossibleException is thrown when the address entered cannot be shipped to by service + * currently for some reason. + */ + +public class ShippingNotPossibleException extends Exception { + private static final long serialVersionUID = 342055L; +} diff --git a/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java new file mode 100644 index 000000000000..fbba52cac8b7 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java @@ -0,0 +1,52 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.messagingservice; + +import com.iluwatar.commander.Database; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import com.iluwatar.commander.messagingservice.MessagingService.MessageRequest; +import java.util.Hashtable; + +/** + * The MessagingDatabase is where the MessageRequest is added. + */ + +public class MessagingDatabase extends Database { + private Hashtable data; + + public MessagingDatabase() { + this.data = new Hashtable<>(); + } + + @Override + public MessageRequest add(MessageRequest r) { + return data.put(r.reqId, r); + } + + @Override + public MessageRequest get(String requestId) { + return data.get(requestId); + } + +} diff --git a/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java new file mode 100644 index 000000000000..3fb38575726c --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java @@ -0,0 +1,101 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.messagingservice; + +import com.iluwatar.commander.Service; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The MessagingService is used to send messages to user regarding their order and payment status. + * In case an error is encountered in payment and this service is found to be unavailable, the order + * is added to the {@link com.iluwatar.commander.employeehandle.EmployeeDatabase}. + */ + +public class MessagingService extends Service { + private static final Logger LOGGER = LoggerFactory.getLogger(MessagingService.class); + + enum MessageToSend { + PaymentFail, PaymentTrying, PaymentSuccessful + } + + class MessageRequest { + String reqId; + MessageToSend msg; + + MessageRequest(String reqId, MessageToSend msg) { + this.reqId = reqId; + this.msg = msg; + } + } + + public MessagingService(MessagingDatabase db, Exception... exc) { + super(db, exc); + } + + /** + * Public method which will receive request from {@link com.iluwatar.commander.Commander}. + */ + public String receiveRequest(Object... parameters) throws DatabaseUnavailableException { + var messageToSend = (int) parameters[0]; + var id = generateId(); + MessageToSend msg; + if (messageToSend == 0) { + msg = MessageToSend.PaymentFail; + } else if (messageToSend == 1) { + msg = MessageToSend.PaymentTrying; + } else { //messageToSend == 2 + msg = MessageToSend.PaymentSuccessful; + } + var req = new MessageRequest(id, msg); + return updateDb(req); + } + + protected String updateDb(Object... parameters) throws DatabaseUnavailableException { + var req = (MessageRequest) parameters[0]; + if (this.database.get(req.reqId) == null) { //idempotence, in case db fails here + database.add(req); //if successful: + LOGGER.info(sendMessage(req.msg)); + return req.reqId; + } + return null; + } + + String sendMessage(MessageToSend m) { + if (m.equals(MessageToSend.PaymentSuccessful)) { + return "Msg: Your order has been placed and paid for successfully!" + + " Thank you for shopping with us!"; + } else if (m.equals(MessageToSend.PaymentTrying)) { + return "Msg: There was an error in your payment process," + + " we are working on it and will return back to you shortly." + + " Meanwhile, your order has been placed and will be shipped."; + } else { + return "Msg: There was an error in your payment process." + + " Your order is placed and has been converted to COD." + + " Please reach us on CUSTOMER-CARE-NUBER in case of any queries." + + " Thank you for shopping with us!"; + } + } +} diff --git a/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java b/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java new file mode 100644 index 000000000000..644979883f24 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java @@ -0,0 +1,54 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.paymentservice; + +import com.iluwatar.commander.Database; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import com.iluwatar.commander.paymentservice.PaymentService.PaymentRequest; +import java.util.Hashtable; + +/** + * PaymentDatabase is where the PaymentRequest is added, along with details. + */ + +public class PaymentDatabase extends Database { + + private Hashtable data; + + public PaymentDatabase() { + this.data = new Hashtable<>(); + //0-fail, 1-error, 2-success + } + + @Override + public PaymentRequest add(PaymentRequest r) { + return data.put(r.transactionId, r); + } + + @Override + public PaymentRequest get(String requestId) { + return data.get(requestId); + } + +} diff --git a/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentService.java b/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentService.java new file mode 100644 index 000000000000..f18b55eaae7a --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentService.java @@ -0,0 +1,72 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.paymentservice; + +import com.iluwatar.commander.Service; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; + +/** + * The PaymentService class receives request from the {@link com.iluwatar.commander.Commander} and + * adds to the {@link PaymentDatabase}. + */ + +public class PaymentService extends Service { + + class PaymentRequest { + String transactionId; + float payment; + boolean paid; + + PaymentRequest(String transactionId, float payment) { + this.transactionId = transactionId; + this.payment = payment; + this.paid = false; + } + } + + public PaymentService(PaymentDatabase db, Exception... exc) { + super(db, exc); + } + + /** + * Public method which will receive request from {@link com.iluwatar.commander.Commander}. + */ + + public String receiveRequest(Object... parameters) throws DatabaseUnavailableException { + //it could also be sending a userid, payment details here or something, not added here + var id = generateId(); + var req = new PaymentRequest(id, (float) parameters[0]); + return updateDb(req); + } + + protected String updateDb(Object... parameters) throws DatabaseUnavailableException { + var req = (PaymentRequest) parameters[0]; + if (database.get(req.transactionId) == null || !req.paid) { + database.add(req); + req.paid = true; + return req.transactionId; + } + return null; + } +} diff --git a/commander/src/main/java/com/iluwatar/commander/queue/Queue.java b/commander/src/main/java/com/iluwatar/commander/queue/Queue.java new file mode 100644 index 000000000000..7a2c5d7aec36 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/queue/Queue.java @@ -0,0 +1,93 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.queue; + +import com.iluwatar.commander.exceptions.IsEmptyException; + +/** + * Queue data structure implementation. + * + * @param is the type of object the queue will hold. + */ + +public class Queue { + + private Node front; + private Node rear; + private int size; + + static class Node { + V value; + Node next; + + Node(V obj, Node b) { + value = obj; + next = b; + } + } + + /** + * Queue constructor. + */ + Queue() { + front = null; + rear = null; + size = 0; + } + + boolean isEmpty() { + return size == 0; + } + + void enqueue(T obj) { + if (front == null) { + front = new Node<>(obj, null); + rear = front; + } else { + var temp = new Node<>(obj, null); + rear.next = temp; + rear = temp; + } + size++; + } + + T dequeue() throws IsEmptyException { + if (isEmpty()) { + throw new IsEmptyException(); + } else { + var temp = front; + front = front.next; + size = size - 1; + return temp.value; + } + } + + T peek() throws IsEmptyException { + if (isEmpty()) { + throw new IsEmptyException(); + } else { + return front.value; + } + } +} diff --git a/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java b/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java new file mode 100644 index 000000000000..91a7966f7566 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java @@ -0,0 +1,80 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.queue; + +import com.iluwatar.commander.Database; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import com.iluwatar.commander.exceptions.IsEmptyException; +import java.util.ArrayList; +import java.util.List; + +/** + * QueueDatabase id where the instructions to be implemented are queued. + */ + +public class QueueDatabase extends Database { + + private Queue data; + public List exceptionsList; + + public QueueDatabase(Exception... exc) { + this.data = new Queue<>(); + this.exceptionsList = new ArrayList<>(List.of(exc)); + } + + @Override + public QueueTask add(QueueTask t) { + data.enqueue(t); + return t; + //even if same thing queued twice, it is taken care of in other dbs + } + + /** + * peek method returns object at front without removing it from queue. + * + * @return object at front of queue + * @throws IsEmptyException if queue is empty + */ + + public QueueTask peek() throws IsEmptyException { + return this.data.peek(); + } + + /** + * dequeue method removes the object at front and returns it. + * + * @return object at front of queue + * @throws IsEmptyException if queue is empty + */ + + public QueueTask dequeue() throws IsEmptyException { + return this.data.dequeue(); + } + + @Override + public QueueTask get(String taskId) { + return null; + } + +} diff --git a/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java b/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java new file mode 100644 index 000000000000..a27dd62b89ab --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java @@ -0,0 +1,83 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.queue; + +import com.iluwatar.commander.Order; + +/** + * QueueTask object is the object enqueued in queue. + */ + +public class QueueTask { + + /** + * TaskType is the type of task to be done. + */ + + public enum TaskType { + Messaging, Payment, EmployeeDb + } + + public Order order; + public TaskType taskType; + public int messageType; //0-fail, 1-error, 2-success + /*we could have varargs Object instead to pass in any parameter instead of just message type + but keeping it simple here*/ + public long firstAttemptTime; //when first time attempt made to do task + + /** + * QueueTask constructor. + * + * @param o is the order for which the queuetask is being created + * @param t is the type of task to be done + * @param messageType if it is a message, which type of message - this could have instead been + * object varargs, and contained all additional details related to tasktype. + */ + + public QueueTask(Order o, TaskType t, int messageType) { + this.order = o; + this.taskType = t; + this.messageType = messageType; + this.firstAttemptTime = -1; + } + + /** + * getType method. + * + * @return String representing type of task + */ + public String getType() { + if (!this.taskType.equals(TaskType.Messaging)) { + return this.taskType.toString(); + } else { + if (this.messageType == 0) { + return "Payment Failure Message"; + } else if (this.messageType == 1) { + return "Payment Error Message"; + } else { + return "Payment Success Message"; + } + } + } +} diff --git a/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java b/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java new file mode 100644 index 000000000000..305122db298a --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java @@ -0,0 +1,52 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.shippingservice; + +import com.iluwatar.commander.Database; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import com.iluwatar.commander.shippingservice.ShippingService.ShippingRequest; +import java.util.Hashtable; + +/** + * ShippingDatabase is where the ShippingRequest objects are added. + */ + +public class ShippingDatabase extends Database { + + private Hashtable data; + + public ShippingDatabase() { + this.data = new Hashtable<>(); + } + + @Override + public ShippingRequest add(ShippingRequest r) { + return data.put(r.transactionId, r); + } + + public ShippingRequest get(String trasnactionId) { + return data.get(trasnactionId); + } + +} diff --git a/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingService.java b/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingService.java new file mode 100644 index 000000000000..250cfa807827 --- /dev/null +++ b/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingService.java @@ -0,0 +1,72 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander.shippingservice; + +import com.iluwatar.commander.Service; +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; + +/** + * ShippingService class receives request from {@link com.iluwatar.commander.Commander} class and + * adds it to the {@link ShippingDatabase}. + */ + +public class ShippingService extends Service { + + static class ShippingRequest { + String transactionId; + String item; + String address; + + ShippingRequest(String transactionId, String item, String address) { + this.transactionId = transactionId; + this.item = item; + this.address = address; + } + } + + public ShippingService(ShippingDatabase db, Exception... exc) { + super(db, exc); + } + + /** + * Public method which will receive request from {@link com.iluwatar.commander.Commander}. + */ + + public String receiveRequest(Object... parameters) throws DatabaseUnavailableException { + var id = generateId(); + var item = (String) parameters[0]; + var address = (String) parameters[1]; + var req = new ShippingRequest(id, item, address); + return updateDb(req); + } + + protected String updateDb(Object... parameters) throws DatabaseUnavailableException { + var req = (ShippingRequest) parameters[0]; + if (this.database.get(req.transactionId) == null) { + database.add(req); + return req.transactionId; + } + return null; + } +} diff --git a/commander/src/test/java/com/iluwatar/commander/RetryTest.java b/commander/src/test/java/com/iluwatar/commander/RetryTest.java new file mode 100644 index 000000000000..e14bbbc6af51 --- /dev/null +++ b/commander/src/test/java/com/iluwatar/commander/RetryTest.java @@ -0,0 +1,67 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.commander; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.iluwatar.commander.exceptions.DatabaseUnavailableException; +import com.iluwatar.commander.exceptions.ItemUnavailableException; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class RetryTest { + + @Test + void performTest() { + Retry.Operation op = (l) -> { + if (!l.isEmpty()) { + throw l.remove(0); + } + }; + Retry.HandleErrorIssue handleError = (o, e) -> { + }; + var r1 = new Retry<>(op, handleError, 3, 30000, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + var r2 = new Retry<>(op, handleError, 3, 30000, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + var user = new User("Jim", "ABCD"); + var order = new Order(user, "book", 10f); + var arr1 = new ArrayList<>(List.of(new ItemUnavailableException(), new DatabaseUnavailableException())); + try { + r1.perform(arr1, order); + } catch (Exception e1) { + e1.printStackTrace(); + } + var arr2 = new ArrayList<>(List.of(new DatabaseUnavailableException(), new ItemUnavailableException())); + try { + r2.perform(arr2, order); + } catch (Exception e1) { + e1.printStackTrace(); + } + //r1 stops at ItemUnavailableException, r2 retries because it encounters DatabaseUnavailableException + assertTrue(arr1.size() == 1 && arr2.size() == 0); + } + +} diff --git a/composite/README.md b/composite/README.md index 05ee70cbb9b9..1db1ce14fb68 100644 --- a/composite/README.md +++ b/composite/README.md @@ -5,9 +5,7 @@ folder: composite permalink: /patterns/composite/ categories: Structural tags: - - Java - - Gang Of Four - - Difficulty-Intermediate + - Gang of Four --- ## Intent @@ -35,41 +33,56 @@ Taking our sentence example from above. Here we have the base class and differen ```java public abstract class LetterComposite { + private List children = new ArrayList<>(); + public void add(LetterComposite letter) { children.add(letter); } + public int count() { return children.size(); } - protected void printThisBefore() {} - protected void printThisAfter() {} + + protected void printThisBefore() { + } + + protected void printThisAfter() { + } + public void print() { printThisBefore(); - for (LetterComposite letter : children) { - letter.print(); - } + children.forEach(LetterComposite::print); printThisAfter(); } } public class Letter extends LetterComposite { - private char c; + + private char character; + public Letter(char c) { - this.c = c; + this.character = c; } + @Override protected void printThisBefore() { - System.out.print(c); + System.out.print(character); } } public class Word extends LetterComposite { + public Word(List letters) { - for (Letter l : letters) { - this.add(l); + letters.forEach(this::add); + } + + public Word(char... letters) { + for (char letter : letters) { + this.add(new Letter(letter)); } } + @Override protected void printThisBefore() { System.out.print(" "); @@ -77,11 +90,11 @@ public class Word extends LetterComposite { } public class Sentence extends LetterComposite { + public Sentence(List words) { - for (Word w : words) { - this.add(w); - } + words.forEach(this::add); } + @Override protected void printThisAfter() { System.out.print("."); @@ -93,42 +106,55 @@ Then we have a messenger to carry messages ```java public class Messenger { + LetterComposite messageFromOrcs() { - List words = new ArrayList<>(); - words.add(new Word(Arrays.asList(new Letter('W'), new Letter('h'), new Letter('e'), new Letter('r'), new Letter('e')))); - words.add(new Word(Arrays.asList(new Letter('t'), new Letter('h'), new Letter('e'), new Letter('r'), new Letter('e')))); - words.add(new Word(Arrays.asList(new Letter('i'), new Letter('s')))); - words.add(new Word(Arrays.asList(new Letter('a')))); - words.add(new Word(Arrays.asList(new Letter('w'), new Letter('h'), new Letter('i'), new Letter('p')))); - words.add(new Word(Arrays.asList(new Letter('t'), new Letter('h'), new Letter('e'), new Letter('r'), new Letter('e')))); - words.add(new Word(Arrays.asList(new Letter('i'), new Letter('s')))); - words.add(new Word(Arrays.asList(new Letter('a')))); - words.add(new Word(Arrays.asList(new Letter('w'), new Letter('a'), new Letter('y')))); + + var words = List.of( + new Word('W', 'h', 'e', 'r', 'e'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'h', 'i', 'p'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'a', 'y') + ); + return new Sentence(words); + } LetterComposite messageFromElves() { - List words = new ArrayList<>(); - words.add(new Word(Arrays.asList(new Letter('M'), new Letter('u'), new Letter('c'), new Letter('h')))); - words.add(new Word(Arrays.asList(new Letter('w'), new Letter('i'), new Letter('n'), new Letter('d')))); - words.add(new Word(Arrays.asList(new Letter('p'), new Letter('o'), new Letter('u'), new Letter('r'), new Letter('s')))); - words.add(new Word(Arrays.asList(new Letter('f'), new Letter('r'), new Letter('o'), new Letter('m')))); - words.add(new Word(Arrays.asList(new Letter('y'), new Letter('o'), new Letter('u'), new Letter('r')))); - words.add(new Word(Arrays.asList(new Letter('m'), new Letter('o'), new Letter('u'), new Letter('t'), new Letter('h')))); + + var words = List.of( + new Word('M', 'u', 'c', 'h'), + new Word('w', 'i', 'n', 'd'), + new Word('p', 'o', 'u', 'r', 's'), + new Word('f', 'r', 'o', 'm'), + new Word('y', 'o', 'u', 'r'), + new Word('m', 'o', 'u', 't', 'h') + ); + return new Sentence(words); + } + } ``` And then it can be used as ```java -LetterComposite orcMessage = new Messenger().messageFromOrcs(); +var orcMessage = new Messenger().messageFromOrcs(); orcMessage.print(); // Where there is a whip there is a way. -LetterComposite elfMessage = new Messenger().messageFromElves(); +var elfMessage = new Messenger().messageFromElves(); elfMessage.print(); // Much wind pours from your mouth. ``` +## Class diagram +![alt text](./etc/composite.urm.png "Composite class diagram") + ## Applicability Use the Composite pattern when diff --git a/composite/etc/composite.urm.png b/composite/etc/composite.urm.png new file mode 100644 index 000000000000..93c160f6450a Binary files /dev/null and b/composite/etc/composite.urm.png differ diff --git a/composite/etc/composite.urm.puml b/composite/etc/composite.urm.puml new file mode 100644 index 000000000000..6ff774711bcd --- /dev/null +++ b/composite/etc/composite.urm.puml @@ -0,0 +1,41 @@ +@startuml +package com.iluwatar.composite { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + class Letter { + - character : char + + Letter(c : char) + # printThisBefore() + } + abstract class LetterComposite { + - children : List + + LetterComposite() + + add(letter : LetterComposite) + + count() : int + + print() + # printThisAfter() + # printThisBefore() + } + class Messenger { + + Messenger() + ~ messageFromElves() : LetterComposite + ~ messageFromOrcs() : LetterComposite + } + class Sentence { + + Sentence(words : List) + # printThisAfter() + } + class Word { + + Word(letters : List) + + Word(letters : char[]) + # printThisBefore() + } +} +LetterComposite --> "-children" LetterComposite +Letter --|> LetterComposite +Sentence --|> LetterComposite +Word --|> LetterComposite +@enduml \ No newline at end of file diff --git a/composite/pom.xml b/composite/pom.xml index 60142cf9061c..c16b95c13a8a 100644 --- a/composite/pom.xml +++ b/composite/pom.xml @@ -2,7 +2,7 @@ + - - java-design-patterns - com.iluwatar - 1.21.0-SNAPSHOT - - 4.0.0 - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - com.google.guava - guava - - - converter - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + java-design-patterns + com.iluwatar + 1.23.0-SNAPSHOT + + converter + 4.0.0 + + + org.junit.jupiter + junit-jupiter-engine + test + + + com.google.guava + guava + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.converter.App + + + + + + + + diff --git a/converter/src/main/java/com/iluwatar/converter/App.java b/converter/src/main/java/com/iluwatar/converter/App.java index 6e436706d94f..2d4ec0429d21 100644 --- a/converter/src/main/java/com/iluwatar/converter/App.java +++ b/converter/src/main/java/com/iluwatar/converter/App.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,13 +20,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.converter; - -import com.google.common.collect.Lists; +package com.iluwatar.converter; -import java.util.ArrayList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The Converter pattern is a behavioral design pattern which allows a common way of bidirectional @@ -35,8 +34,11 @@ * objects between types. */ public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + /** - * Program entry point + * Program entry point. * * @param args command line args */ @@ -45,16 +47,19 @@ public static void main(String[] args) { UserDto dtoUser = new UserDto("John", "Doe", true, "whatever[at]wherever.com"); User user = userConverter.convertFromDto(dtoUser); - System.out.println("Entity converted from DTO:" + user); + LOGGER.info("Entity converted from DTO:" + user); - ArrayList users = Lists.newArrayList(new User("Camile", "Tough", false, "124sad"), - new User("Marti", "Luther", true, "42309fd"), new User("Kate", "Smith", true, "if0243")); - System.out.println("Domain entities:"); - users.forEach(System.out::println); + var users = List.of( + new User("Camile", "Tough", false, "124sad"), + new User("Marti", "Luther", true, "42309fd"), + new User("Kate", "Smith", true, "if0243") + ); + LOGGER.info("Domain entities:"); + users.stream().map(User::toString).forEach(LOGGER::info); - System.out.println("DTO entities converted from domain:"); + LOGGER.info("DTO entities converted from domain:"); List dtoEntities = userConverter.createFromEntities(users); - dtoEntities.forEach(System.out::println); + dtoEntities.stream().map(UserDto::toString).forEach(LOGGER::info); } } diff --git a/converter/src/main/java/com/iluwatar/converter/Converter.java b/converter/src/main/java/com/iluwatar/converter/Converter.java index 4a2bb7381ede..10425e1ca907 100644 --- a/converter/src/main/java/com/iluwatar/converter/Converter.java +++ b/converter/src/main/java/com/iluwatar/converter/Converter.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -30,8 +30,9 @@ /** * Generic converter, thanks to Java8 features not only provides a way of generic bidirectional - * conversion between coresponding types, but also a common way of converting a collection of objects - * of the same type, reducing boilerplate code to the absolute minimum. + * conversion between corresponding types, but also a common way of converting a collection of + * objects of the same type, reducing boilerplate code to the absolute minimum. + * * @param DTO representation's type * @param Domain representation's type */ @@ -41,7 +42,9 @@ public class Converter { private final Function fromEntity; /** - * @param fromDto Function that converts given dto entity into the domain entity. + * Constructor. + * + * @param fromDto Function that converts given dto entity into the domain entity. * @param fromEntity Function that converts given domain entity into the dto entity. */ public Converter(final Function fromDto, final Function fromEntity) { @@ -50,34 +53,44 @@ public Converter(final Function fromDto, final Function fromEntity) } /** + * Converts DTO to Entity. + * * @param dto DTO entity - * @return The domain representation - the result of the converting function application on dto entity. + * @return The domain representation - the result of the converting function application on dto + * entity. */ public final U convertFromDto(final T dto) { return fromDto.apply(dto); } /** + * Converts Entity to DTO. + * * @param entity domain entity - * @return The DTO representation - the result of the converting function application on domain entity. + * @return The DTO representation - the result of the converting function application on domain + * entity. */ public final T convertFromEntity(final U entity) { return fromEntity.apply(entity); } /** + * Converts list of DTOs to list of Entities. + * * @param dtos collection of DTO entities - * @return List of domain representation of provided entities retrieved by - * mapping each of them with the conversion function + * @return List of domain representation of provided entities retrieved by mapping each of them + * with the conversion function */ public final List createFromDtos(final Collection dtos) { return dtos.stream().map(this::convertFromDto).collect(Collectors.toList()); } /** + * Converts list of Entities to list of DTOs. + * * @param entities collection of domain entities - * @return List of domain representation of provided entities retrieved by - * mapping each of them with the conversion function + * @return List of domain representation of provided entities retrieved by mapping each of them + * with the conversion function */ public final List createFromEntities(final Collection entities) { return entities.stream().map(this::convertFromEntity).collect(Collectors.toList()); diff --git a/converter/src/main/java/com/iluwatar/converter/User.java b/converter/src/main/java/com/iluwatar/converter/User.java index 62903a9313cd..637d77a25e8e 100644 --- a/converter/src/main/java/com/iluwatar/converter/User.java +++ b/converter/src/main/java/com/iluwatar/converter/User.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -26,7 +26,7 @@ import java.util.Objects; /** - * User class + * User class. */ public class User { private String firstName; @@ -35,10 +35,12 @@ public class User { private String userId; /** + * Constructor. + * * @param firstName user's first name * @param lastName user's last name * @param isActive flag indicating whether the user is active - * @param userId user's identificator + * @param userId user's identificator */ public User(String firstName, String lastName, boolean isActive, String userId) { this.firstName = firstName; @@ -63,24 +65,27 @@ public String getUserId() { return userId; } - @Override public boolean equals(Object o) { + @Override + public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } - User user = (User) o; + var user = (User) o; return isActive == user.isActive && Objects.equals(firstName, user.firstName) && Objects - .equals(lastName, user.lastName) && Objects.equals(userId, user.userId); + .equals(lastName, user.lastName) && Objects.equals(userId, user.userId); } - @Override public int hashCode() { + @Override + public int hashCode() { return Objects.hash(firstName, lastName, isActive, userId); } - @Override public String toString() { + @Override + public String toString() { return "User{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' - + ", isActive=" + isActive + ", userId='" + userId + '\'' + '}'; + + ", isActive=" + isActive + ", userId='" + userId + '\'' + '}'; } } diff --git a/converter/src/main/java/com/iluwatar/converter/UserConverter.java b/converter/src/main/java/com/iluwatar/converter/UserConverter.java index 9ef1d03c24de..c2341695e5d9 100644 --- a/converter/src/main/java/com/iluwatar/converter/UserConverter.java +++ b/converter/src/main/java/com/iluwatar/converter/UserConverter.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -28,13 +28,16 @@ */ public class UserConverter extends Converter { - /** - * Constructor. - */ public UserConverter() { - super(userDto -> new User(userDto.getFirstName(), userDto.getLastName(), userDto.isActive(), - userDto.getEmail()), - user -> new UserDto(user.getFirstName(), user.getLastName(), user.isActive(), - user.getUserId())); + super(UserConverter::convertToEntity, UserConverter::convertToDto); + } + + private static UserDto convertToDto(User user) { + return new UserDto(user.getFirstName(), user.getLastName(), user.isActive(), user.getUserId()); } + + private static User convertToEntity(UserDto dto) { + return new User(dto.getFirstName(), dto.getLastName(), dto.isActive(), dto.getEmail()); + } + } diff --git a/converter/src/main/java/com/iluwatar/converter/UserDto.java b/converter/src/main/java/com/iluwatar/converter/UserDto.java index 8eacc0b3be96..e75aaab8cf76 100644 --- a/converter/src/main/java/com/iluwatar/converter/UserDto.java +++ b/converter/src/main/java/com/iluwatar/converter/UserDto.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,11 +23,10 @@ package com.iluwatar.converter; - import java.util.Objects; /** - * User DTO class + * User DTO class. */ public class UserDto { @@ -37,6 +36,8 @@ public class UserDto { private String email; /** + * Constructor. + * * @param firstName user's first name * @param lastName user's last name * @param isActive flag indicating whether the user is active @@ -65,24 +66,27 @@ public String getEmail() { return email; } - @Override public boolean equals(Object o) { + @Override + public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } - UserDto userDto = (UserDto) o; + var userDto = (UserDto) o; return isActive == userDto.isActive && Objects.equals(firstName, userDto.firstName) && Objects - .equals(lastName, userDto.lastName) && Objects.equals(email, userDto.email); + .equals(lastName, userDto.lastName) && Objects.equals(email, userDto.email); } - @Override public int hashCode() { + @Override + public int hashCode() { return Objects.hash(firstName, lastName, isActive, email); } - @Override public String toString() { + @Override + public String toString() { return "UserDto{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' - + ", isActive=" + isActive + ", email='" + email + '\'' + '}'; + + ", isActive=" + isActive + ", email='" + email + '\'' + '}'; } } diff --git a/converter/src/test/java/com/iluwatar/converter/AppTest.java b/converter/src/test/java/com/iluwatar/converter/AppTest.java index 1617beeb40ea..ed53c686393c 100644 --- a/converter/src/test/java/com/iluwatar/converter/AppTest.java +++ b/converter/src/test/java/com/iluwatar/converter/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.converter; import org.junit.jupiter.api.Test; @@ -31,8 +32,7 @@ public class AppTest { @Test public void testMain() { - String[] args = {}; - App.main(args); + App.main(new String[]{}); } } diff --git a/converter/src/test/java/com/iluwatar/converter/ConverterTest.java b/converter/src/test/java/com/iluwatar/converter/ConverterTest.java index 3c8c4e9fc287..d9e4e418bb98 100644 --- a/converter/src/test/java/com/iluwatar/converter/ConverterTest.java +++ b/converter/src/test/java/com/iluwatar/converter/ConverterTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,16 +20,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.converter; -import com.google.common.collect.Lists; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.ArrayList; import java.util.List; import java.util.Random; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; /** * Tests for {@link Converter} @@ -43,8 +41,8 @@ public class ConverterTest { */ @Test public void testConversionsStartingFromDomain() { - User u1 = new User("Tom", "Hanks", true, "tom@hanks.com"); - User u2 = userConverter.convertFromDto(userConverter.convertFromEntity(u1)); + var u1 = new User("Tom", "Hanks", true, "tom@hanks.com"); + var u2 = userConverter.convertFromDto(userConverter.convertFromEntity(u1)); assertEquals(u1, u2); } @@ -53,36 +51,47 @@ public void testConversionsStartingFromDomain() { */ @Test public void testConversionsStartingFromDto() { - UserDto u1 = new UserDto("Tom", "Hanks", true, "tom@hanks.com"); - UserDto u2 = userConverter.convertFromEntity(userConverter.convertFromDto(u1)); + var u1 = new UserDto("Tom", "Hanks", true, "tom@hanks.com"); + var u2 = userConverter.convertFromEntity(userConverter.convertFromDto(u1)); assertEquals(u1, u2); } /** - * Tests the custom users converter. Thanks to Java8 lambdas, converter can be easily and - * cleanly instantiated allowing various different conversion strategies to be implemented. + * Tests the custom users converter. Thanks to Java8 lambdas, converter can be easily and cleanly + * instantiated allowing various different conversion strategies to be implemented. */ @Test public void testCustomConverter() { - Converter converter = new Converter<>( - userDto -> new User(userDto.getFirstName(), userDto.getLastName(), userDto.isActive(), - String.valueOf(new Random().nextInt())), - user -> new UserDto(user.getFirstName(), user.getLastName(), user.isActive(), - user.getFirstName().toLowerCase() + user.getLastName().toLowerCase() + "@whatever.com")); - User u1 = new User("John", "Doe", false, "12324"); - UserDto userDto = converter.convertFromEntity(u1); + var converter = new Converter( + userDto -> new User( + userDto.getFirstName(), + userDto.getLastName(), + userDto.isActive(), + String.valueOf(new Random().nextInt()) + ), + user -> new UserDto( + user.getFirstName(), + user.getLastName(), + user.isActive(), + user.getFirstName().toLowerCase() + user.getLastName().toLowerCase() + "@whatever.com") + ); + var u1 = new User("John", "Doe", false, "12324"); + var userDto = converter.convertFromEntity(u1); assertEquals("johndoe@whatever.com", userDto.getEmail()); } /** - * Test whether converting a collection of Users to DTO Users and then converting them back to domain - * users returns an equal collection. + * Test whether converting a collection of Users to DTO Users and then converting them back to + * domain users returns an equal collection. */ @Test public void testCollectionConversion() { - ArrayList users = Lists.newArrayList(new User("Camile", "Tough", false, "124sad"), - new User("Marti", "Luther", true, "42309fd"), new User("Kate", "Smith", true, "if0243")); - List fromDtos = userConverter.createFromDtos(userConverter.createFromEntities(users)); + var users = List.of( + new User("Camile", "Tough", false, "124sad"), + new User("Marti", "Luther", true, "42309fd"), + new User("Kate", "Smith", true, "if0243") + ); + var fromDtos = userConverter.createFromDtos(userConverter.createFromEntities(users)); assertEquals(users, fromDtos); } } diff --git a/cqrs/README.md b/cqrs/README.md index 111c1ccd45ec..431ae6279d14 100644 --- a/cqrs/README.md +++ b/cqrs/README.md @@ -5,13 +5,14 @@ folder: cqrs permalink: /patterns/cqrs/ categories: Architectural tags: - - Java - - Difficulty-Intermediate + - Performance + - Cloud distributed --- ## Intent CQRS Command Query Responsibility Segregation - Separate the query side from the command side. +## Class diagram ![alt text](./etc/cqrs.png "CQRS") ## Applicability diff --git a/cqrs/etc/cqrs.urm.puml b/cqrs/etc/cqrs.urm.puml new file mode 100644 index 000000000000..2b06980983c5 --- /dev/null +++ b/cqrs/etc/cqrs.urm.puml @@ -0,0 +1,134 @@ +@startuml +package com.iluwatar.cqrs.util { + class HibernateUtil { + - LOGGER : Logger {static} + - SESSIONFACTORY : SessionFactory {static} + + HibernateUtil() + - buildSessionFactory() : SessionFactory {static} + + getSessionFactory() : SessionFactory {static} + } +} +package com.iluwatar.cqrs.app { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } +} +package com.iluwatar.cqrs.dto { + class Author { + - email : String + - name : String + - username : String + + Author() + + Author(name : String, email : String, username : String) + + equals(obj : Object) : boolean + + getEmail() : String + + getName() : String + + getUsername() : String + + hashCode() : int + + toString() : String + } + class Book { + - price : double + - title : String + + Book() + + Book(title : String, price : double) + + equals(obj : Object) : boolean + + getPrice() : double + + getTitle() : String + + hashCode() : int + + toString() : String + } +} +package com.iluwatar.cqrs.commandes { + class CommandServiceImpl { + - sessionFactory : SessionFactory + + CommandServiceImpl() + + authorCreated(username : String, name : String, email : String) + + authorEmailUpdated(username : String, email : String) + + authorNameUpdated(username : String, name : String) + + authorUsernameUpdated(oldUsername : String, newUsername : String) + + bookAddedToAuthor(title : String, price : double, username : String) + + bookPriceUpdated(title : String, price : double) + + bookTitleUpdated(oldTitle : String, newTitle : String) + - getAuthorByUsername(username : String) : Author + - getBookByTitle(title : String) : Book + } + interface ICommandService { + + authorCreated(String, String, String) {abstract} + + authorEmailUpdated(String, String) {abstract} + + authorNameUpdated(String, String) {abstract} + + authorUsernameUpdated(String, String) {abstract} + + bookAddedToAuthor(String, double, String) {abstract} + + bookPriceUpdated(String, double) {abstract} + + bookTitleUpdated(String, String) {abstract} + } +} +package com.iluwatar.cqrs.queries { + interface IQueryService { + + getAuthorBooks(String) : List {abstract} + + getAuthorBooksCount(String) : BigInteger {abstract} + + getAuthorByUsername(String) : Author {abstract} + + getAuthorsCount() : BigInteger {abstract} + + getBook(String) : Book {abstract} + } + class QueryServiceImpl { + - sessionFactory : SessionFactory + + QueryServiceImpl() + + getAuthorBooks(username : String) : List + + getAuthorBooksCount(username : String) : BigInteger + + getAuthorByUsername(username : String) : Author + + getAuthorsCount() : BigInteger + + getBook(title : String) : Book + } +} +package com.iluwatar.cqrs.constants { + class AppConstants { + + E_EVANS : String {static} + + J_BLOCH : String {static} + + M_FOWLER : String {static} + + USER_NAME : String {static} + + AppConstants() + } +} +package com.iluwatar.cqrs.domain.model { + class Author { + - email : String + - id : long + - name : String + - username : String + # Author() + + Author(username : String, name : String, email : String) + + getEmail() : String + + getId() : long + + getName() : String + + getUsername() : String + + setEmail(email : String) + + setId(id : long) + + setName(name : String) + + setUsername(username : String) + + toString() : String + } + class Book { + - author : Author + - id : long + - price : double + - title : String + # Book() + + Book(title : String, price : double, author : Author) + + getAuthor() : Author + + getId() : long + + getPrice() : double + + getTitle() : String + + setAuthor(author : Author) + + setId(id : long) + + setPrice(price : double) + + setTitle(title : String) + + toString() : String + } +} +Book --> "-author" Author +CommandServiceImpl ..|> ICommandService +QueryServiceImpl ..|> IQueryService +@enduml \ No newline at end of file diff --git a/cqrs/pom.xml b/cqrs/pom.xml index 6480f25e9b0f..b3a0303e634e 100644 --- a/cqrs/pom.xml +++ b/cqrs/pom.xml @@ -1,19 +1,28 @@ - + @@ -21,15 +30,10 @@ com.iluwatar java-design-patterns - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT cqrs - - org.junit.jupiter - junit-jupiter-api - test - org.junit.jupiter junit-jupiter-engine @@ -43,5 +47,36 @@ org.hibernate hibernate-core + + com.sun.xml.bind + jaxb-impl + 2.1.17 + test + + + javax.xml.bind + jaxb-api + test + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.cqrs.app.App + + + + + + + + + diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/app/App.java b/cqrs/src/main/java/com/iluwatar/cqrs/app/App.java index a943a7f88891..436c2d1306db 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/app/App.java +++ b/cqrs/src/main/java/com/iluwatar/cqrs/app/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,74 +20,68 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.cqrs.app; -import java.math.BigInteger; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +package com.iluwatar.cqrs.app; import com.iluwatar.cqrs.commandes.CommandServiceImpl; -import com.iluwatar.cqrs.commandes.ICommandService; -import com.iluwatar.cqrs.dto.Author; -import com.iluwatar.cqrs.dto.Book; -import com.iluwatar.cqrs.queries.IQueryService; +import com.iluwatar.cqrs.constants.AppConstants; import com.iluwatar.cqrs.queries.QueryServiceImpl; import com.iluwatar.cqrs.util.HibernateUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * CQRS : Command Query Responsibility Segregation. A pattern used to separate query services from commands or writes - * services. The pattern is very simple but it has many consequences. For example, it can be used to tackle down a - * complex domain, or to use other architectures that were hard to implement with the classical way. - * - * This implementation is an example of managing books and authors in a library. The persistence of books and authors is - * done according to the CQRS architecture. A command side that deals with a data model to persist(insert,update,delete) - * objects to a database. And a query side that uses native queries to get data from the database and return objects as - * DTOs (Data transfer Objects). + * CQRS : Command Query Responsibility Segregation. A pattern used to separate query services from + * commands or writes services. The pattern is very simple but it has many consequences. For + * example, it can be used to tackle down a complex domain, or to use other architectures that were + * hard to implement with the classical way. * + *

This implementation is an example of managing books and authors in a library. The persistence + * of books and authors is done according to the CQRS architecture. A command side that deals with a + * data model to persist(insert,update,delete) objects to a database. And a query side that uses + * native queries to get data from the database and return objects as DTOs (Data transfer Objects). */ public class App { private static final Logger LOGGER = LoggerFactory.getLogger(App.class); /** - * Program entry point - * - * @param args - * command line args + * Program entry point. + * + * @param args command line args */ public static void main(String[] args) { - ICommandService commands = new CommandServiceImpl(); + var commands = new CommandServiceImpl(); // Create Authors and Books using CommandService - commands.authorCreated("eEvans", "Eric Evans", "eEvans@email.com"); - commands.authorCreated("jBloch", "Joshua Bloch", "jBloch@email.com"); - commands.authorCreated("mFowler", "Martin Fowler", "mFowler@email.com"); + commands.authorCreated(AppConstants.E_EVANS, "Eric Evans", "evans@email.com"); + commands.authorCreated(AppConstants.J_BLOCH, "Joshua Bloch", "jBloch@email.com"); + commands.authorCreated(AppConstants.M_FOWLER, "Martin Fowler", "mFowler@email.com"); - commands.bookAddedToAuthor("Domain-Driven Design", 60.08, "eEvans"); - commands.bookAddedToAuthor("Effective Java", 40.54, "jBloch"); - commands.bookAddedToAuthor("Java Puzzlers", 39.99, "jBloch"); - commands.bookAddedToAuthor("Java Concurrency in Practice", 29.40, "jBloch"); - commands.bookAddedToAuthor("Patterns of Enterprise Application Architecture", 54.01, "mFowler"); - commands.bookAddedToAuthor("Domain Specific Languages", 48.89, "mFowler"); - commands.authorNameUpdated("eEvans", "Eric J. Evans"); + commands.bookAddedToAuthor("Domain-Driven Design", 60.08, AppConstants.E_EVANS); + commands.bookAddedToAuthor("Effective Java", 40.54, AppConstants.J_BLOCH); + commands.bookAddedToAuthor("Java Puzzlers", 39.99, AppConstants.J_BLOCH); + commands.bookAddedToAuthor("Java Concurrency in Practice", 29.40, AppConstants.J_BLOCH); + commands.bookAddedToAuthor("Patterns of Enterprise" + + " Application Architecture", 54.01, AppConstants.M_FOWLER); + commands.bookAddedToAuthor("Domain Specific Languages", 48.89, AppConstants.M_FOWLER); + commands.authorNameUpdated(AppConstants.E_EVANS, "Eric J. Evans"); - IQueryService queries = new QueryServiceImpl(); + var queries = new QueryServiceImpl(); // Query the database using QueryService - Author nullAuthor = queries.getAuthorByUsername("username"); - Author eEvans = queries.getAuthorByUsername("eEvans"); - BigInteger jBlochBooksCount = queries.getAuthorBooksCount("jBloch"); - BigInteger authorsCount = queries.getAuthorsCount(); - Book dddBook = queries.getBook("Domain-Driven Design"); - List jBlochBooks = queries.getAuthorBooks("jBloch"); + var nullAuthor = queries.getAuthorByUsername("username"); + var evans = queries.getAuthorByUsername(AppConstants.E_EVANS); + var blochBooksCount = queries.getAuthorBooksCount(AppConstants.J_BLOCH); + var authorsCount = queries.getAuthorsCount(); + var dddBook = queries.getBook("Domain-Driven Design"); + var blochBooks = queries.getAuthorBooks(AppConstants.J_BLOCH); LOGGER.info("Author username : {}", nullAuthor); - LOGGER.info("Author eEvans : {}", eEvans); - LOGGER.info("jBloch number of books : {}", jBlochBooksCount); + LOGGER.info("Author evans : {}", evans); + LOGGER.info("jBloch number of books : {}", blochBooksCount); LOGGER.info("Number of authors : {}", authorsCount); LOGGER.info("DDD book : {}", dddBook); - LOGGER.info("jBloch books : {}", jBlochBooks); + LOGGER.info("jBloch books : {}", blochBooks); HibernateUtil.getSessionFactory().close(); } diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java b/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java index a15f8a45716d..ba08811e7fc0 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java +++ b/cqrs/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,28 +20,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.cqrs.commandes; -import org.hibernate.Query; -import org.hibernate.Session; -import org.hibernate.SessionFactory; +package com.iluwatar.cqrs.commandes; import com.iluwatar.cqrs.domain.model.Author; import com.iluwatar.cqrs.domain.model.Book; import com.iluwatar.cqrs.util.HibernateUtil; +import org.hibernate.SessionFactory; /** - * This class is an implementation of {@link ICommandService} interface. It uses Hibernate as an api for persistence. - * + * This class is an implementation of {@link ICommandService} interface. It uses Hibernate as an api + * for persistence. */ public class CommandServiceImpl implements ICommandService { private SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); private Author getAuthorByUsername(String username) { - Author author = null; - try (Session session = sessionFactory.openSession()) { - Query query = session.createQuery("from Author where username=:username"); + Author author; + try (var session = sessionFactory.openSession()) { + var query = session.createQuery("from Author where username=:username"); query.setParameter("username", username); author = (Author) query.uniqueResult(); } @@ -53,9 +51,9 @@ private Author getAuthorByUsername(String username) { } private Book getBookByTitle(String title) { - Book book = null; - try (Session session = sessionFactory.openSession()) { - Query query = session.createQuery("from Book where title=:title"); + Book book; + try (var session = sessionFactory.openSession()) { + var query = session.createQuery("from Book where title=:title"); query.setParameter("title", title); book = (Book) query.uniqueResult(); } @@ -68,8 +66,8 @@ private Book getBookByTitle(String title) { @Override public void authorCreated(String username, String name, String email) { - Author author = new Author(username, name, email); - try (Session session = sessionFactory.openSession()) { + var author = new Author(username, name, email); + try (var session = sessionFactory.openSession()) { session.beginTransaction(); session.save(author); session.getTransaction().commit(); @@ -78,9 +76,9 @@ public void authorCreated(String username, String name, String email) { @Override public void bookAddedToAuthor(String title, double price, String username) { - Author author = getAuthorByUsername(username); - Book book = new Book(title, price, author); - try (Session session = sessionFactory.openSession()) { + var author = getAuthorByUsername(username); + var book = new Book(title, price, author); + try (var session = sessionFactory.openSession()) { session.beginTransaction(); session.save(book); session.getTransaction().commit(); @@ -89,9 +87,9 @@ public void bookAddedToAuthor(String title, double price, String username) { @Override public void authorNameUpdated(String username, String name) { - Author author = getAuthorByUsername(username); + var author = getAuthorByUsername(username); author.setName(name); - try (Session session = sessionFactory.openSession()) { + try (var session = sessionFactory.openSession()) { session.beginTransaction(); session.update(author); session.getTransaction().commit(); @@ -100,9 +98,9 @@ public void authorNameUpdated(String username, String name) { @Override public void authorUsernameUpdated(String oldUsername, String newUsername) { - Author author = getAuthorByUsername(oldUsername); + var author = getAuthorByUsername(oldUsername); author.setUsername(newUsername); - try (Session session = sessionFactory.openSession()) { + try (var session = sessionFactory.openSession()) { session.beginTransaction(); session.update(author); session.getTransaction().commit(); @@ -111,9 +109,9 @@ public void authorUsernameUpdated(String oldUsername, String newUsername) { @Override public void authorEmailUpdated(String username, String email) { - Author author = getAuthorByUsername(username); + var author = getAuthorByUsername(username); author.setEmail(email); - try (Session session = sessionFactory.openSession()) { + try (var session = sessionFactory.openSession()) { session.beginTransaction(); session.update(author); session.getTransaction().commit(); @@ -122,9 +120,9 @@ public void authorEmailUpdated(String username, String email) { @Override public void bookTitleUpdated(String oldTitle, String newTitle) { - Book book = getBookByTitle(oldTitle); + var book = getBookByTitle(oldTitle); book.setTitle(newTitle); - try (Session session = sessionFactory.openSession()) { + try (var session = sessionFactory.openSession()) { session.beginTransaction(); session.update(book); session.getTransaction().commit(); @@ -133,9 +131,9 @@ public void bookTitleUpdated(String oldTitle, String newTitle) { @Override public void bookPriceUpdated(String title, double price) { - Book book = getBookByTitle(title); + var book = getBookByTitle(title); book.setPrice(price); - try (Session session = sessionFactory.openSession()) { + try (var session = sessionFactory.openSession()) { session.beginTransaction(); session.update(book); session.getTransaction().commit(); diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/commandes/ICommandService.java b/cqrs/src/main/java/com/iluwatar/cqrs/commandes/ICommandService.java index 1da3f6c42392..b81d24c7c580 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/commandes/ICommandService.java +++ b/cqrs/src/main/java/com/iluwatar/cqrs/commandes/ICommandService.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,11 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.cqrs.commandes; /** - * This interface represents the commands of the CQRS pattern - * + * This interface represents the commands of the CQRS pattern. */ public interface ICommandService { diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/constants/AppConstants.java b/cqrs/src/main/java/com/iluwatar/cqrs/constants/AppConstants.java new file mode 100644 index 000000000000..0a8971729ef5 --- /dev/null +++ b/cqrs/src/main/java/com/iluwatar/cqrs/constants/AppConstants.java @@ -0,0 +1,36 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.cqrs.constants; + +/** + * Class to define the constants. + */ +public class AppConstants { + + public static final String E_EVANS = "eEvans"; + public static final String J_BLOCH = "jBloch"; + public static final String M_FOWLER = "mFowler"; + public static final String USER_NAME = "username"; + +} diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Author.java b/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Author.java index 9825de9f7b25..9f2b73e486fd 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Author.java +++ b/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Author.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.cqrs.domain.model; import javax.persistence.Entity; @@ -29,7 +30,6 @@ /** * This is an Author entity. It is used by Hibernate for persistence. - * */ @Entity public class Author { @@ -41,13 +41,11 @@ public class Author { private String email; /** - * - * @param username - * username of the author - * @param name - * name of the author - * @param email - * email of the author + * Constructor. + * + * @param username username of the author + * @param name name of the author + * @param email email of the author */ public Author(String username, String name, String email) { this.username = username; diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Book.java b/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Book.java index 8a11fcdd4166..8b87bdbf4301 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Book.java +++ b/cqrs/src/main/java/com/iluwatar/cqrs/domain/model/Book.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.cqrs.domain.model; import javax.persistence.Entity; @@ -29,8 +30,8 @@ import javax.persistence.ManyToOne; /** - * This is a Book entity. It is used by Hibernate for persistence. Many books can be written by one {@link Author} - * + * This is a Book entity. It is used by Hibernate for persistence. Many books can be written by one + * {@link Author} */ @Entity public class Book { @@ -43,13 +44,11 @@ public class Book { private Author author; /** + * Constructor. * - * @param title - * title of the book - * @param price - * price of the book - * @param author - * author of the book + * @param title title of the book + * @param price price of the book + * @param author author of the book */ public Book(String title, double price, Author author) { this.title = title; diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/dto/Author.java b/cqrs/src/main/java/com/iluwatar/cqrs/dto/Author.java index c5473354dd59..f3fa3f03b6e3 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/dto/Author.java +++ b/cqrs/src/main/java/com/iluwatar/cqrs/dto/Author.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,14 +20,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.cqrs.dto; import java.util.Objects; /** - * - * This is a DTO (Data Transfer Object) author, contains only useful information to be returned - * + * This is a DTO (Data Transfer Object) author, contains only useful information to be returned. */ public class Author { @@ -36,13 +35,11 @@ public class Author { private String username; /** - * - * @param name - * name of the author - * @param email - * email of the author - * @param username - * username of the author + * Constructor. + * + * @param name name of the author + * @param email email of the author + * @param username username of the author */ public Author(String name, String email, String username) { this.name = name; @@ -83,8 +80,9 @@ public boolean equals(Object obj) { if (!(obj instanceof Author)) { return false; } - Author other = (Author) obj; - return username.equals(other.getUsername()) && email.equals(other.getEmail()) && name.equals(other.getName()); + var other = (Author) obj; + return username.equals(other.getUsername()) && email.equals(other.getEmail()) && name + .equals(other.getName()); } diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/dto/Book.java b/cqrs/src/main/java/com/iluwatar/cqrs/dto/Book.java index f121a2ca7c2c..afddd5f28d8d 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/dto/Book.java +++ b/cqrs/src/main/java/com/iluwatar/cqrs/dto/Book.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,14 +20,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.cqrs.dto; import java.util.Objects; /** - * - * This is a DTO (Data Transfer Object) book, contains only useful information to be returned - * + * This is a DTO (Data Transfer Object) book, contains only useful information to be returned. */ public class Book { @@ -35,11 +34,10 @@ public class Book { private double price; /** - * - * @param title - * title of the book - * @param price - * price of the book + * Constructor. + * + * @param title title of the book + * @param price price of the book */ public Book(String title, double price) { this.title = title; @@ -75,7 +73,7 @@ public boolean equals(Object obj) { if (!(obj instanceof Book)) { return false; } - Book book = (Book) obj; + var book = (Book) obj; return title.equals(book.getTitle()) && price == book.getPrice(); } diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/queries/IQueryService.java b/cqrs/src/main/java/com/iluwatar/cqrs/queries/IQueryService.java index 9c0252b0aeab..e67371bc723a 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/queries/IQueryService.java +++ b/cqrs/src/main/java/com/iluwatar/cqrs/queries/IQueryService.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,18 +20,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.cqrs.queries; -import java.math.BigInteger; -import java.util.List; +package com.iluwatar.cqrs.queries; import com.iluwatar.cqrs.dto.Author; import com.iluwatar.cqrs.dto.Book; +import java.math.BigInteger; +import java.util.List; /** - * - * This interface represents the query methods of the CQRS pattern - * + * This interface represents the query methods of the CQRS pattern. */ public interface IQueryService { diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java b/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java index dc44d0f1b7c8..9b008402edc8 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java +++ b/cqrs/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,24 +20,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.cqrs.queries; +import com.iluwatar.cqrs.constants.AppConstants; +import com.iluwatar.cqrs.dto.Author; +import com.iluwatar.cqrs.dto.Book; +import com.iluwatar.cqrs.util.HibernateUtil; import java.math.BigInteger; import java.util.List; - -import org.hibernate.SQLQuery; -import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.transform.Transformers; -import com.iluwatar.cqrs.dto.Author; -import com.iluwatar.cqrs.dto.Book; -import com.iluwatar.cqrs.util.HibernateUtil; - /** - * This class is an implementation of {@link IQueryService}. It uses Hibernate native queries to return DTOs from the - * database. - * + * This class is an implementation of {@link IQueryService}. It uses Hibernate native queries to + * return DTOs from the database. */ public class QueryServiceImpl implements IQueryService { @@ -45,36 +42,38 @@ public class QueryServiceImpl implements IQueryService { @Override public Author getAuthorByUsername(String username) { - Author authorDTo = null; - try (Session session = sessionFactory.openSession()) { - SQLQuery sqlQuery = session - .createSQLQuery("SELECT a.username as \"username\", a.name as \"name\", a.email as \"email\"" - + "FROM Author a where a.username=:username"); - sqlQuery.setParameter("username", username); - authorDTo = (Author) sqlQuery.setResultTransformer(Transformers.aliasToBean(Author.class)).uniqueResult(); + Author authorDTo; + try (var session = sessionFactory.openSession()) { + var sqlQuery = session.createSQLQuery("SELECT a.username as \"username\"," + + " a.name as \"name\", a.email as \"email\"" + + "FROM Author a where a.username=:username"); + sqlQuery.setParameter(AppConstants.USER_NAME, username); + authorDTo = (Author) sqlQuery.setResultTransformer(Transformers.aliasToBean(Author.class)) + .uniqueResult(); } return authorDTo; } @Override public Book getBook(String title) { - Book bookDTo = null; - try (Session session = sessionFactory.openSession()) { - SQLQuery sqlQuery = session - .createSQLQuery("SELECT b.title as \"title\", b.price as \"price\"" + " FROM Book b where b.title=:title"); + Book bookDTo; + try (var session = sessionFactory.openSession()) { + var sqlQuery = session.createSQLQuery("SELECT b.title as \"title\"," + + " b.price as \"price\"" + " FROM Book b where b.title=:title"); sqlQuery.setParameter("title", title); - bookDTo = (Book) sqlQuery.setResultTransformer(Transformers.aliasToBean(Book.class)).uniqueResult(); + bookDTo = + (Book) sqlQuery.setResultTransformer(Transformers.aliasToBean(Book.class)).uniqueResult(); } return bookDTo; } @Override public List getAuthorBooks(String username) { - List bookDTos = null; - try (Session session = sessionFactory.openSession()) { - SQLQuery sqlQuery = session.createSQLQuery("SELECT b.title as \"title\", b.price as \"price\"" + List bookDTos; + try (var session = sessionFactory.openSession()) { + var sqlQuery = session.createSQLQuery("SELECT b.title as \"title\", b.price as \"price\"" + " FROM Author a , Book b where b.author_id = a.id and a.username=:username"); - sqlQuery.setParameter("username", username); + sqlQuery.setParameter(AppConstants.USER_NAME, username); bookDTos = sqlQuery.setResultTransformer(Transformers.aliasToBean(Book.class)).list(); } return bookDTos; @@ -82,11 +81,12 @@ public List getAuthorBooks(String username) { @Override public BigInteger getAuthorBooksCount(String username) { - BigInteger bookcount = null; - try (Session session = sessionFactory.openSession()) { - SQLQuery sqlQuery = session.createSQLQuery( - "SELECT count(b.title)" + " FROM Book b, Author a where b.author_id = a.id and a.username=:username"); - sqlQuery.setParameter("username", username); + BigInteger bookcount; + try (var session = sessionFactory.openSession()) { + var sqlQuery = session.createSQLQuery( + "SELECT count(b.title)" + " FROM Book b, Author a" + + " where b.author_id = a.id and a.username=:username"); + sqlQuery.setParameter(AppConstants.USER_NAME, username); bookcount = (BigInteger) sqlQuery.uniqueResult(); } return bookcount; @@ -94,9 +94,9 @@ public BigInteger getAuthorBooksCount(String username) { @Override public BigInteger getAuthorsCount() { - BigInteger authorcount = null; - try (Session session = sessionFactory.openSession()) { - SQLQuery sqlQuery = session.createSQLQuery("SELECT count(id) from Author"); + BigInteger authorcount; + try (var session = sessionFactory.openSession()) { + var sqlQuery = session.createSQLQuery("SELECT count(id) from Author"); authorcount = (BigInteger) sqlQuery.uniqueResult(); } return authorcount; diff --git a/cqrs/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java b/cqrs/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java index a5b59e20dc27..31610e5c1b01 100644 --- a/cqrs/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java +++ b/cqrs/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,18 +20,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.cqrs.util; import org.hibernate.SessionFactory; import org.hibernate.boot.MetadataSources; -import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This class simply returns one instance of {@link SessionFactory} initialized when the application is started - * + * This class simply returns one instance of {@link SessionFactory} initialized when the application + * is started. */ public class HibernateUtil { @@ -41,7 +41,7 @@ public class HibernateUtil { private static SessionFactory buildSessionFactory() { // configures settings from hibernate.cfg.xml - final StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build(); + final var registry = new StandardServiceRegistryBuilder().configure().build(); try { return new MetadataSources(registry).buildMetadata().buildSessionFactory(); } catch (Exception ex) { diff --git a/cqrs/src/main/resources/hibernate.cfg.xml b/cqrs/src/main/resources/hibernate.cfg.xml index 4ea1421667e1..019cd3917c84 100644 --- a/cqrs/src/main/resources/hibernate.cfg.xml +++ b/cqrs/src/main/resources/hibernate.cfg.xml @@ -2,7 +2,7 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 com.iluwatar java-design-patterns - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT dao - - org.junit.jupiter - junit-jupiter-api - test - org.junit.jupiter junit-jupiter-engine test - log4j - log4j + com.h2database + h2 - com.h2database - h2 - - - org.mockito - mockito-core + org.mockito + mockito-core - - - - - - - src/main/resources - - - src/main/resources - - log4j.xml - - .. - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.6 - - - log4j.xml - - - - true - - - + maven-assembly-plugin + + + + + + com.iluwatar.dao.App + + + + + - diff --git a/dao/src/main/java/com/iluwatar/dao/App.java b/dao/src/main/java/com/iluwatar/dao/App.java index d2fa945b617d..de9c7b7c1c10 100644 --- a/dao/src/main/java/com/iluwatar/dao/App.java +++ b/dao/src/main/java/com/iluwatar/dao/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,17 +23,12 @@ package com.iluwatar.dao; -import java.sql.Connection; import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; import java.util.List; -import java.util.stream.Stream; - import javax.sql.DataSource; - -import org.apache.log4j.Logger; import org.h2.jdbcx.JdbcDataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Data Access Object (DAO) is an object that provides an abstract interface to some type of @@ -43,93 +38,88 @@ * application needs, in terms of domain-specific objects and data types (the public interface of * the DAO), from how these needs can be satisfied with a specific DBMS. * - *

With the DAO pattern, we can use various method calls to retrieve/add/delete/update data - * without directly interacting with the data source. The below example demonstrates basic CRUD + *

With the DAO pattern, we can use various method calls to retrieve/add/delete/update data + * without directly interacting with the data source. The below example demonstrates basic CRUD * operations: select, add, update, and delete. - * - * */ public class App { private static final String DB_URL = "jdbc:h2:~/dao"; - private static Logger log = Logger.getLogger(App.class); - + private static Logger log = LoggerFactory.getLogger(App.class); + private static final String ALL_CUSTOMERS = "customerDao.getAllCustomers(): "; + /** * Program entry point. - * + * * @param args command line args. - * @throws Exception if any error occurs. + * @throws Exception if any error occurs. */ public static void main(final String[] args) throws Exception { - final CustomerDao inMemoryDao = new InMemoryCustomerDao(); + final var inMemoryDao = new InMemoryCustomerDao(); performOperationsUsing(inMemoryDao); - - final DataSource dataSource = createDataSource(); + + final var dataSource = createDataSource(); createSchema(dataSource); - final CustomerDao dbDao = new DbCustomerDao(dataSource); + final var dbDao = new DbCustomerDao(dataSource); performOperationsUsing(dbDao); deleteSchema(dataSource); } private static void deleteSchema(DataSource dataSource) throws SQLException { - try (Connection connection = dataSource.getConnection(); - Statement statement = connection.createStatement()) { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL); } } private static void createSchema(DataSource dataSource) throws SQLException { - try (Connection connection = dataSource.getConnection(); - Statement statement = connection.createStatement()) { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL); } } private static DataSource createDataSource() { - JdbcDataSource dataSource = new JdbcDataSource(); + var dataSource = new JdbcDataSource(); dataSource.setURL(DB_URL); return dataSource; } private static void performOperationsUsing(final CustomerDao customerDao) throws Exception { addCustomers(customerDao); - log.info("customerDao.getAllCustomers(): "); - try (Stream customerStream = customerDao.getAll()) { - customerStream.forEach((customer) -> log.info(customer)); + log.info(ALL_CUSTOMERS); + try (var customerStream = customerDao.getAll()) { + customerStream.forEach((customer) -> log.info(customer.toString())); } log.info("customerDao.getCustomerById(2): " + customerDao.getById(2)); - final Customer customer = new Customer(4, "Dan", "Danson"); + final var customer = new Customer(4, "Dan", "Danson"); customerDao.add(customer); - log.info("customerDao.getAllCustomers(): " + customerDao.getAll()); + log.info(ALL_CUSTOMERS + customerDao.getAll()); customer.setFirstName("Daniel"); customer.setLastName("Danielson"); customerDao.update(customer); - log.info("customerDao.getAllCustomers(): "); - try (Stream customerStream = customerDao.getAll()) { - customerStream.forEach((cust) -> log.info(cust)); + log.info(ALL_CUSTOMERS); + try (var customerStream = customerDao.getAll()) { + customerStream.forEach((cust) -> log.info(cust.toString())); } customerDao.delete(customer); - log.info("customerDao.getAllCustomers(): " + customerDao.getAll()); + log.info(ALL_CUSTOMERS + customerDao.getAll()); } private static void addCustomers(CustomerDao customerDao) throws Exception { - for (Customer customer : generateSampleCustomers()) { + for (var customer : generateSampleCustomers()) { customerDao.add(customer); } } /** * Generate customers. - * + * * @return list of customers. */ public static List generateSampleCustomers() { - final Customer customer1 = new Customer(1, "Adam", "Adamson"); - final Customer customer2 = new Customer(2, "Bob", "Bobson"); - final Customer customer3 = new Customer(3, "Carl", "Carlson"); - final List customers = new ArrayList<>(); - customers.add(customer1); - customers.add(customer2); - customers.add(customer3); - return customers; + final var customer1 = new Customer(1, "Adam", "Adamson"); + final var customer2 = new Customer(2, "Bob", "Bobson"); + final var customer3 = new Customer(3, "Carl", "Carlson"); + return List.of(customer1, customer2, customer3); } } diff --git a/dao/src/main/java/com/iluwatar/dao/CustomException.java b/dao/src/main/java/com/iluwatar/dao/CustomException.java index fd291b605392..3da6ab20e7a1 100644 --- a/dao/src/main/java/com/iluwatar/dao/CustomException.java +++ b/dao/src/main/java/com/iluwatar/dao/CustomException.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,23 +20,23 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.dao; /** - * - * Custom exception - * + * Custom exception. */ public class CustomException extends Exception { private static final long serialVersionUID = 1L; - public CustomException() {} + public CustomException() { + } public CustomException(String message) { super(message); } - + public CustomException(String message, Throwable cause) { super(message, cause); } diff --git a/dao/src/main/java/com/iluwatar/dao/Customer.java b/dao/src/main/java/com/iluwatar/dao/Customer.java index a133bf2b0ded..43649989b53d 100644 --- a/dao/src/main/java/com/iluwatar/dao/Customer.java +++ b/dao/src/main/java/com/iluwatar/dao/Customer.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,7 +25,6 @@ /** * A customer POJO that represents the data that will be read from the data source. - * */ public class Customer { @@ -74,11 +73,11 @@ public String toString() { @Override public boolean equals(final Object that) { - boolean isEqual = false; + var isEqual = false; if (this == that) { isEqual = true; } else if (that != null && getClass() == that.getClass()) { - final Customer customer = (Customer) that; + final var customer = (Customer) that; if (getId() == customer.getId()) { isEqual = true; } diff --git a/dao/src/main/java/com/iluwatar/dao/CustomerDao.java b/dao/src/main/java/com/iluwatar/dao/CustomerDao.java index 6156bb8d7d95..f5dc573ba008 100644 --- a/dao/src/main/java/com/iluwatar/dao/CustomerDao.java +++ b/dao/src/main/java/com/iluwatar/dao/CustomerDao.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,37 +28,43 @@ /** * In an application the Data Access Object (DAO) is a part of Data access layer. It is an object - * that provides an interface to some type of persistence mechanism. By mapping application calls - * to the persistence layer, DAO provides some specific data operations without exposing details - * of the database. This isolation supports the Single responsibility principle. It separates what - * data accesses the application needs, in terms of domain-specific objects and data types - * (the public interface of the DAO), from how these needs can be satisfied with a specific DBMS, - * database schema, etc. - * - *

Any change in the way data is stored and retrieved will not change the client code as the + * that provides an interface to some type of persistence mechanism. By mapping application calls to + * the persistence layer, DAO provides some specific data operations without exposing details of the + * database. This isolation supports the Single responsibility principle. It separates what data + * accesses the application needs, in terms of domain-specific objects and data types (the public + * interface of the DAO), from how these needs can be satisfied with a specific DBMS, database + * schema, etc. + * + *

Any change in the way data is stored and retrieved will not change the client code as the * client will be using interface and need not worry about exact source. - * + * * @see InMemoryCustomerDao * @see DbCustomerDao */ public interface CustomerDao { /** - * @return all the customers as a stream. The stream may be lazily or eagerly evaluated based - * on the implementation. The stream must be closed after use. + * Get all customers. + * + * @return all the customers as a stream. The stream may be lazily or eagerly evaluated based on + * the implementation. The stream must be closed after use. * @throws Exception if any error occurs. */ Stream getAll() throws Exception; - + /** + * Get customer as Optional by id. + * * @param id unique identifier of the customer. - * @return an optional with customer if a customer with unique identifier id - * exists, empty optional otherwise. + * @return an optional with customer if a customer with unique identifier id exists, + * empty optional otherwise. * @throws Exception if any error occurs. */ Optional getById(int id) throws Exception; /** + * Add a customer. + * * @param customer the customer to be added. * @return true if customer is successfully added, false if customer already exists. * @throws Exception if any error occurs. @@ -66,6 +72,8 @@ public interface CustomerDao { boolean add(Customer customer) throws Exception; /** + * Update a customer. + * * @param customer the customer to be updated. * @return true if customer exists and is successfully updated, false otherwise. * @throws Exception if any error occurs. @@ -73,6 +81,8 @@ public interface CustomerDao { boolean update(Customer customer) throws Exception; /** + * Delete a customer. + * * @param customer the customer to be deleted. * @return true if customer exists and is successfully deleted, false otherwise. * @throws Exception if any error occurs. diff --git a/dao/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java b/dao/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java index cce967dc8939..633a65860ab7 100644 --- a/dao/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java +++ b/dao/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,17 +20,20 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.dao; /** - * Customer Schema SQL Class + * Customer Schema SQL Class. */ public final class CustomerSchemaSql { - private CustomerSchemaSql() {} + private CustomerSchemaSql() { + } - public static final String CREATE_SCHEMA_SQL = "CREATE TABLE CUSTOMERS (ID NUMBER, FNAME VARCHAR(100), " - + "LNAME VARCHAR(100))"; + public static final String CREATE_SCHEMA_SQL = + "CREATE TABLE CUSTOMERS (ID NUMBER, FNAME VARCHAR(100), " + + "LNAME VARCHAR(100))"; public static final String DELETE_SCHEMA_SQL = "DROP TABLE CUSTOMERS"; diff --git a/dao/src/main/java/com/iluwatar/dao/DbCustomerDao.java b/dao/src/main/java/com/iluwatar/dao/DbCustomerDao.java index fa2e411dab56..15f20eb43b30 100644 --- a/dao/src/main/java/com/iluwatar/dao/DbCustomerDao.java +++ b/dao/src/main/java/com/iluwatar/dao/DbCustomerDao.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -33,25 +33,23 @@ import java.util.function.Consumer; import java.util.stream.Stream; import java.util.stream.StreamSupport; - import javax.sql.DataSource; - -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * An implementation of {@link CustomerDao} that persists customers in RDBMS. - * */ public class DbCustomerDao implements CustomerDao { - private static final Logger LOGGER = Logger.getLogger(DbCustomerDao.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DbCustomerDao.class); private final DataSource dataSource; /** - * Creates an instance of {@link DbCustomerDao} which uses provided dataSource - * to store and retrieve customer information. - * + * Creates an instance of {@link DbCustomerDao} which uses provided dataSource to + * store and retrieve customer information. + * * @param dataSource a non-null dataSource. */ public DbCustomerDao(DataSource dataSource) { @@ -59,19 +57,19 @@ public DbCustomerDao(DataSource dataSource) { } /** - * @return a lazily populated stream of customers. Note the stream returned must be closed to - * free all the acquired resources. The stream keeps an open connection to the database till - * it is complete or is closed manually. + * Get all customers as Java Stream. + * + * @return a lazily populated stream of customers. Note the stream returned must be closed to free + * all the acquired resources. The stream keeps an open connection to the database till it is + * complete or is closed manually. */ @Override public Stream getAll() throws Exception { - - Connection connection; try { - connection = getConnection(); - PreparedStatement statement = connection.prepareStatement("SELECT * FROM CUSTOMERS"); // NOSONAR - ResultSet resultSet = statement.executeQuery(); // NOSONAR - return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, + var connection = getConnection(); + var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS"); + var resultSet = statement.executeQuery(); // NOSONAR + return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { @Override @@ -107,8 +105,8 @@ private void mutedClose(Connection connection, PreparedStatement statement, Resu } private Customer createCustomer(ResultSet resultSet) throws SQLException { - return new Customer(resultSet.getInt("ID"), - resultSet.getString("FNAME"), + return new Customer(resultSet.getInt("ID"), + resultSet.getString("FNAME"), resultSet.getString("LNAME")); } @@ -120,9 +118,8 @@ public Optional getById(int id) throws Exception { ResultSet resultSet = null; - try (Connection connection = getConnection(); - PreparedStatement statement = - connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) { + try (var connection = getConnection(); + var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) { statement.setInt(1, id); resultSet = statement.executeQuery(); @@ -149,9 +146,8 @@ public boolean add(Customer customer) throws Exception { return false; } - try (Connection connection = getConnection(); - PreparedStatement statement = - connection.prepareStatement("INSERT INTO CUSTOMERS VALUES (?,?,?)")) { + try (var connection = getConnection(); + var statement = connection.prepareStatement("INSERT INTO CUSTOMERS VALUES (?,?,?)")) { statement.setInt(1, customer.getId()); statement.setString(2, customer.getFirstName()); statement.setString(3, customer.getLastName()); @@ -167,9 +163,10 @@ public boolean add(Customer customer) throws Exception { */ @Override public boolean update(Customer customer) throws Exception { - try (Connection connection = getConnection(); - PreparedStatement statement = - connection.prepareStatement("UPDATE CUSTOMERS SET FNAME = ?, LNAME = ? WHERE ID = ?")) { + try (var connection = getConnection(); + var statement = + connection + .prepareStatement("UPDATE CUSTOMERS SET FNAME = ?, LNAME = ? WHERE ID = ?")) { statement.setString(1, customer.getFirstName()); statement.setString(2, customer.getLastName()); statement.setInt(3, customer.getId()); @@ -184,9 +181,8 @@ public boolean update(Customer customer) throws Exception { */ @Override public boolean delete(Customer customer) throws Exception { - try (Connection connection = getConnection(); - PreparedStatement statement = - connection.prepareStatement("DELETE FROM CUSTOMERS WHERE ID = ?")) { + try (var connection = getConnection(); + var statement = connection.prepareStatement("DELETE FROM CUSTOMERS WHERE ID = ?")) { statement.setInt(1, customer.getId()); return statement.executeUpdate() > 0; } catch (SQLException ex) { diff --git a/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java b/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java index 947da23ca4a0..6dbfa367a9fe 100644 --- a/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java +++ b/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,8 +29,8 @@ import java.util.stream.Stream; /** - * An in memory implementation of {@link CustomerDao}, which stores the customers in JVM memory - * and data is lost when the application exits. + * An in memory implementation of {@link CustomerDao}, which stores the customers in JVM memory and + * data is lost when the application exits. *
* This implementation is useful as temporary database or for testing. */ @@ -56,7 +56,7 @@ public boolean add(final Customer customer) { if (getById(customer.getId()).isPresent()) { return false; } - + idToCustomer.put(customer.getId(), customer); return true; } diff --git a/dao/src/test/java/com/iluwatar/dao/AppTest.java b/dao/src/test/java/com/iluwatar/dao/AppTest.java index 75ef59f18463..edfcf7cd05ba 100644 --- a/dao/src/test/java/com/iluwatar/dao/AppTest.java +++ b/dao/src/test/java/com/iluwatar/dao/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,7 +31,6 @@ public class AppTest { @Test public void test() throws Exception { - String[] args = {}; - App.main(args); + App.main(new String[]{}); } } diff --git a/dao/src/test/java/com/iluwatar/dao/CustomerTest.java b/dao/src/test/java/com/iluwatar/dao/CustomerTest.java index 6bc8689fd87d..479b619a3614 100644 --- a/dao/src/test/java/com/iluwatar/dao/CustomerTest.java +++ b/dao/src/test/java/com/iluwatar/dao/CustomerTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -46,36 +46,36 @@ public void setUp() { @Test public void getAndSetId() { - final int newId = 2; + final var newId = 2; customer.setId(newId); assertEquals(newId, customer.getId()); } @Test public void getAndSetFirstname() { - final String newFirstname = "Bill"; + final var newFirstname = "Bill"; customer.setFirstName(newFirstname); assertEquals(newFirstname, customer.getFirstName()); } @Test public void getAndSetLastname() { - final String newLastname = "Clinton"; + final var newLastname = "Clinton"; customer.setLastName(newLastname); assertEquals(newLastname, customer.getLastName()); } @Test public void notEqualWithDifferentId() { - final int newId = 2; - final Customer otherCustomer = new Customer(newId, FIRSTNAME, LASTNAME); + final var newId = 2; + final var otherCustomer = new Customer(newId, FIRSTNAME, LASTNAME); assertNotEquals(customer, otherCustomer); assertNotEquals(customer.hashCode(), otherCustomer.hashCode()); } @Test public void equalsWithSameObjectValues() { - final Customer otherCustomer = new Customer(ID, FIRSTNAME, LASTNAME); + final var otherCustomer = new Customer(ID, FIRSTNAME, LASTNAME); assertEquals(customer, otherCustomer); assertEquals(customer.hashCode(), otherCustomer.hashCode()); } diff --git a/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java b/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java index 23caf14ec28e..b7a0b9769a2f 100644 --- a/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java +++ b/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,21 +20,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.dao; - -import org.h2.jdbcx.JdbcDataSource; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.stream.Stream; +package com.iluwatar.dao; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -45,6 +32,17 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import javax.sql.DataSource; +import org.h2.jdbcx.JdbcDataSource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + /** * Tests {@link DbCustomerDao}. */ @@ -56,12 +54,13 @@ public class DbCustomerDaoTest { /** * Creates customers schema. + * * @throws SQLException if there is any error while creating schema. */ @BeforeEach public void createSchema() throws SQLException { - try (Connection connection = DriverManager.getConnection(DB_URL); - Statement statement = connection.createStatement()) { + try (var connection = DriverManager.getConnection(DB_URL); + var statement = connection.createStatement()) { statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL); } } @@ -74,14 +73,15 @@ public class ConnectionSuccess { /** * Setup for connection success scenario. + * * @throws Exception if any error occurs. */ @BeforeEach public void setUp() throws Exception { - JdbcDataSource dataSource = new JdbcDataSource(); + var dataSource = new JdbcDataSource(); dataSource.setURL(DB_URL); dao = new DbCustomerDao(dataSource); - boolean result = dao.add(existingCustomer); + var result = dao.add(existingCustomer); assertTrue(result); } @@ -93,12 +93,12 @@ public class NonExistingCustomer { @Test public void addingShouldResultInSuccess() throws Exception { - try (Stream allCustomers = dao.getAll()) { + try (var allCustomers = dao.getAll()) { assumeTrue(allCustomers.count() == 1); } - final Customer nonExistingCustomer = new Customer(2, "Robert", "Englund"); - boolean result = dao.add(nonExistingCustomer); + final var nonExistingCustomer = new Customer(2, "Robert", "Englund"); + var result = dao.add(nonExistingCustomer); assertTrue(result); assertCustomerCountIs(2); @@ -107,8 +107,8 @@ public void addingShouldResultInSuccess() throws Exception { @Test public void deletionShouldBeFailureAndNotAffectExistingCustomers() throws Exception { - final Customer nonExistingCustomer = new Customer(2, "Robert", "Englund"); - boolean result = dao.delete(nonExistingCustomer); + final var nonExistingCustomer = new Customer(2, "Robert", "Englund"); + var result = dao.delete(nonExistingCustomer); assertFalse(result); assertCustomerCountIs(1); @@ -116,11 +116,11 @@ public void deletionShouldBeFailureAndNotAffectExistingCustomers() throws Except @Test public void updationShouldBeFailureAndNotAffectExistingCustomers() throws Exception { - final int nonExistingId = getNonExistingCustomerId(); - final String newFirstname = "Douglas"; - final String newLastname = "MacArthur"; - final Customer customer = new Customer(nonExistingId, newFirstname, newLastname); - boolean result = dao.update(customer); + final var nonExistingId = getNonExistingCustomerId(); + final var newFirstname = "Douglas"; + final var newLastname = "MacArthur"; + final var customer = new Customer(nonExistingId, newFirstname, newLastname); + var result = dao.update(customer); assertFalse(result); assertFalse(dao.getById(nonExistingId).isPresent()); @@ -135,16 +135,14 @@ public void retrieveShouldReturnNoCustomer() throws Exception { /** * Represents a scenario where DAO operations are being performed on an already existing * customer. - * */ @Nested public class ExistingCustomer { @Test public void addingShouldResultInFailureAndNotAffectExistingCustomers() throws Exception { - Customer existingCustomer = new Customer(1, "Freddy", "Krueger"); - - boolean result = dao.add(existingCustomer); + var existingCustomer = new Customer(1, "Freddy", "Krueger"); + var result = dao.add(existingCustomer); assertFalse(result); assertCustomerCountIs(1); @@ -153,7 +151,7 @@ public void addingShouldResultInFailureAndNotAffectExistingCustomers() throws Ex @Test public void deletionShouldBeSuccessAndCustomerShouldBeNonAccessible() throws Exception { - boolean result = dao.delete(existingCustomer); + var result = dao.delete(existingCustomer); assertTrue(result); assertCustomerCountIs(0); @@ -161,15 +159,16 @@ public void deletionShouldBeSuccessAndCustomerShouldBeNonAccessible() throws Exc } @Test - public void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() throws Exception { - final String newFirstname = "Bernard"; - final String newLastname = "Montgomery"; - final Customer customer = new Customer(existingCustomer.getId(), newFirstname, newLastname); - boolean result = dao.update(customer); + public void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() throws + Exception { + final var newFirstname = "Bernard"; + final var newLastname = "Montgomery"; + final var customer = new Customer(existingCustomer.getId(), newFirstname, newLastname); + var result = dao.update(customer); assertTrue(result); - final Customer cust = dao.getById(existingCustomer.getId()).get(); + final var cust = dao.getById(existingCustomer.getId()).get(); assertEquals(newFirstname, cust.getFirstName()); assertEquals(newLastname, cust.getLastName()); } @@ -177,28 +176,28 @@ public void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdate } /** - * Represents a scenario where DB connectivity is not present due to network issue, or - * DB service unavailable. - * + * Represents a scenario where DB connectivity is not present due to network issue, or DB service + * unavailable. */ @Nested public class ConnectivityIssue { - + private static final String EXCEPTION_CAUSE = "Connection not available"; /** * setup a connection failure scenario. + * * @throws SQLException if any error occurs. */ @BeforeEach public void setUp() throws SQLException { dao = new DbCustomerDao(mockedDatasource()); } - + private DataSource mockedDatasource() throws SQLException { - DataSource mockedDataSource = mock(DataSource.class); - Connection mockedConnection = mock(Connection.class); - SQLException exception = new SQLException(EXCEPTION_CAUSE); + var mockedDataSource = mock(DataSource.class); + var mockedConnection = mock(Connection.class); + var exception = new SQLException(EXCEPTION_CAUSE); doThrow(exception).when(mockedConnection).prepareStatement(Mockito.anyString()); doReturn(mockedConnection).when(mockedDataSource).getConnection(); return mockedDataSource; @@ -210,30 +209,30 @@ public void addingACustomerFailsWithExceptionAsFeedbackToClient() { dao.add(new Customer(2, "Bernard", "Montgomery")); }); } - + @Test public void deletingACustomerFailsWithExceptionAsFeedbackToTheClient() { assertThrows(Exception.class, () -> { dao.delete(existingCustomer); }); } - + @Test public void updatingACustomerFailsWithFeedbackToTheClient() { - final String newFirstname = "Bernard"; - final String newLastname = "Montgomery"; + final var newFirstname = "Bernard"; + final var newLastname = "Montgomery"; assertThrows(Exception.class, () -> { dao.update(new Customer(existingCustomer.getId(), newFirstname, newLastname)); }); } - + @Test public void retrievingACustomerByIdFailsWithExceptionAsFeedbackToClient() { assertThrows(Exception.class, () -> { dao.getById(existingCustomer.getId()); }); } - + @Test public void retrievingAllCustomersFailsWithExceptionAsFeedbackToClient() { assertThrows(Exception.class, () -> { @@ -245,18 +244,19 @@ public void retrievingAllCustomersFailsWithExceptionAsFeedbackToClient() { /** * Delete customer schema for fresh setup per test. + * * @throws SQLException if any error occurs. */ @AfterEach public void deleteSchema() throws SQLException { - try (Connection connection = DriverManager.getConnection(DB_URL); - Statement statement = connection.createStatement()) { + try (var connection = DriverManager.getConnection(DB_URL); + var statement = connection.createStatement()) { statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL); } } private void assertCustomerCountIs(int count) throws Exception { - try (Stream allCustomers = dao.getAll()) { + try (var allCustomers = dao.getAll()) { assertEquals(count, allCustomers.count()); } } @@ -264,7 +264,7 @@ private void assertCustomerCountIs(int count) throws Exception { /** * An arbitrary number which does not correspond to an active Customer id. - * + * * @return an int of a customer id which doesn't exist */ private int getNonExistingCustomerId() { diff --git a/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java b/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java index 0aa86576b1b2..64ce433df98e 100644 --- a/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java +++ b/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,18 +23,15 @@ package com.iluwatar.dao; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.util.Optional; -import java.util.stream.Stream; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + /** * Tests {@link InMemoryCustomerDao}. */ @@ -50,20 +47,20 @@ public void setUp() { } /** - * Represents the scenario when the DAO operations are being performed on a non existent - * customer. + * Represents the scenario when the DAO operations are being performed on a non existent + * customer. */ @Nested public class NonExistingCustomer { @Test public void addingShouldResultInSuccess() throws Exception { - try (Stream allCustomers = dao.getAll()) { + try (var allCustomers = dao.getAll()) { assumeTrue(allCustomers.count() == 1); } - final Customer nonExistingCustomer = new Customer(2, "Robert", "Englund"); - boolean result = dao.add(nonExistingCustomer); + final var nonExistingCustomer = new Customer(2, "Robert", "Englund"); + var result = dao.add(nonExistingCustomer); assertTrue(result); assertCustomerCountIs(2); @@ -72,8 +69,8 @@ public void addingShouldResultInSuccess() throws Exception { @Test public void deletionShouldBeFailureAndNotAffectExistingCustomers() throws Exception { - final Customer nonExistingCustomer = new Customer(2, "Robert", "Englund"); - boolean result = dao.delete(nonExistingCustomer); + final var nonExistingCustomer = new Customer(2, "Robert", "Englund"); + var result = dao.delete(nonExistingCustomer); assertFalse(result); assertCustomerCountIs(1); @@ -81,11 +78,11 @@ public void deletionShouldBeFailureAndNotAffectExistingCustomers() throws Except @Test public void updationShouldBeFailureAndNotAffectExistingCustomers() throws Exception { - final int nonExistingId = getNonExistingCustomerId(); - final String newFirstname = "Douglas"; - final String newLastname = "MacArthur"; - final Customer customer = new Customer(nonExistingId, newFirstname, newLastname); - boolean result = dao.update(customer); + final var nonExistingId = getNonExistingCustomerId(); + final var newFirstname = "Douglas"; + final var newLastname = "MacArthur"; + final var customer = new Customer(nonExistingId, newFirstname, newLastname); + var result = dao.update(customer); assertFalse(result); assertFalse(dao.getById(nonExistingId).isPresent()); @@ -106,7 +103,7 @@ public class ExistingCustomer { @Test public void addingShouldResultInFailureAndNotAffectExistingCustomers() throws Exception { - boolean result = dao.add(CUSTOMER); + var result = dao.add(CUSTOMER); assertFalse(result); assertCustomerCountIs(1); @@ -115,7 +112,7 @@ public void addingShouldResultInFailureAndNotAffectExistingCustomers() throws Ex @Test public void deletionShouldBeSuccessAndCustomerShouldBeNonAccessible() throws Exception { - boolean result = dao.delete(CUSTOMER); + var result = dao.delete(CUSTOMER); assertTrue(result); assertCustomerCountIs(0); @@ -123,23 +120,24 @@ public void deletionShouldBeSuccessAndCustomerShouldBeNonAccessible() throws Exc } @Test - public void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() throws Exception { - final String newFirstname = "Bernard"; - final String newLastname = "Montgomery"; - final Customer customer = new Customer(CUSTOMER.getId(), newFirstname, newLastname); - boolean result = dao.update(customer); + public void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() throws + Exception { + final var newFirstname = "Bernard"; + final var newLastname = "Montgomery"; + final var customer = new Customer(CUSTOMER.getId(), newFirstname, newLastname); + var result = dao.update(customer); assertTrue(result); - final Customer cust = dao.getById(CUSTOMER.getId()).get(); + final var cust = dao.getById(CUSTOMER.getId()).get(); assertEquals(newFirstname, cust.getFirstName()); assertEquals(newLastname, cust.getLastName()); } - + @Test public void retriveShouldReturnTheCustomer() { - Optional optionalCustomer = dao.getById(CUSTOMER.getId()); - + var optionalCustomer = dao.getById(CUSTOMER.getId()); + assertTrue(optionalCustomer.isPresent()); assertEquals(CUSTOMER, optionalCustomer.get()); } @@ -147,15 +145,15 @@ public void retriveShouldReturnTheCustomer() { /** * An arbitrary number which does not correspond to an active Customer id. - * + * * @return an int of a customer id which doesn't exist */ private int getNonExistingCustomerId() { return 999; } - + private void assertCustomerCountIs(int count) throws Exception { - try (Stream allCustomers = dao.getAll()) { + try (var allCustomers = dao.getAll()) { assertEquals(count, allCustomers.count()); } } diff --git a/data-bus/README.md b/data-bus/README.md index 257192e25234..cf54beb32525 100644 --- a/data-bus/README.md +++ b/data-bus/README.md @@ -6,8 +6,7 @@ permalink: /patterns/data-bus/ categories: Architectural tags: - - Java - - Difficulty-Intermediate + - Decoupling --- ## Intent @@ -16,6 +15,7 @@ Allows send of messages/events between components of an application without them needing to know about each other. They only need to know about the type of the message/event being sent. +## Class diagram ![data bus pattern uml diagram](./etc/data-bus.urm.png "Data Bus pattern") ## Applicability diff --git a/data-bus/etc/data-bus.urm.puml b/data-bus/etc/data-bus.urm.puml new file mode 100644 index 000000000000..8ca7f85efb11 --- /dev/null +++ b/data-bus/etc/data-bus.urm.puml @@ -0,0 +1,82 @@ +@startuml +package com.iluwatar.databus { + class AbstractDataType { + - dataBus : DataBus + + AbstractDataType() + + getDataBus() : DataBus + + setDataBus(dataBus : DataBus) + } + ~class App { + ~ App() + + main(args : String[]) {static} + } + class DataBus { + - INSTANCE : DataBus {static} + - listeners : Set + + DataBus() + + getInstance() : DataBus {static} + + publish(event : DataType) + + subscribe(member : Member) + + unsubscribe(member : Member) + } + interface DataType { + + getDataBus() : DataBus {abstract} + + setDataBus(DataBus) {abstract} + } + interface Member { + + accept(DataType) {abstract} + } +} +package com.iluwatar.databus.data { + class MessageData { + - message : String + + MessageData(message : String) + + getMessage() : String + + of(message : String) : DataType {static} + } + class StartingData { + - when : LocalDateTime + + StartingData(when : LocalDateTime) + + getWhen() : LocalDateTime + + of(when : LocalDateTime) : DataType {static} + } + class StoppingData { + - when : LocalDateTime + + StoppingData(when : LocalDateTime) + + getWhen() : LocalDateTime + + of(when : LocalDateTime) : DataType {static} + } +} +package com.iluwatar.databus.members { + class MessageCollectorMember { + - LOGGER : Logger {static} + - messages : List + - name : String + + MessageCollectorMember(name : String) + + accept(data : DataType) + + getMessages() : List + - handleEvent(data : MessageData) + } + class StatusMember { + - LOGGER : Logger {static} + - id : int + - started : LocalDateTime + - stopped : LocalDateTime + + StatusMember(id : int) + + accept(data : DataType) + + getStarted() : LocalDateTime + + getStopped() : LocalDateTime + - handleEvent(data : StartingData) + - handleEvent(data : StoppingData) + } +} +AbstractDataType --> "-dataBus" DataBus +DataBus --> "-INSTANCE" DataBus +DataBus --> "-listeners" Member +AbstractDataType ..|> DataType +MessageData --|> AbstractDataType +StartingData --|> AbstractDataType +StoppingData --|> AbstractDataType +MessageCollectorMember ..|> Member +StatusMember ..|> Member +@enduml \ No newline at end of file diff --git a/data-bus/pom.xml b/data-bus/pom.xml index 9106191af529..e67135ae0a28 100644 --- a/data-bus/pom.xml +++ b/data-bus/pom.xml @@ -2,7 +2,7 @@ "-physicsComponentManager" PhysicsComponentManager +GameEntity --> "-aiComponentManager" AiComponentManager +GameEntity --> "-renderComponentManager" RenderComponentManager +AiComponent ..|> Component +PhysicsComponent ..|> Component +RenderComponent ..|> Component +@enduml \ No newline at end of file diff --git a/data-locality/pom.xml b/data-locality/pom.xml new file mode 100644 index 000000000000..660daa9b7fcc --- /dev/null +++ b/data-locality/pom.xml @@ -0,0 +1,63 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + data-locality + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.data.locality.Application + + + + + + + + + \ No newline at end of file diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/Application.java b/data-locality/src/main/java/com/iluwatar/data/locality/Application.java new file mode 100644 index 000000000000..463a02b784f1 --- /dev/null +++ b/data-locality/src/main/java/com/iluwatar/data/locality/Application.java @@ -0,0 +1,53 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.data.locality; + +import com.iluwatar.data.locality.game.GameEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Use the Data Locality pattern is when you have a performance problem. Take advantage of that to + * improve performance by increasing data locality — keeping data in contiguous memory in the order + * that you process it. + * + *

Example: Game loop that processes a bunch of game entities. Those entities are decomposed + * into different domains  — AI, physics, and rendering — using the Component pattern. + */ +public class Application { + + private static final Logger LOGGER = LoggerFactory.getLogger(Application.class); + + private static final int NUM_ENTITIES = 5; + + /** + * Start game loop with each component have NUM_ENTITIES instance. + */ + public static void main(String[] args) { + LOGGER.info("Start Game Application using Data-Locality pattern"); + var gameEntity = new GameEntity(NUM_ENTITIES); + gameEntity.start(); + gameEntity.update(); + } +} diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/GameEntity.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/GameEntity.java new file mode 100644 index 000000000000..337532175dc5 --- /dev/null +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/GameEntity.java @@ -0,0 +1,84 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.data.locality.game; + +import com.iluwatar.data.locality.game.component.manager.AiComponentManager; +import com.iluwatar.data.locality.game.component.manager.PhysicsComponentManager; +import com.iluwatar.data.locality.game.component.manager.RenderComponentManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The game Entity maintains a big array of pointers . Each spin of the game loop, we need to run + * the following: + * + *

Update the AI components. + * + *

Update the physics components for them. + * + *

Render them using their render components. + */ +public class GameEntity { + private static final Logger LOGGER = LoggerFactory.getLogger(GameEntity.class); + + private final AiComponentManager aiComponentManager; + private final PhysicsComponentManager physicsComponentManager; + private final RenderComponentManager renderComponentManager; + + /** + * Init components. + */ + public GameEntity(int numEntities) { + LOGGER.info("Init Game with #Entity : {}", numEntities); + aiComponentManager = new AiComponentManager(numEntities); + physicsComponentManager = new PhysicsComponentManager(numEntities); + renderComponentManager = new RenderComponentManager(numEntities); + } + + /** + * start all component. + */ + public void start() { + LOGGER.info("Start Game"); + aiComponentManager.start(); + physicsComponentManager.start(); + renderComponentManager.start(); + } + + /** + * update all component. + */ + public void update() { + LOGGER.info("Update Game Component"); + // Process AI. + aiComponentManager.update(); + + // update physics. + physicsComponentManager.update(); + + // Draw to screen. + renderComponentManager.render(); + } + +} diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java new file mode 100644 index 000000000000..5b1be9e35cc7 --- /dev/null +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java @@ -0,0 +1,48 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.data.locality.game.component; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of AI component for Game. + */ +public class AiComponent implements Component { + + private static final Logger LOGGER = LoggerFactory.getLogger(AiComponent.class); + + /** + * Update ai component. + */ + @Override + public void update() { + LOGGER.info("update AI component"); + } + + @Override + public void render() { + + } +} diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/Component.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/Component.java new file mode 100644 index 000000000000..f159df4f651a --- /dev/null +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/Component.java @@ -0,0 +1,34 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.data.locality.game.component; + +/** + * Implement different Game component update and render process. + */ +public interface Component { + + void update(); + + void render(); +} diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/PhysicsComponent.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/PhysicsComponent.java new file mode 100644 index 000000000000..89c6f1503009 --- /dev/null +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/PhysicsComponent.java @@ -0,0 +1,48 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.data.locality.game.component; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of Physics Component of Game. + */ +public class PhysicsComponent implements Component { + + private static final Logger LOGGER = LoggerFactory.getLogger(PhysicsComponent.class); + + /** + * update physics component of game. + */ + @Override + public void update() { + LOGGER.info("Update physics component of game"); + } + + @Override + public void render() { + // do nothing + } +} diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/RenderComponent.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/RenderComponent.java new file mode 100644 index 000000000000..0b49da056394 --- /dev/null +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/RenderComponent.java @@ -0,0 +1,48 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.data.locality.game.component; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of Render Component of Game. + */ +public class RenderComponent implements Component { + + private static final Logger LOGGER = LoggerFactory.getLogger(RenderComponent.class); + + @Override + public void update() { + // do nothing + } + + /** + * render. + */ + @Override + public void render() { + LOGGER.info("Render Component"); + } +} diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java new file mode 100644 index 000000000000..616ebf8015b4 --- /dev/null +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java @@ -0,0 +1,66 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.data.locality.game.component.manager; + +import com.iluwatar.data.locality.game.component.AiComponent; +import com.iluwatar.data.locality.game.component.Component; +import java.util.stream.IntStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * AI component manager for Game. + */ +public class AiComponentManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(AiComponentManager.class); + + private static final int MAX_ENTITIES = 10000; + + private final int numEntities; + + private static final Component[] AI_COMPONENTS = new AiComponent[MAX_ENTITIES]; + + public AiComponentManager(int numEntities) { + this.numEntities = numEntities; + } + + /** + * start AI component of Game. + */ + public void start() { + LOGGER.info("Start AI Game Component"); + IntStream.range(0, numEntities).forEach(i -> AI_COMPONENTS[i] = new AiComponent()); + } + + /** + * Update AI component of Game. + */ + public void update() { + LOGGER.info("Update AI Game Component"); + IntStream.range(0, numEntities) + .filter(i -> AI_COMPONENTS.length > i && AI_COMPONENTS[i] != null) + .forEach(i -> AI_COMPONENTS[i].update()); + } +} diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java new file mode 100644 index 000000000000..61ba4ebddca8 --- /dev/null +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java @@ -0,0 +1,68 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.data.locality.game.component.manager; + +import com.iluwatar.data.locality.game.component.Component; +import com.iluwatar.data.locality.game.component.PhysicsComponent; +import java.util.stream.IntStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Physics component Manager for Game. + */ +public class PhysicsComponentManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(PhysicsComponentManager.class); + + private static final int MAX_ENTITIES = 10000; + + private final int numEntities; + + private static final Component[] PHYSICS_COMPONENTS = new PhysicsComponent[MAX_ENTITIES]; + + public PhysicsComponentManager(int numEntities) { + this.numEntities = numEntities; + } + + /** + * Start physics component of Game. + */ + public void start() { + LOGGER.info("Start Physics Game Component "); + IntStream.range(0, numEntities).forEach(i -> PHYSICS_COMPONENTS[i] = new PhysicsComponent()); + } + + + /** + * Update physics component of Game. + */ + public void update() { + LOGGER.info("Update Physics Game Component "); + // Process physics. + IntStream.range(0, numEntities) + .filter(i -> PHYSICS_COMPONENTS.length > i && PHYSICS_COMPONENTS[i] != null) + .forEach(i -> PHYSICS_COMPONENTS[i].update()); + } +} diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java new file mode 100644 index 000000000000..f8c4b3522321 --- /dev/null +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java @@ -0,0 +1,68 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.data.locality.game.component.manager; + +import com.iluwatar.data.locality.game.component.Component; +import com.iluwatar.data.locality.game.component.RenderComponent; +import java.util.stream.IntStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Render component manager for Game. + */ +public class RenderComponentManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(RenderComponentManager.class); + + private static final int MAX_ENTITIES = 10000; + + private final int numEntities; + + private static final Component[] RENDER_COMPONENTS = new RenderComponent[MAX_ENTITIES]; + + public RenderComponentManager(int numEntities) { + this.numEntities = numEntities; + } + + /** + * Start render component. + */ + public void start() { + LOGGER.info("Start Render Game Component "); + IntStream.range(0, numEntities).forEach(i -> RENDER_COMPONENTS[i] = new RenderComponent()); + } + + + /** + * render component. + */ + public void render() { + LOGGER.info("Update Render Game Component "); + // Process Render. + IntStream.range(0, numEntities) + .filter(i -> RENDER_COMPONENTS.length > i && RENDER_COMPONENTS[i] != null) + .forEach(i -> RENDER_COMPONENTS[i].render()); + } +} diff --git a/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java b/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java new file mode 100644 index 000000000000..3371be4c1f56 --- /dev/null +++ b/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java @@ -0,0 +1,41 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.data.locality; + + +import org.junit.jupiter.api.Test; + +/** + * Test Game Application + */ +class ApplicationTest { + + /** + * Test run + */ + @Test + void main() { + Application.main(new String[] {}); + } +} \ No newline at end of file diff --git a/data-mapper/README.md b/data-mapper/README.md index 075e8eecef15..fe0af92365ec 100644 --- a/data-mapper/README.md +++ b/data-mapper/README.md @@ -3,15 +3,15 @@ layout: pattern title: Data Mapper folder: data-mapper permalink: /patterns/data-mapper/ -categories: Persistence Tier +categories: Architectural tags: - - Java - - Difficulty-Beginner + - Decoupling --- ## Intent A layer of mappers that moves data between objects and a database while keeping them independent of each other and the mapper itself +## Class diagram ![alt text](./etc/data-mapper.png "Data Mapper") ## Applicability diff --git a/data-mapper/etc/data-mapper.urm.puml b/data-mapper/etc/data-mapper.urm.puml new file mode 100644 index 000000000000..3b32411d11c9 --- /dev/null +++ b/data-mapper/etc/data-mapper.urm.puml @@ -0,0 +1,43 @@ +@startuml +package com.iluwatar.datamapper { + class App { + - STUDENT_STRING : String {static} + - log : Logger {static} + - App() + + main(args : String[]) {static} + } + class Student { + - grade : char + - name : String + - serialVersionUID : long {static} + - studentId : int + + Student(studentId : int, name : String, grade : char) + + equals(inputObject : Object) : boolean + + getGrade() : char + + getName() : String + + getStudentId() : int + + hashCode() : int + + setGrade(grade : char) + + setName(name : String) + + setStudentId(studentId : int) + + toString() : String + } + interface StudentDataMapper { + + delete(Student) {abstract} + + find(int) : Optional {abstract} + + insert(Student) {abstract} + + update(Student) {abstract} + } + class StudentDataMapperImpl { + - students : List + + StudentDataMapperImpl() + + delete(studentToBeDeleted : Student) + + find(studentId : int) : Optional + + getStudents() : List + + insert(studentToBeInserted : Student) + + update(studentToBeUpdated : Student) + } +} +StudentDataMapperImpl --> "-students" Student +StudentDataMapperImpl ..|> StudentDataMapper +@enduml \ No newline at end of file diff --git a/data-mapper/pom.xml b/data-mapper/pom.xml index bbcf42ee6b5d..64f03a186a7c 100644 --- a/data-mapper/pom.xml +++ b/data-mapper/pom.xml @@ -1,50 +1,60 @@ - - - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.21.0-SNAPSHOT - - data-mapper - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - log4j - log4j - - - + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + data-mapper + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.datamapper.App + + + + + + + + + diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/App.java b/data-mapper/src/main/java/com/iluwatar/datamapper/App.java index 5fcd0d9ea04c..9bfc329523d1 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/App.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/App.java @@ -1,26 +1,30 @@ -/** - * The MIT License Copyright (c) 2016 Amit Dixit +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ -package com.iluwatar.datamapper; -import java.util.Optional; +package com.iluwatar.datamapper; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The Data Mapper (DM) is a layer of software that separates the in-memory objects from the @@ -29,36 +33,37 @@ * present; they need no SQL interface code, and certainly no knowledge of the database schema. (The * database schema is always ignorant of the objects that use it.) Since it's a form of Mapper , * Data Mapper itself is even unknown to the domain layer. - *

- * The below example demonstrates basic CRUD operations: Create, Read, Update, and Delete. - * + * + *

The below example demonstrates basic CRUD operations: Create, Read, Update, and Delete. */ public final class App { - private static Logger log = Logger.getLogger(App.class); + private static Logger log = LoggerFactory.getLogger(App.class); + private static final String STUDENT_STRING = "App.main(), student : "; + /** * Program entry point. - * + * * @param args command line args. */ public static void main(final String... args) { /* Create new data mapper for type 'first' */ - final StudentDataMapper mapper = new StudentDataMapperImpl(); + final var mapper = new StudentDataMapperImpl(); /* Create new student */ - Student student = new Student(1, "Adam", 'A'); + var student = new Student(1, "Adam", 'A'); /* Add student in respectibe store */ mapper.insert(student); - log.debug("App.main(), student : " + student + ", is inserted"); + log.debug(STUDENT_STRING + student + ", is inserted"); /* Find this student */ - final Optional studentToBeFound = mapper.find(student.getStudentId()); + final var studentToBeFound = mapper.find(student.getStudentId()); - log.debug("App.main(), student : " + studentToBeFound + ", is searched"); + log.debug(STUDENT_STRING + studentToBeFound + ", is searched"); /* Update existing student object */ student = new Student(student.getStudentId(), "AdamUpdated", 'A'); @@ -66,12 +71,13 @@ public static void main(final String... args) { /* Update student in respectibe db */ mapper.update(student); - log.debug("App.main(), student : " + student + ", is updated"); - log.debug("App.main(), student : " + student + ", is going to be deleted"); + log.debug(STUDENT_STRING + student + ", is updated"); + log.debug(STUDENT_STRING + student + ", is going to be deleted"); /* Delete student in db */ mapper.delete(student); } - private App() {} + private App() { + } } diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java b/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java index a6995b06d6fe..4f354c7ebd10 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java @@ -1,29 +1,33 @@ -/** - * The MIT License Copyright (c) 2016 Amit Dixit +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package com.iluwatar.datamapper; /** * Using Runtime Exception for avoiding dependancy on implementation exceptions. This helps in * decoupling. - * - * @author amit.dixit * + * @author amit.dixit */ public final class DataMapperException extends RuntimeException { @@ -34,7 +38,7 @@ public final class DataMapperException extends RuntimeException { * initialized, and may subsequently be initialized by a call to {@link #initCause}. * * @param message the detail message. The detail message is saved for later retrieval by the - * {@link #getMessage()} method. + * {@link #getMessage()} method. */ public DataMapperException(final String message) { super(message); diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java b/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java index d9c3d389f545..25075b1e00f6 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java @@ -1,28 +1,32 @@ -/** - * The MIT License Copyright (c) 2016 Amit Dixit +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ -package com.iluwatar.datamapper; +package com.iluwatar.datamapper; import java.io.Serializable; /** - * Class defining Student + * Class defining Student. */ public final class Student implements Serializable { @@ -34,11 +38,11 @@ public final class Student implements Serializable { /** - * Use this constructor to create a Student with all details + * Use this constructor to create a Student with all details. * * @param studentId as unique student id - * @param name as student name - * @param grade as respective grade of student + * @param name as student name + * @param grade as respective grade of student */ public Student(final int studentId, final String name, final char grade) { this.studentId = studentId; @@ -46,57 +50,30 @@ public Student(final int studentId, final String name, final char grade) { this.grade = grade; } - /** - * - * @return the student id - */ public int getStudentId() { return studentId; } - /** - * - * @param studentId as unique student id - */ public void setStudentId(final int studentId) { this.studentId = studentId; } - /** - * - * @return name of student - */ public String getName() { return name; } - /** - * - * @param name as 'name' of student - */ public void setName(final String name) { this.name = name; } - /** - * - * @return grade of student - */ public char getGrade() { return grade; } - /** - * - * @param grade as 'grade of student' - */ public void setGrade(final char grade) { this.grade = grade; } - /** - * - */ @Override public boolean equals(final Object inputObject) { @@ -108,7 +85,7 @@ public boolean equals(final Object inputObject) { isEqual = true; } else if (inputObject != null && getClass() == inputObject.getClass()) { - final Student inputStudent = (Student) inputObject; + final var inputStudent = (Student) inputObject; /* If student id matched */ if (this.getStudentId() == inputStudent.getStudentId()) { @@ -120,9 +97,6 @@ public boolean equals(final Object inputObject) { return isEqual; } - /** - * - */ @Override public int hashCode() { @@ -130,9 +104,6 @@ public int hashCode() { return this.getStudentId(); } - /** - * - */ @Override public String toString() { return "Student [studentId=" + studentId + ", name=" + name + ", grade=" + grade + "]"; diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java index cb93f46973fe..3dfe4787fb9d 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java @@ -1,27 +1,32 @@ -/** - * The MIT License Copyright (c) 2016 Amit Dixit +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package com.iluwatar.datamapper; import java.util.Optional; /** - * Interface lists out the possible behaviour for all possible student mappers + * Interface lists out the possible behaviour for all possible student mappers. */ public interface StudentDataMapper { diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java index 685a439ac60c..85ad4aa8d233 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java @@ -1,21 +1,26 @@ -/** - * The MIT License Copyright (c) 2016 Amit Dixit +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package com.iluwatar.datamapper; import java.util.ArrayList; @@ -23,7 +28,7 @@ import java.util.Optional; /** - * Implementation of Actions on Students Data + * Implementation of Actions on Students Data. */ public final class StudentDataMapperImpl implements StudentDataMapper { @@ -32,70 +37,36 @@ public final class StudentDataMapperImpl implements StudentDataMapper { @Override public Optional find(int studentId) { - - /* Compare with existing students */ - for (final Student student : this.getStudents()) { - - /* Check if student is found */ - if (student.getStudentId() == studentId) { - - return Optional.of(student); - } - } - - /* Return empty value */ - return Optional.empty(); + return this.getStudents().stream().filter(x -> x.getStudentId() == studentId).findFirst(); } @Override public void update(Student studentToBeUpdated) throws DataMapperException { - - - /* Check with existing students */ - if (this.getStudents().contains(studentToBeUpdated)) { - - /* Get the index of student in list */ - final int index = this.getStudents().indexOf(studentToBeUpdated); - - /* Update the student in list */ - this.getStudents().set(index, studentToBeUpdated); - - } else { - - /* Throw user error after wrapping in a runtime exception */ - throw new DataMapperException("Student [" + studentToBeUpdated.getName() + "] is not found"); - } + String name = studentToBeUpdated.getName(); + Integer index = Optional.of(studentToBeUpdated) + .map(Student::getStudentId) + .flatMap(this::find) + .map(students::indexOf) + .orElseThrow(() -> new DataMapperException("Student [" + name + "] is not found")); + students.set(index, studentToBeUpdated); } @Override public void insert(Student studentToBeInserted) throws DataMapperException { - - /* Check with existing students */ - if (!this.getStudents().contains(studentToBeInserted)) { - - /* Add student in list */ - this.getStudents().add(studentToBeInserted); - - } else { - - /* Throw user error after wrapping in a runtime exception */ - throw new DataMapperException("Student already [" + studentToBeInserted.getName() + "] exists"); + Optional student = find(studentToBeInserted.getStudentId()); + if (student.isPresent()) { + String name = studentToBeInserted.getName(); + throw new DataMapperException("Student already [" + name + "] exists"); } + + students.add(studentToBeInserted); } @Override public void delete(Student studentToBeDeleted) throws DataMapperException { - - /* Check with existing students */ - if (this.getStudents().contains(studentToBeDeleted)) { - - /* Delete the student from list */ - this.getStudents().remove(studentToBeDeleted); - - } else { - - /* Throw user error after wrapping in a runtime exception */ - throw new DataMapperException("Student [" + studentToBeDeleted.getName() + "] is not found"); + if (!students.remove(studentToBeDeleted)) { + String name = studentToBeDeleted.getName(); + throw new DataMapperException("Student [" + name + "] is not found"); } } diff --git a/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java b/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java index 5c665a4e470d..ec1d71be4245 100644 --- a/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java +++ b/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java @@ -1,21 +1,26 @@ -/** - * The MIT License Copyright (c) 2016 Amit Dixit +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package com.iluwatar.datamapper; import org.junit.jupiter.api.Test; @@ -27,7 +32,6 @@ public final class AppTest { @Test public void test() { - final String[] args = {}; - App.main(args); + App.main(); } } diff --git a/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java b/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java index 0e1552a78cce..717e4fe909cb 100644 --- a/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java +++ b/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java @@ -1,28 +1,33 @@ -/** - * The MIT License Copyright (c) 2016 Amit Dixit +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ -package com.iluwatar.datamapper; -import org.junit.jupiter.api.Test; +package com.iluwatar.datamapper; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import org.junit.jupiter.api.Test; + /** * The Data Mapper (DM) is a layer of software that separates the in-memory objects from the * database. Its responsibility is to transfer data between the two and also to isolate them from @@ -41,11 +46,11 @@ public class DataMapperTest { public void testFirstDataMapper() { /* Create new data mapper of first type */ - final StudentDataMapper mapper = new StudentDataMapperImpl(); + final var mapper = new StudentDataMapperImpl(); /* Create new student */ - int studentId = 1; - Student student = new Student(studentId, "Adam", 'A'); + var studentId = 1; + var student = new Student(studentId, "Adam", 'A'); /* Add student in respectibe db */ mapper.insert(student); @@ -54,7 +59,7 @@ public void testFirstDataMapper() { assertEquals(studentId, mapper.find(student.getStudentId()).get().getStudentId()); /* Update existing student object */ - String updatedName = "AdamUpdated"; + var updatedName = "AdamUpdated"; student = new Student(student.getStudentId(), updatedName, 'A'); /* Update student in respectibe db */ diff --git a/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java b/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java index 7bf396a5d2da..2ea7a3daa41f 100644 --- a/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java +++ b/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java @@ -1,29 +1,32 @@ -/** - * The MIT License Copyright (c) 2016 Amit Dixit +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ -package com.iluwatar.datamapper; -import org.junit.jupiter.api.Test; +package com.iluwatar.datamapper; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; /** * Tests {@link Student}. @@ -31,8 +34,8 @@ public final class StudentTest { /** - * This API tests the equality behaviour of Student object - * Object Equality should work as per logic defined in equals method + * This API tests the equality behaviour of Student object Object Equality should work as per + * logic defined in equals method * * @throws Exception if any execution error during test */ @@ -40,9 +43,9 @@ public final class StudentTest { public void testEquality() throws Exception { /* Create some students */ - final Student firstStudent = new Student(1, "Adam", 'A'); - final Student secondStudent = new Student(2, "Donald", 'B'); - final Student secondSameStudent = new Student(2, "Donald", 'B'); + final var firstStudent = new Student(1, "Adam", 'A'); + final var secondStudent = new Student(2, "Donald", 'B'); + final var secondSameStudent = new Student(2, "Donald", 'B'); /* Check equals functionality: should return 'true' */ assertEquals(firstStudent, firstStudent); diff --git a/data-transfer-object/README.md b/data-transfer-object/README.md index ad9b9f4e2198..f269fc66bcf0 100644 --- a/data-transfer-object/README.md +++ b/data-transfer-object/README.md @@ -5,16 +5,14 @@ folder: data-transfer-object permalink: /patterns/data-transfer-object/ categories: Architectural tags: - - Java - - KISS - - YAGNI - - Difficulty-Beginner + - Performance --- ## Intent Pass data with multiple attributes in one shot from client to server, to avoid multiple calls to remote server. +## Class diagram ![alt text](./etc/data-transfer-object.urm.png "data-transfer-object") ## Applicability diff --git a/data-transfer-object/etc/data-transfer-object.urm.puml b/data-transfer-object/etc/data-transfer-object.urm.puml new file mode 100644 index 000000000000..ee96c37ca967 --- /dev/null +++ b/data-transfer-object/etc/data-transfer-object.urm.puml @@ -0,0 +1,27 @@ +@startuml +package com.iluwatar.datatransfer { + class CustomerClientApp { + - LOGGER : Logger {static} + + CustomerClientApp() + + main(args : String[]) {static} + - printCustomerDetails(allCustomers : List) {static} + } + class CustomerDto { + - firstName : String + - id : String + - lastName : String + + CustomerDto(id : String, firstName : String, lastName : String) + + getFirstName() : String + + getId() : String + + getLastName() : String + } + class CustomerResource { + - customers : List + + CustomerResource(customers : List) + + delete(customerId : String) + + getAllCustomers() : List + + save(customer : CustomerDto) + } +} +CustomerResource --> "-customers" CustomerDto +@enduml \ No newline at end of file diff --git a/data-transfer-object/pom.xml b/data-transfer-object/pom.xml index 3dcb2dc3ce4d..5889daba887f 100644 --- a/data-transfer-object/pom.xml +++ b/data-transfer-object/pom.xml @@ -2,7 +2,7 @@ "-decorated" Troll +ClubbedTroll ..|> Troll +SimpleTroll ..|> Troll +@enduml \ No newline at end of file diff --git a/decorator/pom.xml b/decorator/pom.xml index a9b9c361d11a..c7e1a4d8deb3 100644 --- a/decorator/pom.xml +++ b/decorator/pom.xml @@ -2,7 +2,7 @@ "-printer" Printer +PrinterController ..|> Printer +CanonPrinter ..|> Printer +EpsonPrinter ..|> Printer +HpPrinter ..|> Printer +@enduml \ No newline at end of file diff --git a/delegation/pom.xml b/delegation/pom.xml index 62bda23185ff..63cd91842caa 100644 --- a/delegation/pom.xml +++ b/delegation/pom.xml @@ -2,7 +2,7 @@ - - - java-design-patterns - com.iluwatar - 1.21.0-SNAPSHOT - - 4.0.0 + + java-design-patterns + com.iluwatar + 1.23.0-SNAPSHOT + + 4.0.0 - delegation + delegation - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - \ No newline at end of file + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.delegation.simple.App + + + + + + + + + diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/App.java b/delegation/src/main/java/com/iluwatar/delegation/simple/App.java index 83e00fd1fb1c..9b9f1284ea95 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/App.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.delegation.simple; import com.iluwatar.delegation.simple.printers.CanonPrinter; @@ -27,30 +28,32 @@ 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. + * 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. + *

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 { private static final String MESSAGE_TO_PRINT = "hello world"; /** - * Program entry point + * 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()); + var hpPrinterController = new PrinterController(new HpPrinter()); + var canonPrinterController = new PrinterController(new CanonPrinter()); + var epsonPrinterController = new PrinterController(new EpsonPrinter()); hpPrinterController.print(MESSAGE_TO_PRINT); canonPrinterController.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 index ee4d54938a86..f3f434adb478 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/Printer.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/Printer.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.delegation.simple; import com.iluwatar.delegation.simple.printers.CanonPrinter; @@ -37,7 +38,8 @@ 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. + * controller and the delegate allowing the controller to call the same method on the delegate + * class. * * @param message to be printed to the screen */ diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java b/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java index c54f611ee213..add1d71e6a11 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/PrinterController.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,13 +20,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.delegation.simple; /** - * Delegator Class to delegate the implementation of the Printer. - * This ensures two things: - * - when the actual implementation of the Printer class changes the delegation will still be operational - * - the actual benefit is observed when there are more than one implementors and they share a delegation control + * Delegator Class to delegate the implementation of the Printer. This ensures two things: - when + * the actual implementation of the Printer class changes the delegation will still be operational - + * the actual benefit is observed when there are more than one implementors and they share a + * delegation control */ public class PrinterController implements Printer { @@ -37,10 +38,10 @@ public PrinterController(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. + * 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 */ 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 index 8f024122f15f..5d7c59c863c7 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.delegation.simple.printers; import com.iluwatar.delegation.simple.Printer; @@ -27,8 +28,8 @@ import org.slf4j.LoggerFactory; /** - * Specialised Implementation of {@link Printer} for a Canon Printer, in - * this case the message to be printed is appended to "Canon 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 */ 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 index fb763d21a862..67bd00ac8881 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.delegation.simple.printers; import com.iluwatar.delegation.simple.Printer; @@ -27,8 +28,8 @@ import org.slf4j.LoggerFactory; /** - * Specialised Implementation of {@link Printer} for a Epson Printer, in - * this case the message to be printed is appended to "Epson 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 */ 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 index d80bb7aa7c9f..082e0054a2f1 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.delegation.simple.printers; import com.iluwatar.delegation.simple.Printer; @@ -27,8 +28,8 @@ import org.slf4j.LoggerFactory; /** - * Specialised Implementation of {@link Printer} for a HP Printer, in - * this case the message to be printed is appended to "HP 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 */ diff --git a/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java b/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java index ffdc96b807bd..2865c76c1e78 100644 --- a/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java +++ b/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.delegation.simple; import org.junit.jupiter.api.Test; @@ -31,8 +32,7 @@ public class AppTest { @Test public void test() { - String[] args = {}; - App.main(args); + App.main(new String[]{}); } } diff --git a/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java b/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java index 422da5685ffc..2da1e05713eb 100644 --- a/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java +++ b/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,24 +20,24 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.delegation.simple; +import static org.junit.jupiter.api.Assertions.assertEquals; + import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; import com.iluwatar.delegation.simple.printers.CanonPrinter; import com.iluwatar.delegation.simple.printers.EpsonPrinter; import com.iluwatar.delegation.simple.printers.HpPrinter; +import java.util.LinkedList; +import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -import java.util.LinkedList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Test for Delegation Pattern */ @@ -59,7 +59,7 @@ public void tearDown() { @Test public void testCanonPrinter() throws Exception { - PrinterController printerController = new PrinterController(new CanonPrinter()); + var printerController = new PrinterController(new CanonPrinter()); printerController.print(MESSAGE); assertEquals("Canon Printer : Test Message Printed", appender.getLastMessage()); @@ -67,7 +67,7 @@ public void testCanonPrinter() throws Exception { @Test public void testHpPrinter() throws Exception { - PrinterController printerController = new PrinterController(new HpPrinter()); + var printerController = new PrinterController(new HpPrinter()); printerController.print(MESSAGE); assertEquals("HP Printer : Test Message Printed", appender.getLastMessage()); @@ -75,7 +75,7 @@ public void testHpPrinter() throws Exception { @Test public void testEpsonPrinter() throws Exception { - PrinterController printerController = new PrinterController(new EpsonPrinter()); + var printerController = new PrinterController(new EpsonPrinter()); printerController.print(MESSAGE); assertEquals("Epson Printer : Test Message Printed", appender.getLastMessage()); diff --git a/dependency-injection/README.md b/dependency-injection/README.md index 735f589b1555..90edd4061bae 100644 --- a/dependency-injection/README.md +++ b/dependency-injection/README.md @@ -3,10 +3,9 @@ layout: pattern title: Dependency Injection folder: dependency-injection permalink: /patterns/dependency-injection/ -categories: Behavioral +categories: Creational tags: - - Java - - Difficulty-Beginner + - Decoupling --- ## Intent @@ -17,6 +16,7 @@ pattern separates the creation of a client's dependencies from its own behavior, which allows program designs to be loosely coupled and to follow the inversion of control and single responsibility principles. +## Class diagram ![alt text](./etc/dependency-injection.png "Dependency Injection") ## Applicability diff --git a/dependency-injection/etc/dependency-injection.urm.puml b/dependency-injection/etc/dependency-injection.urm.puml new file mode 100644 index 000000000000..84ed3c6dd321 --- /dev/null +++ b/dependency-injection/etc/dependency-injection.urm.puml @@ -0,0 +1,57 @@ +@startuml +package com.iluwatar.dependency.injection { + class AdvancedSorceress { + - tobacco : Tobacco + + AdvancedSorceress() + + setTobacco(tobacco : Tobacco) + + smoke() + } + class AdvancedWizard { + - tobacco : Tobacco + + AdvancedWizard(tobacco : Tobacco) + + smoke() + } + class App { + + App() + + main(args : String[]) {static} + } + class GuiceWizard { + - tobacco : Tobacco + + GuiceWizard(tobacco : Tobacco) + + smoke() + } + class OldTobyTobacco { + + OldTobyTobacco() + } + class RivendellTobacco { + + RivendellTobacco() + } + class SecondBreakfastTobacco { + + SecondBreakfastTobacco() + } + class SimpleWizard { + - tobacco : OldTobyTobacco + + SimpleWizard() + + smoke() + } + abstract class Tobacco { + - LOGGER : Logger {static} + + Tobacco() + + smoke(wizard : Wizard) + } + interface Wizard { + + smoke() {abstract} + } +} +AdvancedSorceress --> "-tobacco" Tobacco +SimpleWizard --> "-tobacco" OldTobyTobacco +AdvancedWizard --> "-tobacco" Tobacco +GuiceWizard --> "-tobacco" Tobacco +AdvancedSorceress ..|> Wizard +AdvancedWizard ..|> Wizard +GuiceWizard ..|> Wizard +OldTobyTobacco --|> Tobacco +RivendellTobacco --|> Tobacco +SecondBreakfastTobacco --|> Tobacco +SimpleWizard ..|> Wizard +@enduml \ No newline at end of file diff --git a/dependency-injection/pom.xml b/dependency-injection/pom.xml index 631fc6b7c03f..9baffe3824ab 100644 --- a/dependency-injection/pom.xml +++ b/dependency-injection/pom.xml @@ -2,7 +2,7 @@ "-df" DataFetcher +@enduml \ No newline at end of file diff --git a/dirty-flag/pom.xml b/dirty-flag/pom.xml index e91d6c9f4c25..c014cd41e193 100644 --- a/dirty-flag/pom.xml +++ b/dirty-flag/pom.xml @@ -2,7 +2,7 @@ + + + java-design-patterns + com.iluwatar + 1.23.0-SNAPSHOT + + 4.0.0 + + double-buffer + + + + junit + junit + + + org.apache.commons + commons-lang3 + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.doublebuffer.App + + + + + + + + + + \ No newline at end of file diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/App.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/App.java new file mode 100644 index 000000000000..00aa3b114654 --- /dev/null +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/App.java @@ -0,0 +1,79 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.doublebuffer; + +import java.util.List; +import org.apache.commons.lang3.tuple.MutablePair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Double buffering is a term used to describe a device that has two buffers. The usage of multiple + * buffers increases the overall throughput of a device and helps prevents bottlenecks. This example + * shows using double buffer pattern on graphics. It is used to show one image or frame while a + * separate frame is being buffered to be shown next. This method makes animations and games look + * more realistic than the same done in a single buffer mode. + */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Program main entry point. + * + * @param args runtime arguments + */ + public static void main(String[] args) { + final var scene = new Scene(); + var drawPixels1 = List.of( + new MutablePair<>(1, 1), + new MutablePair<>(5, 6), + new MutablePair<>(3, 2) + ); + scene.draw(drawPixels1); + var buffer1 = scene.getBuffer(); + printBlackPixelCoordinate(buffer1); + + var drawPixels2 = List.of( + new MutablePair<>(3, 7), + new MutablePair<>(6, 1) + ); + scene.draw(drawPixels2); + var buffer2 = scene.getBuffer(); + printBlackPixelCoordinate(buffer2); + } + + private static void printBlackPixelCoordinate(Buffer buffer) { + StringBuilder log = new StringBuilder("Black Pixels: "); + var pixels = buffer.getPixels(); + for (var i = 0; i < pixels.length; ++i) { + if (pixels[i] == Pixel.BLACK) { + var y = i / FrameBuffer.WIDTH; + var x = i % FrameBuffer.WIDTH; + log.append(" (").append(x).append(", ").append(y).append(")"); + } + } + LOGGER.info(log.toString()); + } +} diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Buffer.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Buffer.java new file mode 100644 index 000000000000..78877c3d9f32 --- /dev/null +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Buffer.java @@ -0,0 +1,59 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.doublebuffer; + +/** + * Buffer interface. + */ +public interface Buffer { + + /** + * Clear the pixel in (x, y). + * + * @param x X coordinate + * @param y Y coordinate + */ + void clear(int x, int y); + + /** + * Draw the pixel in (x, y). + * + * @param x X coordinate + * @param y Y coordinate + */ + void draw(int x, int y); + + /** + * Clear all the pixels. + */ + void clearAll(); + + /** + * Get all the pixels. + * + * @return pixel list + */ + Pixel[] getPixels(); + +} diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java new file mode 100644 index 000000000000..5f683cf1ead4 --- /dev/null +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java @@ -0,0 +1,65 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.doublebuffer; + +import java.util.Arrays; + +/** + * FrameBuffer implementation class. + */ +public class FrameBuffer implements Buffer { + + public static final int WIDTH = 10; + public static final int HEIGHT = 8; + + private Pixel[] pixels = new Pixel[WIDTH * HEIGHT]; + + public FrameBuffer() { + clearAll(); + } + + @Override + public void clear(int x, int y) { + pixels[getIndex(x, y)] = Pixel.WHITE; + } + + @Override + public void draw(int x, int y) { + pixels[getIndex(x, y)] = Pixel.BLACK; + } + + @Override + public void clearAll() { + Arrays.fill(pixels, Pixel.WHITE); + } + + @Override + public Pixel[] getPixels() { + return pixels; + } + + private int getIndex(int x, int y) { + return x + WIDTH * y; + } +} diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java new file mode 100644 index 000000000000..501797743e81 --- /dev/null +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java @@ -0,0 +1,39 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.doublebuffer; + +/** + * Pixel enum. Each pixel can be white (not drawn) or black (drawn). + */ +public enum Pixel { + + WHITE(0), + BLACK(1); + + private int color; + + Pixel(int color) { + this.color = color; + } +} diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java new file mode 100644 index 000000000000..2c150391835b --- /dev/null +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java @@ -0,0 +1,86 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.doublebuffer; + +import java.util.List; +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Scene class. Render the output frame. + */ +public class Scene { + + private static final Logger LOGGER = LoggerFactory.getLogger(Scene.class); + + private Buffer[] frameBuffers; + + private int current; + + private int next; + + /** + * Constructor of Scene. + */ + public Scene() { + frameBuffers = new FrameBuffer[2]; + frameBuffers[0] = new FrameBuffer(); + frameBuffers[1] = new FrameBuffer(); + current = 0; + next = 1; + } + + /** + * Draw the next frame. + * + * @param coordinateList list of pixels of which the color should be black + */ + public void draw(List> coordinateList) { + LOGGER.info("Start drawing next frame"); + LOGGER.info("Current buffer: " + current + " Next buffer: " + next); + frameBuffers[next].clearAll(); + coordinateList.forEach(coordinate -> { + var x = coordinate.getKey(); + var y = coordinate.getValue(); + frameBuffers[next].draw(x, y); + }); + LOGGER.info("Swap current and next buffer"); + swap(); + LOGGER.info("Finish swapping"); + LOGGER.info("Current buffer: " + current + " Next buffer: " + next); + } + + public Buffer getBuffer() { + LOGGER.info("Get current buffer: " + current); + return frameBuffers[current]; + } + + private void swap() { + current = current ^ next; + next = current ^ next; + current = current ^ next; + } + +} diff --git a/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java b/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java new file mode 100644 index 000000000000..eb89a4044307 --- /dev/null +++ b/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java @@ -0,0 +1,38 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.doublebuffer; + +import org.junit.Test; + +/** + * App unit test. + */ +public class AppTest { + + @Test + public void testMain() { + App.main(new String[]{}); + } + +} diff --git a/double-buffer/src/test/java/com/iluwatar/doublebuffer/FrameBufferTest.java b/double-buffer/src/test/java/com/iluwatar/doublebuffer/FrameBufferTest.java new file mode 100644 index 000000000000..2ade416cd4d5 --- /dev/null +++ b/double-buffer/src/test/java/com/iluwatar/doublebuffer/FrameBufferTest.java @@ -0,0 +1,92 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.doublebuffer; + +import java.util.Arrays; +import org.junit.Assert; +import org.junit.Test; + +/** + * FrameBuffer unit test. + */ +public class FrameBufferTest { + + @Test + public void testClearAll() { + try { + var field = FrameBuffer.class.getDeclaredField("pixels"); + var pixels = new Pixel[FrameBuffer.HEIGHT * FrameBuffer.WIDTH]; + Arrays.fill(pixels, Pixel.WHITE); + pixels[0] = Pixel.BLACK; + var frameBuffer = new FrameBuffer(); + field.setAccessible(true); + field.set(frameBuffer, pixels); + frameBuffer.clearAll(); + Assert.assertEquals(Pixel.WHITE, frameBuffer.getPixels()[0]); + } catch (NoSuchFieldException | IllegalAccessException e) { + Assert.fail("Fail to modify field access."); + } + } + + @Test + public void testClear() { + try { + var field = FrameBuffer.class.getDeclaredField("pixels"); + var pixels = new Pixel[FrameBuffer.HEIGHT * FrameBuffer.WIDTH]; + Arrays.fill(pixels, Pixel.WHITE); + pixels[0] = Pixel.BLACK; + var frameBuffer = new FrameBuffer(); + field.setAccessible(true); + field.set(frameBuffer, pixels); + frameBuffer.clear(0, 0); + Assert.assertEquals(Pixel.WHITE, frameBuffer.getPixels()[0]); + } catch (NoSuchFieldException | IllegalAccessException e) { + Assert.fail("Fail to modify field access."); + } + } + + @Test + public void testDraw() { + var frameBuffer = new FrameBuffer(); + frameBuffer.draw(0, 0); + Assert.assertEquals(Pixel.BLACK, frameBuffer.getPixels()[0]); + } + + @Test + public void testGetPixels() { + try { + var field = FrameBuffer.class.getDeclaredField("pixels"); + var pixels = new Pixel[FrameBuffer.HEIGHT * FrameBuffer.WIDTH]; + Arrays.fill(pixels, Pixel.WHITE); + pixels[0] = Pixel.BLACK; + var frameBuffer = new FrameBuffer(); + field.setAccessible(true); + field.set(frameBuffer, pixels); + Assert.assertEquals(pixels, frameBuffer.getPixels()); + } catch (NoSuchFieldException | IllegalAccessException e) { + Assert.fail("Fail to modify field access."); + } + } + +} diff --git a/double-buffer/src/test/java/com/iluwatar/doublebuffer/SceneTest.java b/double-buffer/src/test/java/com/iluwatar/doublebuffer/SceneTest.java new file mode 100644 index 000000000000..cd17d21916d2 --- /dev/null +++ b/double-buffer/src/test/java/com/iluwatar/doublebuffer/SceneTest.java @@ -0,0 +1,72 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.doublebuffer; + +import java.util.ArrayList; +import org.junit.Assert; +import org.junit.Test; + +/** + * Scene unit tests. + */ +public class SceneTest { + + @Test + public void testGetBuffer() { + try { + var scene = new Scene(); + var field1 = Scene.class.getDeclaredField("current"); + field1.setAccessible(true); + field1.set(scene, 0); + var frameBuffers = new FrameBuffer[2]; + var frameBuffer = new FrameBuffer(); + frameBuffer.draw(0, 0); + frameBuffers[0] = frameBuffer; + var field2 = Scene.class.getDeclaredField("frameBuffers"); + field2.setAccessible(true); + field2.set(scene, frameBuffers); + Assert.assertEquals(frameBuffer, scene.getBuffer()); + } catch (NoSuchFieldException | IllegalAccessException e) { + Assert.fail("Fail to access private field."); + } + } + + @Test + public void testDraw() { + try { + var scene = new Scene(); + var field1 = Scene.class.getDeclaredField("current"); + var field2 = Scene.class.getDeclaredField("next"); + field1.setAccessible(true); + field1.set(scene, 0); + field2.setAccessible(true); + field2.set(scene, 1); + scene.draw(new ArrayList<>()); + Assert.assertEquals(1, field1.get(scene)); + Assert.assertEquals(0, field2.get(scene)); + } catch (NoSuchFieldException | IllegalAccessException e) { + Assert.fail("Fail to access private field"); + } + } +} diff --git a/double-checked-locking/README.md b/double-checked-locking/README.md index da1fdd1a2ee7..64bf1522ce28 100644 --- a/double-checked-locking/README.md +++ b/double-checked-locking/README.md @@ -3,11 +3,9 @@ layout: pattern title: Double Checked Locking folder: double-checked-locking permalink: /patterns/double-checked-locking/ -categories: Concurrency +categories: Idiom tags: - - Java - - Difficulty-Beginner - - Idiom + - Performance --- ## Intent @@ -16,6 +14,7 @@ locking criterion (the "lock hint") without actually acquiring the lock. Only if the locking criterion check indicates that locking is required does the actual locking logic proceed. +## Class diagram ![alt text](./etc/double_checked_locking_1.png "Double Checked Locking") ## Applicability diff --git a/double-checked-locking/etc/double-checked-locking.urm.puml b/double-checked-locking/etc/double-checked-locking.urm.puml new file mode 100644 index 000000000000..242519fce77c --- /dev/null +++ b/double-checked-locking/etc/double-checked-locking.urm.puml @@ -0,0 +1,22 @@ +@startuml +package com.iluwatar.doublechecked.locking { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + class Inventory { + - LOGGER : Logger {static} + - inventorySize : int + - items : List + - lock : Lock + + Inventory(inventorySize : int) + + addItem(item : Item) : boolean + + getItems() : List + } + class Item { + + Item() + } +} +Inventory --> "-items" Item +@enduml \ No newline at end of file diff --git a/double-checked-locking/pom.xml b/double-checked-locking/pom.xml index 999f78d878af..a77546386723 100644 --- a/double-checked-locking/pom.xml +++ b/double-checked-locking/pom.xml @@ -1,7 +1,7 @@ com.iluwatar java-design-patterns - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT @@ -38,7 +43,6 @@ org.apache.camel camel-core - ${camel.version} @@ -47,17 +51,21 @@ ${camel.version} + + com.sun.xml.bind + jaxb-impl + + + javax.xml.bind + jaxb-api + + com.github.sbrannen spring-test-junit5 test - - org.junit.jupiter - junit-jupiter-api - test - org.junit.jupiter junit-jupiter-engine @@ -75,4 +83,24 @@ - \ No newline at end of file + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.epi.aggregator.App + + + + + + + + + diff --git a/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/App.java b/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/App.java index d55f24e3ddad..34fa6e3077ba 100644 --- a/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/App.java +++ b/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,48 +20,45 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.eip.aggregator; import org.apache.camel.CamelContext; import org.apache.camel.builder.RouteBuilder; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.ConfigurableApplicationContext; /** - * Sometimes in enterprise systems there is a need to group incoming data in order to process it as a whole. For example - * you may need to gather offers and after defined number of offers has been received you would like to choose the one - * with the best parameters. - * - *

- * Aggregator allows you to merge messages based on defined criteria and parameters. It gathers original messages, - * applies aggregation strategy and upon fulfilling given criteria, releasing merged messages. - *

+ * Sometimes in enterprise systems there is a need to group incoming data in order to process it as + * a whole. For example you may need to gather offers and after defined number of offers has been + * received you would like to choose the one with the best parameters. * + *

Aggregator allows you to merge messages based on defined criteria and parameters. It gathers + * original messages, applies aggregation strategy and upon fulfilling given criteria, releasing + * merged messages. */ @SpringBootApplication public class App { /** - * Program entry point. It starts Spring Boot application and using Apache Camel it auto-configures routes. + * Program entry point. It starts Spring Boot application and using Apache Camel it + * auto-configures routes. * * @param args command line args */ public static void main(String[] args) throws Exception { // Run Spring Boot application and obtain ApplicationContext - ConfigurableApplicationContext context = SpringApplication.run(App.class, args); + var context = SpringApplication.run(App.class, args); // Get CamelContext from ApplicationContext - CamelContext camelContext = (CamelContext) context.getBean("camelContext"); + var camelContext = (CamelContext) context.getBean("camelContext"); // Add a new routes that will handle endpoints form SplitterRoute class. camelContext.addRoutes(new RouteBuilder() { - @Override - public void configure() throws Exception { + public void configure() { from("{{endpoint}}").log("ENDPOINT: ${body}"); } - }); // Add producer that will send test message to an entry point in WireTapRoute diff --git a/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/routes/AggregatorRoute.java b/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/routes/AggregatorRoute.java index 5f130ff69159..2465fd960aaa 100644 --- a/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/routes/AggregatorRoute.java +++ b/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/routes/AggregatorRoute.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.eip.aggregator.routes; import org.apache.camel.builder.RouteBuilder; @@ -29,16 +30,15 @@ /** * Sample aggregator route definition. * - *

- * It consumes messages out of the direct:entry entry point and forwards them to direct:endpoint. - * Route accepts messages containing String as a body, it aggregates the messages based on the settings and forwards - * them as CSV to the output chanel. + *

It consumes messages out of the direct:entry entry point and forwards them to + * direct:endpoint. Route accepts messages containing String as a body, it aggregates the + * messages based on the settings and forwards them as CSV to the output chanel. * - * Settings for the aggregation are: aggregate until 3 messages are bundled or wait 2000ms before sending bundled - * messages further. - *

+ *

Settings for the aggregation are: aggregate until 3 messages are bundled or wait 2000ms + * before sending bundled messages further. * - * In this example input/output endpoints names are stored in application.properties file. + *

In this example input/output endpoints names are stored in application.properties + * file. */ @Component public class AggregatorRoute extends RouteBuilder { @@ -47,8 +47,7 @@ public class AggregatorRoute extends RouteBuilder { private MessageAggregationStrategy aggregator; /** - * Configures the route - * @throws Exception in case of exception during configuration + * Configures the route. */ @Override public void configure() { diff --git a/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/routes/MessageAggregationStrategy.java b/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/routes/MessageAggregationStrategy.java index 4b5e4cb2f501..9fd4c933acab 100644 --- a/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/routes/MessageAggregationStrategy.java +++ b/eip-aggregator/src/main/java/com/iluwatar/eip/aggregator/routes/MessageAggregationStrategy.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.eip.aggregator.routes; import org.apache.camel.Exchange; @@ -27,8 +28,8 @@ import org.springframework.stereotype.Component; /** - * Aggregation strategy joining bodies of messages. If message is first one oldMessage is null. All changes are - * made on IN messages. + * Aggregation strategy joining bodies of messages. If message is first one oldMessage is + * null. All changes are made on IN messages. */ @Component public class MessageAggregationStrategy implements AggregationStrategy { @@ -39,8 +40,8 @@ public Exchange aggregate(Exchange oldExchange, Exchange newExchange) { return newExchange; } - String in1 = (String) oldExchange.getIn().getBody(); - String in2 = (String) newExchange.getIn().getBody(); + var in1 = (String) oldExchange.getIn().getBody(); + var in2 = (String) newExchange.getIn().getBody(); oldExchange.getIn().setBody(in1 + ";" + in2); diff --git a/eip-aggregator/src/main/resources/application.properties b/eip-aggregator/src/main/resources/application.properties index 044833fc1be1..34febce1a9c8 100644 --- a/eip-aggregator/src/main/resources/application.properties +++ b/eip-aggregator/src/main/resources/application.properties @@ -1,6 +1,6 @@ # # The MIT License -# Copyright (c) 2014 Ilkka Seppälä +# Copyright © 2014-2019 Ilkka Seppälä # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/AppTest.java b/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/AppTest.java index 40a33678670f..ed604e8c2060 100644 --- a/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/AppTest.java +++ b/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.eip.aggregator; import org.junit.jupiter.api.Test; @@ -31,7 +32,6 @@ public class AppTest { @Test public void testMain() throws Exception { - String[] args = {}; - App.main(args); + App.main(new String[]{}); } } diff --git a/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/routes/AggregatorRouteTest.java b/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/routes/AggregatorRouteTest.java index 2c7d207d6f53..e1c6f5b2bc62 100644 --- a/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/routes/AggregatorRouteTest.java +++ b/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/routes/AggregatorRouteTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,31 +20,32 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.eip.aggregator.routes; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.apache.camel.EndpointInject; import org.apache.camel.ProducerTemplate; import org.apache.camel.component.mock.MockEndpoint; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.ComponentScan; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Test class for AggregatorRoute. *

- * In order for it to work we have to mock endpoints we want to read/write to. To mock those we need to substitute - * original endpoint names to mocks. + * In order for it to work we have to mock endpoints we want to read/write to. To mock those we need + * to substitute original endpoint names to mocks. *

*/ @ExtendWith(SpringExtension.class) -@SpringApplicationConfiguration(classes = AggregatorRouteTest.class) +@SpringBootTest(classes = AggregatorRouteTest.class) @ActiveProfiles("test") @EnableAutoConfiguration @ComponentScan @@ -58,6 +59,7 @@ public class AggregatorRouteTest { /** * Test if endpoint receives three separate messages. + * * @throws Exception in case of en exception during the test */ @Test @@ -75,10 +77,10 @@ public void testSplitter() throws Exception { endpoint.expectedMessageCount(2); endpoint.assertIsSatisfied(); - String body = (String) endpoint.getReceivedExchanges().get(0).getIn().getBody(); + var body = (String) endpoint.getReceivedExchanges().get(0).getIn().getBody(); assertEquals(3, body.split(";").length); - String body2 = (String) endpoint.getReceivedExchanges().get(1).getIn().getBody(); + var body2 = (String) endpoint.getReceivedExchanges().get(1).getIn().getBody(); assertEquals(2, body2.split(";").length); } } diff --git a/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/routes/MessageAggregationStrategyTest.java b/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/routes/MessageAggregationStrategyTest.java index 7a7a15154677..8325e725521b 100644 --- a/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/routes/MessageAggregationStrategyTest.java +++ b/eip-aggregator/src/test/java/com/iluwatar/eip/aggregator/routes/MessageAggregationStrategyTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,15 +20,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.eip.aggregator.routes; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.apache.camel.CamelContext; -import org.apache.camel.Exchange; import org.apache.camel.impl.DefaultExchange; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Tests MessageAggregationStrategy */ @@ -36,27 +36,27 @@ public class MessageAggregationStrategyTest { @Test public void testAggregate() { - MessageAggregationStrategy mas = new MessageAggregationStrategy(); - Exchange oldExchange = new DefaultExchange((CamelContext) null); + var mas = new MessageAggregationStrategy(); + var oldExchange = new DefaultExchange((CamelContext) null); oldExchange.getIn().setBody("TEST1"); - Exchange newExchange = new DefaultExchange((CamelContext) null); + var newExchange = new DefaultExchange((CamelContext) null); newExchange.getIn().setBody("TEST2"); - Exchange output = mas.aggregate(oldExchange, newExchange); - String outputBody = (String) output.getIn().getBody(); + var output = mas.aggregate(oldExchange, newExchange); + var outputBody = (String) output.getIn().getBody(); assertEquals("TEST1;TEST2", outputBody); } @Test public void testAggregateOldNull() { - MessageAggregationStrategy mas = new MessageAggregationStrategy(); + var mas = new MessageAggregationStrategy(); - Exchange newExchange = new DefaultExchange((CamelContext) null); + var newExchange = new DefaultExchange((CamelContext) null); newExchange.getIn().setBody("TEST2"); - Exchange output = mas.aggregate(null, newExchange); - String outputBody = (String) output.getIn().getBody(); + var output = mas.aggregate(null, newExchange); + var outputBody = (String) output.getIn().getBody(); assertEquals(newExchange, output); assertEquals("TEST2", outputBody); diff --git a/eip-aggregator/src/test/resources/application-test.properties b/eip-aggregator/src/test/resources/application-test.properties index 8d6ecbbd3bf2..0cab1156b7fd 100644 --- a/eip-aggregator/src/test/resources/application-test.properties +++ b/eip-aggregator/src/test/resources/application-test.properties @@ -1,6 +1,6 @@ # # The MIT License -# Copyright (c) 2014 Ilkka Seppälä +# Copyright © 2014-2019 Ilkka Seppälä # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/eip-message-channel/README.md b/eip-message-channel/README.md index 606619619e8f..8e2f5dc8f203 100644 --- a/eip-message-channel/README.md +++ b/eip-message-channel/README.md @@ -5,15 +5,14 @@ folder: eip-message-channel permalink: /patterns/eip-message-channel/ categories: Integration tags: - - Java - - EIP - - Apache Camel™ + - Enterprise Integration Pattern --- ## Intent When two applications communicate using a messaging system they do it by using logical addresses of the system, so called Message Channels. +## Class diagram ![alt text](./etc/message-channel.png "Message Channel") ## Applicability diff --git a/eip-message-channel/etc/eip-message-channel.urm.puml b/eip-message-channel/etc/eip-message-channel.urm.puml new file mode 100644 index 000000000000..38e2369ce562 --- /dev/null +++ b/eip-message-channel/etc/eip-message-channel.urm.puml @@ -0,0 +1,9 @@ +@startuml +package com.iluwatar.eip.message.channel { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } +} +@enduml \ No newline at end of file diff --git a/eip-message-channel/pom.xml b/eip-message-channel/pom.xml index 9b297f72995e..bea72b1f9110 100644 --- a/eip-message-channel/pom.xml +++ b/eip-message-channel/pom.xml @@ -2,7 +2,7 @@ com.iluwatar java-design-patterns - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT @@ -38,7 +43,6 @@ org.apache.camel camel-core - ${camel.version} @@ -47,17 +51,22 @@ ${camel.version} + + com.sun.xml.bind + jaxb-impl + + + javax.xml.bind + jaxb-api + + + com.github.sbrannen spring-test-junit5 test - - org.junit.jupiter - junit-jupiter-api - test - org.junit.jupiter junit-jupiter-engine @@ -75,4 +84,24 @@ - \ No newline at end of file + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.eip.splitter.App + + + + + + + + + + diff --git a/eip-splitter/src/main/java/com/iluwatar/eip/splitter/App.java b/eip-splitter/src/main/java/com/iluwatar/eip/splitter/App.java index ceadb5f8dc0e..2792e0be4c1c 100644 --- a/eip-splitter/src/main/java/com/iluwatar/eip/splitter/App.java +++ b/eip-splitter/src/main/java/com/iluwatar/eip/splitter/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,41 +20,42 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.eip.splitter; import org.apache.camel.CamelContext; import org.apache.camel.builder.RouteBuilder; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.ConfigurableApplicationContext; /** - * It is very common in integration systems that incoming messages consists of many items bundled together. For example - * an invoice document contains multiple invoice lines describing transaction (quantity, name of provided - * service/sold goods, price etc.). Such bundled messages may not be accepted by other systems. This is where splitter - * pattern comes in handy. It will take the whole document, split it based on given criteria and send individual - * items to the endpoint. + * It is very common in integration systems that incoming messages consists of many items bundled + * together. For example an invoice document contains multiple invoice lines describing transaction + * (quantity, name of provided service/sold goods, price etc.). Such bundled messages may not be + * accepted by other systems. This is where splitter pattern comes in handy. It will take the whole + * document, split it based on given criteria and send individual items to the endpoint. * *

- * Splitter allows you to split messages based on defined criteria. It takes original message, process it and send - * multiple parts to the output channel. It is not defined if it should keep the order of items though. + * Splitter allows you to split messages based on defined criteria. It takes original message, + * process it and send multiple parts to the output channel. It is not defined if it should keep the + * order of items though. *

- * */ @SpringBootApplication public class App { /** - * Program entry point. It starts Spring Boot application and using Apache Camel it auto-configures routes. + * Program entry point. It starts Spring Boot application and using Apache Camel it + * auto-configures routes. * * @param args command line args */ public static void main(String[] args) throws Exception { // Run Spring Boot application and obtain ApplicationContext - ConfigurableApplicationContext context = SpringApplication.run(App.class, args); + var context = SpringApplication.run(App.class, args); // Get CamelContext from ApplicationContext - CamelContext camelContext = (CamelContext) context.getBean("camelContext"); + var camelContext = (CamelContext) context.getBean("camelContext"); // Add a new routes that will handle endpoints form SplitterRoute class. camelContext.addRoutes(new RouteBuilder() { diff --git a/eip-splitter/src/main/java/com/iluwatar/eip/splitter/routes/SplitterRoute.java b/eip-splitter/src/main/java/com/iluwatar/eip/splitter/routes/SplitterRoute.java index f7eb28deaf11..4d2cb3efb0ab 100644 --- a/eip-splitter/src/main/java/com/iluwatar/eip/splitter/routes/SplitterRoute.java +++ b/eip-splitter/src/main/java/com/iluwatar/eip/splitter/routes/SplitterRoute.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.eip.splitter.routes; import org.apache.camel.builder.RouteBuilder; @@ -28,19 +29,19 @@ /** * Sample splitter route definition. * - *

- * It consumes messages out of the direct:entry entry point and forwards them to direct:endpoint. - * Route accepts messages having body of array or collection of objects. Splitter component split message body and - * forwards single objects to the endpoint. - *

+ *

It consumes messages out of the direct:entry entry point and forwards them to + * direct:endpoint. Route accepts messages having body of array or collection of objects. + * Splitter component split message body and forwards single objects to the endpoint. * - * In this example input/output endpoints names are stored in application.properties file. + *

In this example input/output endpoints names are stored in application.properties + * file. */ @Component public class SplitterRoute extends RouteBuilder { /** - * Configures the route + * Configures the route. + * * @throws Exception in case of exception during configuration */ @Override diff --git a/eip-splitter/src/main/resources/application.properties b/eip-splitter/src/main/resources/application.properties index 044833fc1be1..34febce1a9c8 100644 --- a/eip-splitter/src/main/resources/application.properties +++ b/eip-splitter/src/main/resources/application.properties @@ -1,6 +1,6 @@ # # The MIT License -# Copyright (c) 2014 Ilkka Seppälä +# Copyright © 2014-2019 Ilkka Seppälä # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/eip-splitter/src/test/java/com/iluwatar/eip/splitter/AppTest.java b/eip-splitter/src/test/java/com/iluwatar/eip/splitter/AppTest.java index fe3eca01e2db..1a7dfcb0a45d 100644 --- a/eip-splitter/src/test/java/com/iluwatar/eip/splitter/AppTest.java +++ b/eip-splitter/src/test/java/com/iluwatar/eip/splitter/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.eip.splitter; import org.junit.jupiter.api.Test; @@ -31,7 +32,6 @@ public class AppTest { @Test public void testMain() throws Exception { - String[] args = {}; - App.main(args); + App.main(new String[]{}); } } diff --git a/eip-splitter/src/test/java/com/iluwatar/eip/splitter/routes/SplitterRouteTest.java b/eip-splitter/src/test/java/com/iluwatar/eip/splitter/routes/SplitterRouteTest.java index 9257a4410186..037954609e2d 100644 --- a/eip-splitter/src/test/java/com/iluwatar/eip/splitter/routes/SplitterRouteTest.java +++ b/eip-splitter/src/test/java/com/iluwatar/eip/splitter/routes/SplitterRouteTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.eip.splitter.routes; import org.apache.camel.EndpointInject; @@ -28,7 +29,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.ComponentScan; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; @@ -37,12 +38,12 @@ /** * Test class for SplitterRoute. *

- * In order for it to work we have to mock endpoints we want to read/write to. To mock those we need to substitute - * original endpoint names to mocks. + * In order for it to work we have to mock endpoints we want to read/write to. To mock those we need + * to substitute original endpoint names to mocks. *

*/ @ExtendWith(SpringExtension.class) -@SpringApplicationConfiguration(classes = SplitterRouteTest.class) +@SpringBootTest(classes = SplitterRouteTest.class) @ActiveProfiles("test") @EnableAutoConfiguration @ComponentScan @@ -56,6 +57,7 @@ public class SplitterRouteTest { /** * Test if endpoint receives three separate messages. + * * @throws Exception in case of en exception during the test */ @Test @@ -63,7 +65,7 @@ public class SplitterRouteTest { public void testSplitter() throws Exception { // Three items in one entry message - entry.sendBody(new String[] {"TEST1", "TEST2", "TEST3"}); + entry.sendBody(new String[]{"TEST1", "TEST2", "TEST3"}); // Endpoint should have three different messages in the end order of the messages is not important endpoint.expectedMessageCount(3); diff --git a/eip-splitter/src/test/resources/application-test.properties b/eip-splitter/src/test/resources/application-test.properties index 8d6ecbbd3bf2..0cab1156b7fd 100644 --- a/eip-splitter/src/test/resources/application-test.properties +++ b/eip-splitter/src/test/resources/application-test.properties @@ -1,6 +1,6 @@ # # The MIT License -# Copyright (c) 2014 Ilkka Seppälä +# Copyright © 2014-2019 Ilkka Seppälä # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/eip-wire-tap/README.md b/eip-wire-tap/README.md index 31cbd9123603..83b43dfd1f48 100644 --- a/eip-wire-tap/README.md +++ b/eip-wire-tap/README.md @@ -5,9 +5,7 @@ folder: eip-wire-tap permalink: /patterns/eip-wire-tap/ categories: Integration tags: - - Java - - Difficulty-Intermittent - - EIP + - Enterprise Integration Pattern --- ## Intent @@ -15,6 +13,7 @@ In most integration cases there is a need to monitor the messages flowing throug by intercepting the message and redirecting it to a different location like console, filesystem or the database. It is important that such functionality should not modify the original message and influence the processing path. +## Diagram ![alt text](./etc/wiretap.gif "Wire Tap") ## Applicability diff --git a/eip-wire-tap/etc/eip-wire-tap.urm.puml b/eip-wire-tap/etc/eip-wire-tap.urm.puml new file mode 100644 index 000000000000..51ee99723cc8 --- /dev/null +++ b/eip-wire-tap/etc/eip-wire-tap.urm.puml @@ -0,0 +1,8 @@ +@startuml +package com.iluwatar.eip.wiretap { + class App { + + App() + + main(args : String[]) {static} + } +} +@enduml \ No newline at end of file diff --git a/eip-wire-tap/pom.xml b/eip-wire-tap/pom.xml index c9f569a554d8..06cbc33db7b8 100644 --- a/eip-wire-tap/pom.xml +++ b/eip-wire-tap/pom.xml @@ -1,15 +1,19 @@ com.iluwatar java-design-patterns - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT @@ -38,7 +43,6 @@ org.apache.camel camel-core - ${camel.version} @@ -47,17 +51,22 @@ ${camel.version} + + com.sun.xml.bind + jaxb-impl + + + javax.xml.bind + jaxb-api + + + com.github.sbrannen spring-test-junit5 test - - org.junit.jupiter - junit-jupiter-api - test - org.junit.jupiter junit-jupiter-engine @@ -75,4 +84,24 @@ - \ No newline at end of file + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.eip.wiretap.App + + + + + + + + + + diff --git a/eip-wire-tap/src/main/java/com/iluwatar/eip/wiretap/App.java b/eip-wire-tap/src/main/java/com/iluwatar/eip/wiretap/App.java index ca605cb78171..f3b5c11c1541 100644 --- a/eip-wire-tap/src/main/java/com/iluwatar/eip/wiretap/App.java +++ b/eip-wire-tap/src/main/java/com/iluwatar/eip/wiretap/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,39 +20,41 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.eip.wiretap; import org.apache.camel.CamelContext; import org.apache.camel.builder.RouteBuilder; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.ConfigurableApplicationContext; /** - * In most integration cases there is a need to monitor the messages flowing through the system. It is usually achieved - * by intercepting the message and redirecting it to a different location like console, filesystem or the database. - * It is important that such functionality should not modify the original message and influence the processing path. + * In most integration cases there is a need to monitor the messages flowing through the system. It + * is usually achieved by intercepting the message and redirecting it to a different location like + * console, filesystem or the database. It is important that such functionality should not modify + * the original message and influence the processing path. * *

- * Wire Tap allows you to route messages to a separate location while they are being forwarded to the ultimate - * destination. It basically consumes messages of the input channel and publishes the unmodified message to both - * output channels. + * Wire Tap allows you to route messages to a separate location while they are being forwarded to + * the ultimate destination. It basically consumes messages of the input channel and publishes the + * unmodified message to both output channels. *

*/ @SpringBootApplication public class App { /** - * Program entry point. It starts Spring Boot application and using Apache Camel it auto-configures routes. + * Program entry point. It starts Spring Boot application and using Apache Camel it + * auto-configures routes. * * @param args command line args */ public static void main(String[] args) throws Exception { // Run Spring Boot application and obtain ApplicationContext - ConfigurableApplicationContext context = SpringApplication.run(App.class, args); + var context = SpringApplication.run(App.class, args); // Get CamelContext from ApplicationContext - CamelContext camelContext = (CamelContext) context.getBean("camelContext"); + var camelContext = (CamelContext) context.getBean("camelContext"); // Add a new routes that will handle endpoints form WireTapRoute class. camelContext.addRoutes(new RouteBuilder() { diff --git a/eip-wire-tap/src/main/java/com/iluwatar/eip/wiretap/routes/WireTapRoute.java b/eip-wire-tap/src/main/java/com/iluwatar/eip/wiretap/routes/WireTapRoute.java index 994ceacdb6bc..8eea7adbd2bb 100644 --- a/eip-wire-tap/src/main/java/com/iluwatar/eip/wiretap/routes/WireTapRoute.java +++ b/eip-wire-tap/src/main/java/com/iluwatar/eip/wiretap/routes/WireTapRoute.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.eip.wiretap.routes; import org.apache.camel.builder.RouteBuilder; @@ -28,19 +29,20 @@ /** * Sample wire tap route definition. * - *

- * It consumes messages out of the direct:entry entry point and forwards them to direct:endpoint. - * Wire Tap intercepts the message and sends it to direct:wireTap, which in turn forwards it to + *

It consumes messages out of the direct:entry entry point and forwards them to + * direct:endpoint. Wire Tap intercepts the message and sends it to direct:wireTap, + * which in turn forwards it to * direct:wireTapEndpoint. - *

* - * In this example input/output endpoints names are stored in application.properties file. + *

In this example input/output endpoints names are stored in application.properties + * file. */ @Component public class WireTapRoute extends RouteBuilder { /** - * Configures the route + * Configures the route. + * * @throws Exception in case of exception during configuration */ @Override diff --git a/eip-wire-tap/src/main/resources/application.properties b/eip-wire-tap/src/main/resources/application.properties index 90a152425a4e..13e13b959fd2 100644 --- a/eip-wire-tap/src/main/resources/application.properties +++ b/eip-wire-tap/src/main/resources/application.properties @@ -1,6 +1,6 @@ # # The MIT License -# Copyright (c) 2014 Ilkka Seppälä +# Copyright © 2014-2019 Ilkka Seppälä # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/AppTest.java b/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/AppTest.java index 673803df706b..31154043c31a 100644 --- a/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/AppTest.java +++ b/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.eip.wiretap; import org.junit.jupiter.api.Test; @@ -31,7 +32,6 @@ public class AppTest { @Test public void testMain() throws Exception { - String[] args = {}; - App.main(args); + App.main(new String[]{}); } } diff --git a/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/routes/WireTapRouteTest.java b/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/routes/WireTapRouteTest.java index 449f86208258..0d13374dc5f8 100644 --- a/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/routes/WireTapRouteTest.java +++ b/eip-wire-tap/src/test/java/com/iluwatar/eip/wiretap/routes/WireTapRouteTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,32 +20,32 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.eip.wiretap.routes; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.apache.camel.EndpointInject; -import org.apache.camel.Message; import org.apache.camel.ProducerTemplate; import org.apache.camel.component.mock.MockEndpoint; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.ComponentScan; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Test class for WireTapRoute. *

- * In order for it to work we have to mock endpoints we want to read/write to. To mock those we need to substitute - * original endpoint names to mocks. + * In order for it to work we have to mock endpoints we want to read/write to. To mock those we need + * to substitute original endpoint names to mocks. *

*/ @ExtendWith(SpringExtension.class) -@SpringApplicationConfiguration(classes = WireTapRouteTest.class) +@SpringBootTest(classes = WireTapRouteTest.class) @ActiveProfiles("test") @EnableAutoConfiguration @ComponentScan @@ -62,6 +62,7 @@ public class WireTapRouteTest { /** * Test if both endpoints receive exactly one message containing the same, unchanged body. + * * @throws Exception in case of en exception during the test */ @Test @@ -75,8 +76,8 @@ public void testWireTap() throws Exception { endpoint.assertIsSatisfied(); wireTapEndpoint.assertIsSatisfied(); - Message endpointIn = endpoint.getExchanges().get(0).getIn(); - Message wireTapEndpointIn = wireTapEndpoint.getExchanges().get(0).getIn(); + var endpointIn = endpoint.getExchanges().get(0).getIn(); + var wireTapEndpointIn = wireTapEndpoint.getExchanges().get(0).getIn(); assertEquals("TEST", endpointIn.getBody()); assertEquals("TEST", wireTapEndpointIn.getBody()); diff --git a/eip-wire-tap/src/test/resources/application-test.properties b/eip-wire-tap/src/test/resources/application-test.properties index e76faa1fc864..a64f4b316700 100644 --- a/eip-wire-tap/src/test/resources/application-test.properties +++ b/eip-wire-tap/src/test/resources/application-test.properties @@ -1,6 +1,6 @@ # # The MIT License -# Copyright (c) 2014 Ilkka Seppälä +# Copyright © 2014-2019 Ilkka Seppälä # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/etc/java-design-patterns.urm.puml b/etc/java-design-patterns.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/etc/java-design-patterns.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/event-aggregator/README.md b/event-aggregator/README.md index ac07869e7a89..5a8eb9685f60 100644 --- a/event-aggregator/README.md +++ b/event-aggregator/README.md @@ -5,8 +5,6 @@ folder: event-aggregator permalink: /patterns/event-aggregator/ categories: Structural tags: - - Java - - Difficulty-Beginner - Reactive --- @@ -18,6 +16,7 @@ requires a separate subscription. An Event Aggregator acts as a single source of events for many objects. It registers for all the events of the many objects allowing clients to register with just the aggregator. +## Class diagram ![alt text](./etc/classes.png "Event Aggregator") ## Applicability diff --git a/event-aggregator/etc/event-aggregator.urm.puml b/event-aggregator/etc/event-aggregator.urm.puml new file mode 100644 index 000000000000..341d7d65e02c --- /dev/null +++ b/event-aggregator/etc/event-aggregator.urm.puml @@ -0,0 +1,74 @@ +@startuml +package com.iluwatar.event.aggregator { + class App { + + App() + + main(args : String[]) {static} + } + enum Event { + + STARK_SIGHTED {static} + + TRAITOR_DETECTED {static} + + WARSHIPS_APPROACHING {static} + - description : String + + toString() : String + + valueOf(name : String) : Event {static} + + values() : Event[] {static} + } + abstract class EventEmitter { + - observers : List + + EventEmitter() + + EventEmitter(obs : EventObserver) + # notifyObservers(e : Event) + + registerObserver(obs : EventObserver) + + timePasses(Weekday) {abstract} + } + interface EventObserver { + + onEvent(Event) {abstract} + } + class KingJoffrey { + - LOGGER : Logger {static} + + KingJoffrey() + + onEvent(e : Event) + } + class KingsHand { + + KingsHand() + + KingsHand(obs : EventObserver) + + onEvent(e : Event) + + timePasses(day : Weekday) + } + class LordBaelish { + + LordBaelish() + + LordBaelish(obs : EventObserver) + + timePasses(day : Weekday) + } + class LordVarys { + + LordVarys() + + LordVarys(obs : EventObserver) + + timePasses(day : Weekday) + } + class Scout { + + Scout() + + Scout(obs : EventObserver) + + timePasses(day : Weekday) + } + enum Weekday { + + FRIDAY {static} + + MONDAY {static} + + SATURDAY {static} + + SUNDAY {static} + + THURSDAY {static} + + TUESDAY {static} + + WEDNESDAY {static} + - description : String + + toString() : String + + valueOf(name : String) : Weekday {static} + + values() : Weekday[] {static} + } +} +EventEmitter --> "-observers" EventObserver +KingJoffrey ..|> EventObserver +KingsHand ..|> EventObserver +KingsHand --|> EventEmitter +LordBaelish --|> EventEmitter +LordVarys --|> EventEmitter +Scout --|> EventEmitter +@enduml \ No newline at end of file diff --git a/event-aggregator/pom.xml b/event-aggregator/pom.xml index a6d473841d2e..5553de2e342a 100644 --- a/event-aggregator/pom.xml +++ b/event-aggregator/pom.xml @@ -1,7 +1,7 @@ "-eventListener" ThreadCompleteListener +Event ..|> IEvent +EventManager ..|> ThreadCompleteListener +@enduml \ No newline at end of file diff --git a/event-asynchronous/pom.xml b/event-asynchronous/pom.xml index 64e69adb629a..001b3b9a894c 100644 --- a/event-asynchronous/pom.xml +++ b/event-asynchronous/pom.xml @@ -2,7 +2,7 @@ "-user" User +UserUpdatedEvent --> "-user" User +AbstractEvent ..|> Event +UserCreatedEvent --|> AbstractEvent +UserUpdatedEvent --|> AbstractEvent +UserCreatedEventHandler ..|> Handler +UserUpdatedEventHandler ..|> Handler +@enduml \ No newline at end of file diff --git a/event-driven-architecture/pom.xml b/event-driven-architecture/pom.xml index 03ff66fed956..17d2795c4398 100644 --- a/event-driven-architecture/pom.xml +++ b/event-driven-architecture/pom.xml @@ -2,7 +2,7 @@ "-INSTANCE" Audio +@enduml \ No newline at end of file diff --git a/event-queue/pom.xml b/event-queue/pom.xml index 65bb489c2b9b..fd8ce99026e3 100644 --- a/event-queue/pom.xml +++ b/event-queue/pom.xml @@ -2,7 +2,7 @@ "-processorJournal" JsonFileJournal +AccountCreateEvent --|> DomainEvent +MoneyDepositEvent --|> DomainEvent +MoneyTransferEvent --|> DomainEvent +@enduml \ No newline at end of file diff --git a/event-sourcing/pom.xml b/event-sourcing/pom.xml index bae5e997073c..52bff8ff7746 100644 --- a/event-sourcing/pom.xml +++ b/event-sourcing/pom.xml @@ -2,7 +2,7 @@ "-workers" DwarvenMineWorker +Action ..+ DwarvenMineWorker +DwarvenCartOperator --|> DwarvenMineWorker +DwarvenGoldDigger --|> DwarvenMineWorker +DwarvenTunnelDigger --|> DwarvenMineWorker +@enduml \ No newline at end of file diff --git a/facade/pom.xml b/facade/pom.xml index a658e4376af2..a7fdb88f0809 100644 --- a/facade/pom.xml +++ b/facade/pom.xml @@ -2,7 +2,7 @@ "-weaponType" WeaponType +OrcWeapon --> "-weaponType" WeaponType +App --> "-blacksmith" Blacksmith +ElfBlacksmith ..|> Blacksmith +ElfWeapon ..|> Weapon +OrcBlacksmith ..|> Blacksmith +OrcWeapon ..|> Weapon +@enduml \ No newline at end of file diff --git a/factory-method/etc/presentation.html b/factory-method/etc/presentation.html deleted file mode 100644 index 2b7c789530da..000000000000 --- a/factory-method/etc/presentation.html +++ /dev/null @@ -1,185 +0,0 @@ - - - - - Design Patterns - Factory Method Presentation - - - - - - - - - \ No newline at end of file diff --git a/factory-method/pom.xml b/factory-method/pom.xml index 9ea706365db2..5f0358d4dcc1 100644 --- a/factory-method/pom.xml +++ b/factory-method/pom.xml @@ -2,7 +2,7 @@ "-freeGroup" User +PropertiesFeatureToggleVersion ..|> Service +TieredFeatureToggleVersion ..|> Service +@enduml \ No newline at end of file diff --git a/feature-toggle/pom.xml b/feature-toggle/pom.xml index 9f9f2678790c..13f646b801a5 100644 --- a/feature-toggle/pom.xml +++ b/feature-toggle/pom.xml @@ -2,7 +2,7 @@ - java-design-patterns com.iluwatar - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT 4.0.0 @@ -38,15 +37,29 @@ - - org.junit.jupiter - junit-jupiter-api - test - org.junit.jupiter junit-jupiter-engine test - \ No newline at end of file + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.featuretoggle.App + + + + + + + + + diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/App.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/App.java index 351936b2cd64..ceb926b63f68 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/App.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/App.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -28,75 +28,77 @@ import com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion; import com.iluwatar.featuretoggle.user.User; import com.iluwatar.featuretoggle.user.UserGroup; +import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Properties; - /** - * The Feature Toggle pattern allows for complete code executions to be turned on or off with ease. This allows features - * to be controlled by either dynamic methods just as {@link User} information or by {@link Properties}. In the App - * below there are two examples. Firstly the {@link Properties} version of the feature toggle, where the enhanced - * version of the welcome message which is personalised is turned either on or off at instance creation. This method - * is not as dynamic as the {@link User} driven version where the feature of the personalised welcome message is + * The Feature Toggle pattern allows for complete code executions to be turned on or off with ease. + * This allows features to be controlled by either dynamic methods just as {@link User} information + * or by {@link Properties}. In the App below there are two examples. Firstly the {@link Properties} + * version of the feature toggle, where the enhanced version of the welcome message which is + * personalised is turned either on or off at instance creation. This method is not as dynamic as + * the {@link User} driven version where the feature of the personalised welcome message is * dependant on the {@link UserGroup} the {@link User} is in. So if the user is a memeber of the * {@link UserGroup#isPaid(User)} then they get an ehanced version of the welcome message. * - * Note that this pattern can easily introduce code complexity, and if not kept in check can result in redundant - * unmaintained code within the codebase. - * + *

Note that this pattern can easily introduce code complexity, and if not kept in check can + * result in redundant unmaintained code within the codebase. */ public class App { private static final Logger LOGGER = LoggerFactory.getLogger(App.class); /** - * Block 1 shows the {@link PropertiesFeatureToggleVersion} being run with {@link Properties} setting the feature - * toggle to enabled. + * Block 1 shows the {@link PropertiesFeatureToggleVersion} being run with {@link Properties} + * setting the feature toggle to enabled. * - * Block 2 shows the {@link PropertiesFeatureToggleVersion} being run with {@link Properties} setting the feature - * toggle to disabled. Notice the difference with the printed welcome message the username is not included. + *

Block 2 shows the {@link PropertiesFeatureToggleVersion} being run with {@link Properties} + * setting the feature toggle to disabled. Notice the difference with the printed welcome message + * the username is not included. * - * Block 3 shows the {@link com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion} being - * set up with two users on who is on the free level, while the other is on the paid level. When the - * {@link Service#getWelcomeMessage(User)} is called with the paid {@link User} note that the welcome message - * contains their username, while the same service call with the free tier user is more generic. No username is - * printed. + *

Block 3 shows the {@link + * com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion} being set up with + * two users on who is on the free level, while the other is on the paid level. When the {@link + * Service#getWelcomeMessage(User)} is called with the paid {@link User} note that the welcome + * message contains their username, while the same service call with the free tier user is more + * generic. No username is printed. * - * @see User - * @see UserGroup - * @see Service - * @see PropertiesFeatureToggleVersion - * @see com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion + * @see User + * @see UserGroup + * @see Service + * @see PropertiesFeatureToggleVersion + * @see com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion */ public static void main(String[] args) { - final Properties properties = new Properties(); + final var properties = new Properties(); properties.put("enhancedWelcome", true); - Service service = new PropertiesFeatureToggleVersion(properties); - final String welcomeMessage = service.getWelcomeMessage(new User("Jamie No Code")); + var service = new PropertiesFeatureToggleVersion(properties); + final var welcomeMessage = service.getWelcomeMessage(new User("Jamie No Code")); LOGGER.info(welcomeMessage); // --------------------------------------------- - final Properties turnedOff = new Properties(); + final var turnedOff = new Properties(); turnedOff.put("enhancedWelcome", false); - Service turnedOffService = new PropertiesFeatureToggleVersion(turnedOff); - final String welcomeMessageturnedOff = turnedOffService.getWelcomeMessage(new User("Jamie No Code")); + var turnedOffService = new PropertiesFeatureToggleVersion(turnedOff); + final var welcomeMessageturnedOff = + turnedOffService.getWelcomeMessage(new User("Jamie No Code")); LOGGER.info(welcomeMessageturnedOff); // -------------------------------------------- - - Service service2 = new TieredFeatureToggleVersion(); - final User paidUser = new User("Jamie Coder"); - final User freeUser = new User("Alan Defect"); + var service2 = new TieredFeatureToggleVersion(); + + final var paidUser = new User("Jamie Coder"); + final var freeUser = new User("Alan Defect"); UserGroup.addUserToPaidGroup(paidUser); UserGroup.addUserToFreeGroup(freeUser); - final String welcomeMessagePaidUser = service2.getWelcomeMessage(paidUser); - final String welcomeMessageFreeUser = service2.getWelcomeMessage(freeUser); + final var welcomeMessagePaidUser = service2.getWelcomeMessage(paidUser); + final var welcomeMessageFreeUser = service2.getWelcomeMessage(freeUser); LOGGER.info(welcomeMessageFreeUser); LOGGER.info(welcomeMessagePaidUser); } diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/Service.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/Service.java index 284ccf2ab2cb..bd276f592878 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/Service.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/Service.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,15 +20,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.featuretoggle.pattern; import com.iluwatar.featuretoggle.user.User; /** - * Simple interfaces to allow the calling of the method to generate the welcome message for a given user. While there is - * a helper method to gather the the status of the feature toggle. In some cases there is no need for the - * {@link Service#isEnhanced()} in {@link com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion} - * where the toggle is determined by the actual {@link User}. + * Simple interfaces to allow the calling of the method to generate the welcome message for a given + * user. While there is a helper method to gather the the status of the feature toggle. In some + * cases there is no need for the {@link Service#isEnhanced()} in {@link + * com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion} where the toggle is + * determined by the actual {@link User}. * * @see com.iluwatar.featuretoggle.pattern.propertiesversion.PropertiesFeatureToggleVersion * @see com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java index 1ded334ec7d7..6e2281b9aa04 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,20 +20,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.featuretoggle.pattern.propertiesversion; import com.iluwatar.featuretoggle.pattern.Service; import com.iluwatar.featuretoggle.user.User; - import java.util.Properties; /** - * This example of the Feature Toogle pattern is less dynamic version than - * {@link com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion} where the feature is turned on - * or off at the time of creation of the service. This example uses simple Java {@link Properties} however it could as - * easily be done with an external configuration file loaded by Spring and so on. A good example of when to use this - * version of the feature toggle is when new features are being developed. So you could have a configuration property - * boolean named development or some sort of system environment variable. + * This example of the Feature Toogle pattern is less dynamic version than {@link + * com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion} where the feature is + * turned on or off at the time of creation of the service. This example uses simple Java {@link + * Properties} however it could as easily be done with an external configuration file loaded by + * Spring and so on. A good example of when to use this version of the feature toggle is when new + * features are being developed. So you could have a configuration property boolean named + * development or some sort of system environment variable. * * @see Service * @see com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion @@ -44,9 +45,10 @@ public class PropertiesFeatureToggleVersion implements Service { private boolean isEnhanced; /** - * Creates an instance of {@link PropertiesFeatureToggleVersion} using the passed {@link Properties} to determine, - * the status of the feature toggle {@link PropertiesFeatureToggleVersion#isEnhanced()}. There is also some defensive - * code to ensure the {@link Properties} passed are as expected. + * Creates an instance of {@link PropertiesFeatureToggleVersion} using the passed {@link + * Properties} to determine, the status of the feature toggle {@link + * PropertiesFeatureToggleVersion#isEnhanced()}. There is also some defensive code to ensure the + * {@link Properties} passed are as expected. * * @param properties {@link Properties} used to configure the service and toggle features. * @throws IllegalArgumentException when the passed {@link Properties} is not as expected @@ -65,14 +67,14 @@ public PropertiesFeatureToggleVersion(final Properties properties) { } /** - * Generate a welcome message based on the user being passed and the status of the feature toggle. If the enhanced - * version is enabled, then the message will be personalised with the name of the passed {@link User}. However if - * disabled then a generic version fo the message is returned. + * Generate a welcome message based on the user being passed and the status of the feature toggle. + * If the enhanced version is enabled, then the message will be personalised with the name of the + * passed {@link User}. However if disabled then a generic version fo the message is returned. * - * @param user the {@link User} to be displayed in the message if the enhanced version is enabled see - * {@link PropertiesFeatureToggleVersion#isEnhanced()}. If the enhanced version is enabled, then the - * message will be personalised with the name of the passed {@link User}. However if disabled then a - * generic version fo the message is returned. + * @param user the {@link User} to be displayed in the message if the enhanced version is enabled + * see {@link PropertiesFeatureToggleVersion#isEnhanced()}. If the enhanced version is + * enabled, then the message will be personalised with the name of the passed {@link + * User}. However if disabled then a generic version fo the message is returned. * @return Resulting welcome message. * @see User */ @@ -87,9 +89,9 @@ public String getWelcomeMessage(final User user) { } /** - * Method that checks if the welcome message to be returned is the enhanced venison or not. For this service it will - * see the value of the boolean that was set in the constructor - * {@link PropertiesFeatureToggleVersion#PropertiesFeatureToggleVersion(Properties)} + * Method that checks if the welcome message to be returned is the enhanced venison or not. For + * this service it will see the value of the boolean that was set in the constructor {@link + * PropertiesFeatureToggleVersion#PropertiesFeatureToggleVersion(Properties)} * * @return Boolean value {@code true} if enhanced. */ diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersion.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersion.java index 887c9f663bfe..448218070d63 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersion.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersion.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.featuretoggle.pattern.tieredversion; import com.iluwatar.featuretoggle.pattern.Service; @@ -27,11 +28,12 @@ import com.iluwatar.featuretoggle.user.UserGroup; /** - * This example of the Feature Toogle pattern shows how it could be implemented based on a {@link User}. Therefore - * showing its use within a tiered application where the paying users get access to different content or - * better versions of features. So in this instance a {@link User} is passed in and if they are found to be - * on the {@link UserGroup#isPaid(User)} they are welcomed with a personalised message. While the other is more - * generic. However this pattern is limited to simple examples such as the one below. + * This example of the Feature Toogle pattern shows how it could be implemented based on a {@link + * User}. Therefore showing its use within a tiered application where the paying users get access to + * different content or better versions of features. So in this instance a {@link User} is passed in + * and if they are found to be on the {@link UserGroup#isPaid(User)} they are welcomed with a + * personalised message. While the other is more generic. However this pattern is limited to simple + * examples such as the one below. * * @see Service * @see User @@ -41,12 +43,13 @@ public class TieredFeatureToggleVersion implements Service { /** - * Generates a welcome message from the passed {@link User}. The resulting message depends on the group of the - * {@link User}. So if the {@link User} is in the {@link UserGroup#paidGroup} then the enhanced version of the - * welcome message will be returned where the username is displayed. + * Generates a welcome message from the passed {@link User}. The resulting message depends on the + * group of the {@link User}. So if the {@link User} is in the {@link UserGroup#paidGroup} then + * the enhanced version of the welcome message will be returned where the username is displayed. * - * @param user the {@link User} to generate the welcome message for, different messages are displayed if the user is - * in the {@link UserGroup#isPaid(User)} or {@link UserGroup#freeGroup} + * @param user the {@link User} to generate the welcome message for, different messages are + * displayed if the user is in the {@link UserGroup#isPaid(User)} or {@link + * UserGroup#freeGroup} * @return Resulting welcome message. * @see User * @see UserGroup @@ -61,9 +64,9 @@ public String getWelcomeMessage(User user) { } /** - * Method that checks if the welcome message to be returned is the enhanced version. For this instance as the logic - * is driven by the user group. This method is a little redundant. However can be used to show that there is an - * enhanced version available. + * Method that checks if the welcome message to be returned is the enhanced version. For this + * instance as the logic is driven by the user group. This method is a little redundant. However + * can be used to show that there is an enhanced version available. * * @return Boolean value {@code true} if enhanced. */ diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/User.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/User.java index baf25aa8b7d6..5c660ca59b83 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/User.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/User.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,10 +20,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.featuretoggle.user; /** - * Used to demonstrate the purpose of the feature toggle. This class actually has nothing to do with the pattern. + * Used to demonstrate the purpose of the feature toggle. This class actually has nothing to do with + * the pattern. */ public class User { @@ -40,7 +42,9 @@ public User(String name) { /** * {@inheritDoc} - * @return The {@link String} representation of the User, in this case just return the name of the user. + * + * @return The {@link String} representation of the User, in this case just return the name of the + * user. */ @Override public String toString() { diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/UserGroup.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/UserGroup.java index cb84ec533274..524ea6ef8092 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/UserGroup.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/user/UserGroup.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,14 +20,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.featuretoggle.user; import java.util.ArrayList; import java.util.List; /** - * Contains the lists of users of different groups paid and free. Used to demonstrate the tiered example of feature - * toggle. Allowing certain features to be available to only certain groups of users. + * Contains the lists of users of different groups paid and free. Used to demonstrate the tiered + * example of feature toggle. Allowing certain features to be available to only certain groups of + * users. * * @see User */ @@ -75,7 +77,6 @@ public static void addUserToPaidGroup(final User user) throws IllegalArgumentExc * Method to take a {@link User} to determine if the user is in the {@link UserGroup#paidGroup}. * * @param user {@link User} to check if they are in the {@link UserGroup#paidGroup} - * * @return true if the {@link User} is in {@link UserGroup#paidGroup} */ public static boolean isPaid(User user) { diff --git a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersionTest.java b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersionTest.java index 8b85e93496ae..69641a7ada37 100644 --- a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersionTest.java +++ b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersionTest.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,17 +23,15 @@ package com.iluwatar.featuretoggle.pattern.propertiesversion; -import com.iluwatar.featuretoggle.pattern.Service; -import com.iluwatar.featuretoggle.user.User; -import org.junit.jupiter.api.Test; - -import java.util.Properties; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.iluwatar.featuretoggle.user.User; +import java.util.Properties; +import org.junit.jupiter.api.Test; + /** * Test Properties Toggle */ @@ -49,7 +47,7 @@ public void testNullPropertiesPassed() { @Test public void testNonBooleanProperty() { assertThrows(IllegalArgumentException.class, () -> { - final Properties properties = new Properties(); + final var properties = new Properties(); properties.setProperty("enhancedWelcome", "Something"); new PropertiesFeatureToggleVersion(properties); }); @@ -57,21 +55,21 @@ public void testNonBooleanProperty() { @Test public void testFeatureTurnedOn() { - final Properties properties = new Properties(); + final var properties = new Properties(); properties.put("enhancedWelcome", true); - Service service = new PropertiesFeatureToggleVersion(properties); + var service = new PropertiesFeatureToggleVersion(properties); assertTrue(service.isEnhanced()); - final String welcomeMessage = service.getWelcomeMessage(new User("Jamie No Code")); + final var welcomeMessage = service.getWelcomeMessage(new User("Jamie No Code")); assertEquals("Welcome Jamie No Code. You're using the enhanced welcome message.", welcomeMessage); } @Test public void testFeatureTurnedOff() { - final Properties properties = new Properties(); + final var properties = new Properties(); properties.put("enhancedWelcome", false); - Service service = new PropertiesFeatureToggleVersion(properties); + var service = new PropertiesFeatureToggleVersion(properties); assertFalse(service.isEnhanced()); - final String welcomeMessage = service.getWelcomeMessage(new User("Jamie No Code")); + final var welcomeMessage = service.getWelcomeMessage(new User("Jamie No Code")); assertEquals("Welcome to the application.", welcomeMessage); } } diff --git a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersionTest.java b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersionTest.java index 4755d569e09e..bb7195b7d2e6 100644 --- a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersionTest.java +++ b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersionTest.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,17 +20,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.featuretoggle.pattern.tieredversion; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.iluwatar.featuretoggle.pattern.Service; import com.iluwatar.featuretoggle.user.User; import com.iluwatar.featuretoggle.user.UserGroup; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - /** * Test Tiered Feature Toggle */ @@ -48,15 +49,15 @@ public void setUp() { @Test public void testGetWelcomeMessageForPaidUser() { - final String welcomeMessage = service.getWelcomeMessage(paidUser); - final String expected = "You're amazing Jamie Coder. Thanks for paying for this awesome software."; + final var welcomeMessage = service.getWelcomeMessage(paidUser); + final var expected = "You're amazing Jamie Coder. Thanks for paying for this awesome software."; assertEquals(expected, welcomeMessage); } @Test public void testGetWelcomeMessageForFreeUser() { - final String welcomeMessage = service.getWelcomeMessage(freeUser); - final String expected = "I suppose you can use this software."; + final var welcomeMessage = service.getWelcomeMessage(freeUser); + final var expected = "I suppose you can use this software."; assertEquals(expected, welcomeMessage); } diff --git a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/user/UserGroupTest.java b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/user/UserGroupTest.java index 2771655ddefa..9bc29fabfcf7 100644 --- a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/user/UserGroupTest.java +++ b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/user/UserGroupTest.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

+ * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

+ * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

+ * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,36 +20,37 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.featuretoggle.user; -import org.junit.jupiter.api.Test; +package com.iluwatar.featuretoggle.user; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + /** * Test User Group specific feature */ public class UserGroupTest { @Test - public void testAddUserToFreeGroup() throws Exception { - User user = new User("Free User"); + public void testAddUserToFreeGroup() { + var user = new User("Free User"); UserGroup.addUserToFreeGroup(user); assertFalse(UserGroup.isPaid(user)); } @Test - public void testAddUserToPaidGroup() throws Exception { - User user = new User("Paid User"); + public void testAddUserToPaidGroup() { + var user = new User("Paid User"); UserGroup.addUserToPaidGroup(user); assertTrue(UserGroup.isPaid(user)); } @Test - public void testAddUserToPaidWhenOnFree() throws Exception { - User user = new User("Paid User"); + public void testAddUserToPaidWhenOnFree() { + var user = new User("Paid User"); UserGroup.addUserToFreeGroup(user); assertThrows(IllegalArgumentException.class, () -> { UserGroup.addUserToPaidGroup(user); @@ -57,8 +58,8 @@ public void testAddUserToPaidWhenOnFree() throws Exception { } @Test - public void testAddUserToFreeWhenOnPaid() throws Exception { - User user = new User("Free User"); + public void testAddUserToFreeWhenOnPaid() { + var user = new User("Free User"); UserGroup.addUserToPaidGroup(user); assertThrows(IllegalArgumentException.class, () -> { UserGroup.addUserToFreeGroup(user); diff --git a/fluentinterface/README.md b/fluentinterface/README.md index 767792da79ab..3068468b91f0 100644 --- a/fluentinterface/README.md +++ b/fluentinterface/README.md @@ -3,11 +3,9 @@ layout: pattern title: Fluent Interface folder: fluentinterface permalink: /patterns/fluentinterface/ -categories: Other +categories: Functional tags: - - Java - - Difficulty-Intermediate - - Functional + - Reactive --- ## Intent @@ -21,6 +19,7 @@ A fluent interface can be implemented using any of * Static Factory Methods and Imports * Named parameters - can be simulated in Java using static factory methods. +## Class diagram ![Fluent Interface](./etc/fluentinterface.png "Fluent Interface") ## Applicability diff --git a/fluentinterface/etc/fluentinterface.urm.puml b/fluentinterface/etc/fluentinterface.urm.puml new file mode 100644 index 000000000000..ef71a0f4bace --- /dev/null +++ b/fluentinterface/etc/fluentinterface.urm.puml @@ -0,0 +1,72 @@ +@startuml +package com.iluwatar.fluentinterface.fluentiterable.simple { + class SimpleFluentIterable { + - iterable : Iterable + # SimpleFluentIterable(iterable : Iterable) + + asList() : List + + filter(predicate : Predicate) : FluentIterable + + first() : Optional + + first(count : int) : FluentIterable + + forEach(action : Consumer) + + from(iterable : Iterable) : FluentIterable {static} + + fromCopyOf(iterable : Iterable) : FluentIterable {static} + + getRemainingElementsCount() : int + + iterator() : Iterator + + last() : Optional + + last(count : int) : FluentIterable + + map(function : Function) : FluentIterable + + spliterator() : Spliterator + + toList(iterator : Iterator) : List {static} + } +} +package com.iluwatar.fluentinterface.app { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + - negatives() : Predicate {static} + - positives() : Predicate {static} + - prettyPrint(delimiter : String, prefix : String, iterable : Iterable) {static} + - prettyPrint(prefix : String, iterable : Iterable) {static} + - transformToString() : Function {static} + } +} +package com.iluwatar.fluentinterface.fluentiterable.lazy { + abstract class DecoratingIterator { + # fromIterator : Iterator + - next : E + + DecoratingIterator(fromIterator : Iterator) + + computeNext() : E {abstract} + + hasNext() : boolean + + next() : E + } + class LazyFluentIterable { + - iterable : Iterable + # LazyFluentIterable() + # LazyFluentIterable(iterable : Iterable) + + asList() : List + + filter(predicate : Predicate) : FluentIterable + + first() : Optional + + first(count : int) : FluentIterable + + from(iterable : Iterable) : FluentIterable {static} + + iterator() : Iterator + + last() : Optional + + last(count : int) : FluentIterable + + map(function : Function) : FluentIterable + } +} +package com.iluwatar.fluentinterface.fluentiterable { + interface FluentIterable { + + asList() : List {abstract} + + copyToList(iterable : Iterable) : List {static} + + filter(Predicate) : FluentIterable {abstract} + + first() : Optional {abstract} + + first(int) : FluentIterable {abstract} + + last() : Optional {abstract} + + last(int) : FluentIterable {abstract} + + map(Function) : FluentIterable {abstract} + } +} +LazyFluentIterable ..|> FluentIterable +SimpleFluentIterable ..|> FluentIterable +@enduml \ No newline at end of file diff --git a/fluentinterface/pom.xml b/fluentinterface/pom.xml index c648602e77cc..9eb063c132c5 100644 --- a/fluentinterface/pom.xml +++ b/fluentinterface/pom.xml @@ -2,7 +2,7 @@ "-menuItem" MenuItem +Action --> "-type" ActionType +MenuStore --> "-selected" MenuItem +Dispatcher --> "-instance" Dispatcher +ContentView --> "-content" Content +Dispatcher --> "-stores" Store +MenuView --> "-selected" MenuItem +Store --> "-views" View +ContentStore --> "-content" Content +ContentAction --> "-content" Content +ContentAction --|> Action +MenuAction --|> Action +ContentStore --|> Store +MenuStore --|> Store +ContentView ..|> View +MenuView ..|> View +@enduml \ No newline at end of file diff --git a/flux/pom.xml b/flux/pom.xml index f28c8fbb0197..8effd0fc941b 100644 --- a/flux/pom.xml +++ b/flux/pom.xml @@ -2,7 +2,7 @@ "-topShelf" Potion +HealingPotion ..|> Potion +HolyWaterPotion ..|> Potion +InvisibilityPotion ..|> Potion +PoisonPotion ..|> Potion +StrengthPotion ..|> Potion +@enduml \ No newline at end of file diff --git a/flyweight/pom.xml b/flyweight/pom.xml index 223fae694ea4..f3a8082b5afb 100644 --- a/flyweight/pom.xml +++ b/flyweight/pom.xml @@ -2,7 +2,7 @@ "-status" GameStatus +GameController --> "-bullet" Bullet +GameLoop --> "-controller" GameController +FixedStepGameLoop --|> GameLoop +FrameBasedGameLoop --|> GameLoop +VariableStepGameLoop --|> GameLoop +@enduml \ No newline at end of file diff --git a/game-loop/pom.xml b/game-loop/pom.xml new file mode 100644 index 000000000000..2c290827141c --- /dev/null +++ b/game-loop/pom.xml @@ -0,0 +1,63 @@ + + + + + java-design-patterns + com.iluwatar + 1.23.0-SNAPSHOT + + 4.0.0 + + game-loop + + + junit + junit + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.gameloop.App + + + + + + + + + + \ No newline at end of file diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/App.java b/game-loop/src/main/java/com/iluwatar/gameloop/App.java new file mode 100644 index 000000000000..4d9e4c11dfde --- /dev/null +++ b/game-loop/src/main/java/com/iluwatar/gameloop/App.java @@ -0,0 +1,76 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.gameloop; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A game loop runs continuously during gameplay. Each turn of the loop, it processes + * user input without blocking, updates the game state, and renders the game. It tracks + * the passage of time to control the rate of gameplay. + */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Each type of game loop will run for 2 seconds. + */ + private static final int GAME_LOOP_DURATION_TIME = 2000; + + /** + * Program entry point. + * @param args runtime arguments + */ + public static void main(String[] args) { + + try { + LOGGER.info("Start frame-based game loop:"); + var frameBasedGameLoop = new FrameBasedGameLoop(); + frameBasedGameLoop.run(); + Thread.sleep(GAME_LOOP_DURATION_TIME); + frameBasedGameLoop.stop(); + LOGGER.info("Stop frame-based game loop."); + + LOGGER.info("Start variable-step game loop:"); + var variableStepGameLoop = new VariableStepGameLoop(); + variableStepGameLoop.run(); + Thread.sleep(GAME_LOOP_DURATION_TIME); + variableStepGameLoop.stop(); + LOGGER.info("Stop variable-step game loop."); + + LOGGER.info("Start fixed-step game loop:"); + var fixedStepGameLoop = new FixedStepGameLoop(); + fixedStepGameLoop.run(); + Thread.sleep(GAME_LOOP_DURATION_TIME); + fixedStepGameLoop.stop(); + LOGGER.info("Stop variable-step game loop."); + + } catch (InterruptedException e) { + LOGGER.error(e.getMessage()); + } + } + +} diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/Bullet.java b/game-loop/src/main/java/com/iluwatar/gameloop/Bullet.java new file mode 100644 index 000000000000..69b93bc74846 --- /dev/null +++ b/game-loop/src/main/java/com/iluwatar/gameloop/Bullet.java @@ -0,0 +1,44 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.gameloop; + +/** + * Bullet object class. + */ +public class Bullet { + + private float position; + + public Bullet() { + position = 0.0f; + } + + public float getPosition() { + return position; + } + + public void setPosition(float position) { + this.position = position; + } +} diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/FixedStepGameLoop.java b/game-loop/src/main/java/com/iluwatar/gameloop/FixedStepGameLoop.java new file mode 100644 index 000000000000..07ad21584ea7 --- /dev/null +++ b/game-loop/src/main/java/com/iluwatar/gameloop/FixedStepGameLoop.java @@ -0,0 +1,62 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.gameloop; + +/** + * For fixed-step game loop, a certain amount of real time has elapsed since the + * last turn of the game loop. This is how much game time need to be simulated for + * the game’s “now” to catch up with the player’s. + */ +public class FixedStepGameLoop extends GameLoop { + + /** + * 20 ms per frame = 50 FPS. + */ + private static final long MS_PER_FRAME = 20; + + @Override + protected void processGameLoop() { + var previousTime = System.currentTimeMillis(); + var lag = 0L; + while (isGameRunning()) { + var currentTime = System.currentTimeMillis(); + var elapsedTime = currentTime - previousTime; + previousTime = currentTime; + lag += elapsedTime; + + processInput(); + + while (lag >= MS_PER_FRAME) { + update(); + lag -= MS_PER_FRAME; + } + + render(); + } + } + + protected void update() { + controller.moveBullet(0.5f * MS_PER_FRAME / 1000); + } +} diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/FrameBasedGameLoop.java b/game-loop/src/main/java/com/iluwatar/gameloop/FrameBasedGameLoop.java new file mode 100644 index 000000000000..45c59168ffd3 --- /dev/null +++ b/game-loop/src/main/java/com/iluwatar/gameloop/FrameBasedGameLoop.java @@ -0,0 +1,53 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.gameloop; + +/** + * Frame-based game loop is the easiest implementation. The loop always keeps spinning + * for the following three processes: processInput, update and render. The problem with + * it is you have no control over how fast the game runs. On a fast machine, that loop + * will spin so fast users won’t be able to see what’s going on. On a slow machine, the + * game will crawl. If you have a part of the game that’s content-heavy or does more AI + * or physics, the game will actually play slower there. + */ +public class FrameBasedGameLoop extends GameLoop { + + @Override + protected void processGameLoop() { + while (isGameRunning()) { + processInput(); + update(); + render(); + } + } + + /** + * Each time when update() is invoked, a new frame is created, and the bullet will be + * moved 0.5f away from the current position. + */ + protected void update() { + controller.moveBullet(0.5f); + } + +} diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/GameController.java b/game-loop/src/main/java/com/iluwatar/gameloop/GameController.java new file mode 100644 index 000000000000..00c65332b4ce --- /dev/null +++ b/game-loop/src/main/java/com/iluwatar/gameloop/GameController.java @@ -0,0 +1,61 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.gameloop; + +/** + * Update and render objects in the game. Here we add a Bullet object to the + * game system to show how the game loop works. + */ +public class GameController { + + protected final Bullet bullet; + + /** + * Initialize Bullet instance. + */ + public GameController() { + bullet = new Bullet(); + } + + /** + * Move bullet position by the provided offset. + * + * @param offset moving offset + */ + public void moveBullet(float offset) { + var currentPosition = bullet.getPosition(); + bullet.setPosition(currentPosition + offset); + } + + /** + * Get current position of the bullet. + * + * @return position of bullet + */ + public float getBulletPosition() { + return bullet.getPosition(); + } + +} + diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java b/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java new file mode 100644 index 000000000000..cbb456ccf5d5 --- /dev/null +++ b/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java @@ -0,0 +1,104 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.gameloop; + +import java.util.Random; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract class for GameLoop implementation class. + */ +public abstract class GameLoop { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + protected volatile GameStatus status; + + protected GameController controller; + + private Thread gameThread; + + /** + * Initialize game status to be stopped. + */ + public GameLoop() { + controller = new GameController(); + status = GameStatus.STOPPED; + } + + /** + * Run game loop. + */ + public void run() { + status = GameStatus.RUNNING; + gameThread = new Thread(() -> processGameLoop()); + gameThread.start(); + } + + /** + * Stop game loop. + */ + public void stop() { + status = GameStatus.STOPPED; + } + + /** + * Check if game is running or not. + * + * @return {@code true} if the game is running. + */ + public boolean isGameRunning() { + return status == GameStatus.RUNNING; + } + + /** + * Handle any user input that has happened since the last call. In order to + * simulate the situation in real-life game, here we add a random time lag. + * The time lag ranges from 50 ms to 250 ms. + */ + protected void processInput() { + try { + var lag = new Random().nextInt(200) + 50; + Thread.sleep(lag); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + } + } + + /** + * Render game frames to screen. Here we print bullet position to simulate + * this process. + */ + protected void render() { + var position = controller.getBulletPosition(); + logger.info("Current bullet position: " + position); + } + + /** + * execute game loop logic. + */ + protected abstract void processGameLoop(); + +} diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/GameStatus.java b/game-loop/src/main/java/com/iluwatar/gameloop/GameStatus.java new file mode 100644 index 000000000000..46d90669e498 --- /dev/null +++ b/game-loop/src/main/java/com/iluwatar/gameloop/GameStatus.java @@ -0,0 +1,33 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.gameloop; + +/** + * Enum class for game status. + */ +public enum GameStatus { + + RUNNING, STOPPED + +} diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/VariableStepGameLoop.java b/game-loop/src/main/java/com/iluwatar/gameloop/VariableStepGameLoop.java new file mode 100644 index 000000000000..544590c5bd98 --- /dev/null +++ b/game-loop/src/main/java/com/iluwatar/gameloop/VariableStepGameLoop.java @@ -0,0 +1,51 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.gameloop; + +/** + * The variable-step game loop chooses a time step to advance based on how much + * real time passed since the last frame. The longer the frame takes, the bigger + * steps the game takes. It always keeps up with real time because it will take + * bigger and bigger steps to get there. + */ +public class VariableStepGameLoop extends GameLoop { + + @Override + protected void processGameLoop() { + var lastFrameTime = System.currentTimeMillis(); + while (isGameRunning()) { + processInput(); + var currentFrameTime = System.currentTimeMillis(); + var elapsedTime = currentFrameTime - lastFrameTime; + update(elapsedTime); + lastFrameTime = currentFrameTime; + render(); + } + } + + protected void update(Long elapsedTime) { + controller.moveBullet(0.5f * elapsedTime / 1000); + } + +} diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java new file mode 100644 index 000000000000..447e4f411faa --- /dev/null +++ b/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java @@ -0,0 +1,38 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.gameloop; + +import org.junit.Test; + +/** + * App unit test class. + */ +public class AppTest { + + @Test + public void testMain() { + new App().main(new String[]{}); + } + +} diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/FixedStepGameLoopTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/FixedStepGameLoopTest.java new file mode 100644 index 000000000000..5c8614fa1318 --- /dev/null +++ b/game-loop/src/test/java/com/iluwatar/gameloop/FixedStepGameLoopTest.java @@ -0,0 +1,54 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.gameloop; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * FixedStepGameLoop unit test class. + */ +public class FixedStepGameLoopTest { + + private FixedStepGameLoop gameLoop; + + @Before + public void setup() { + gameLoop = new FixedStepGameLoop(); + } + + @After + public void tearDown() { + gameLoop = null; + } + + @Test + public void testUpdate() { + gameLoop.update(); + Assert.assertEquals(0.01f, gameLoop.controller.getBulletPosition(), 0); + } + +} diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/FrameBasedGameLoopTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/FrameBasedGameLoopTest.java new file mode 100644 index 000000000000..e8eb922dcd3d --- /dev/null +++ b/game-loop/src/test/java/com/iluwatar/gameloop/FrameBasedGameLoopTest.java @@ -0,0 +1,53 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.gameloop; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * FrameBasedGameLoop unit test class. + */ +public class FrameBasedGameLoopTest { + + private FrameBasedGameLoop gameLoop; + + @Before + public void setup() { + gameLoop = new FrameBasedGameLoop(); + } + + @After + public void tearDown() { + gameLoop = null; + } + + @Test + public void testUpdate() { + gameLoop.update(); + Assert.assertEquals(0.5f, gameLoop.controller.getBulletPosition(), 0); + } +} diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/GameControllerTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/GameControllerTest.java new file mode 100644 index 000000000000..3d057d830440 --- /dev/null +++ b/game-loop/src/test/java/com/iluwatar/gameloop/GameControllerTest.java @@ -0,0 +1,56 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.gameloop; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class GameControllerTest { + + private GameController controller; + + @Before + public void setup() { + controller = new GameController(); + } + + @After + public void tearDown() { + controller = null; + } + + @Test + public void testMoveBullet() { + controller.moveBullet(1.5f); + Assert.assertEquals(1.5f, controller.bullet.getPosition(), 0); + } + + @Test + public void testGetBulletPosition() { + Assert.assertEquals(controller.bullet.getPosition(), controller.getBulletPosition(), 0); + } + +} diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/GameLoopTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/GameLoopTest.java new file mode 100644 index 000000000000..4873cfd47ff0 --- /dev/null +++ b/game-loop/src/test/java/com/iluwatar/gameloop/GameLoopTest.java @@ -0,0 +1,71 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.gameloop; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * GameLoop unit test class. + */ +public class GameLoopTest { + + private GameLoop gameLoop; + + /** + * Create mock implementation of GameLoop. + */ + @Before + public void setup() { + gameLoop = new GameLoop() { + @Override + protected void processGameLoop() {} + }; + } + + @After + public void tearDown() { + gameLoop = null; + } + + @Test + public void testRun() { + gameLoop.run(); + Assert.assertEquals(GameStatus.RUNNING, gameLoop.status); + } + + @Test + public void testStop() { + gameLoop.stop(); + Assert.assertEquals(GameStatus.STOPPED, gameLoop.status); + } + + @Test + public void testIsGameRunning() { + Assert.assertFalse(gameLoop.isGameRunning()); + } + +} diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/VariableStepGameLoopTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/VariableStepGameLoopTest.java new file mode 100644 index 000000000000..43dd4f732426 --- /dev/null +++ b/game-loop/src/test/java/com/iluwatar/gameloop/VariableStepGameLoopTest.java @@ -0,0 +1,54 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.gameloop; + +import java.lang.reflect.InvocationTargetException; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * VariableStepGameLoop unit test class. + */ +public class VariableStepGameLoopTest { + + private VariableStepGameLoop gameLoop; + + @Before + public void setup() { + gameLoop = new VariableStepGameLoop(); + } + + @After + public void tearDown() { + gameLoop = null; + } + + @Test + public void testUpdate() { + gameLoop.update(20L); + Assert.assertEquals(0.01f, gameLoop.controller.getBulletPosition(), 0); + } +} diff --git a/guarded-suspension/README.md b/guarded-suspension/README.md index 35044f9b2491..83e6b4fdf545 100644 --- a/guarded-suspension/README.md +++ b/guarded-suspension/README.md @@ -5,17 +5,18 @@ folder: guarded-suspension permalink: /patterns/guarded-suspension/ categories: Concurrency tags: - - Java - - Difficulty-Beginner + - Decoupling --- ## Intent Use Guarded suspension pattern to handle a situation when you want to execute a method on object which is not in a proper state. +## Class diagram ![Guarded Suspension diagram](./etc/guarded-suspension.png) ## Applicability Use Guarded Suspension pattern when the developer knows that the method execution will be blocked for a finite period of time ## Related patterns + * Balking diff --git a/guarded-suspension/etc/guarded-suspension.urm.puml b/guarded-suspension/etc/guarded-suspension.urm.puml new file mode 100644 index 000000000000..45a7d37904cb --- /dev/null +++ b/guarded-suspension/etc/guarded-suspension.urm.puml @@ -0,0 +1,15 @@ +@startuml +package com.iluwatar.guarded.suspension { + class App { + + App() + + main(args : String[]) {static} + } + class GuardedQueue { + - LOGGER : Logger {static} + - sourceList : Queue + + GuardedQueue() + + get() : Integer + + put(e : Integer) + } +} +@enduml \ No newline at end of file diff --git a/guarded-suspension/pom.xml b/guarded-suspension/pom.xml index 367d582d57d8..791c696c158a 100644 --- a/guarded-suspension/pom.xml +++ b/guarded-suspension/pom.xml @@ -2,7 +2,7 @@ "-playerDetails" PlayerDetails +MongoEventLog --> "-stdOutEventLog" StdOutEventLog +LotteryService --> "-wireTransfers" WireTransfers +LotteryAdministration --> "-notifications" LotteryEventLog +LotteryAdministration --> "-wireTransfers" WireTransfers +LotteryTicket --> "-id" LotteryTicketId +LotteryAdministration --> "-repository" LotteryTicketRepository +LotteryService --> "-notifications" LotteryEventLog +LotteryTicket --> "-lotteryNumbers" LotteryNumbers +SampleData --> "-PLAYERS" PlayerDetails +ConsoleAdministrationSrvImpl --> "-administration" LotteryAdministration +RandomNumberGenerator ..+ LotteryNumbers +LotteryService --> "-repository" LotteryTicketRepository +CheckResult ..+ LotteryTicketCheckResult +LotteryTicketCheckResult --> "-checkResult" CheckResult +ConsoleAdministrationSrvImpl ..|> ConsoleAdministrationSrv +InMemoryBank ..|> WireTransfers +MongoBank ..|> WireTransfers +InMemoryTicketRepository ..|> LotteryTicketRepository +MongoTicketRepository ..|> LotteryTicketRepository +MongoEventLog ..|> LotteryEventLog +StdOutEventLog ..|> LotteryEventLog +LotteryConsoleServiceImpl ..|> LotteryConsoleService +@enduml \ No newline at end of file diff --git a/hexagonal/etc/layers.png b/hexagonal/etc/layers.png deleted file mode 100644 index cb5a9c90bbe9..000000000000 Binary files a/hexagonal/etc/layers.png and /dev/null differ diff --git a/hexagonal/etc/ports_and_adapters.png b/hexagonal/etc/ports_and_adapters.png deleted file mode 100644 index d285045dea68..000000000000 Binary files a/hexagonal/etc/ports_and_adapters.png and /dev/null differ diff --git a/hexagonal/etc/ports_and_adapters.xml b/hexagonal/etc/ports_and_adapters.xml deleted file mode 100644 index 9df3173d902e..000000000000 --- a/hexagonal/etc/ports_and_adapters.xml +++ /dev/null @@ -1,25 +0,0 @@ - -7Zpdk6I4FIZ/jbdbJAHEyx7nY/diqrqqd2tnLiOJSDUSK6ZHe3/9BkmUfFiDDqBM2TcNBwLxOe85nBOYoPl6/4XjzeorI7SYwIDsJ+jjBEKA4kj+qyzvtWUag9qQ8Zyok06Gl/w/qoyBsr7lhG6NEwVjhcg3pjFlZUlTYdgw52xnnrZkhXnXDc6oY3hJceFa/82JWNXWJApO9j9pnq30nUGgjixw+ppx9laq+00gWh7+6sNrrK+lzt+uMGG7hgl9mqA5Z0zUW+v9nBYVW42tHvf5zNHjvDktRZsBqB7wAxdvVM/4MC/xrlnIKW6qzRXd44yVE/RhQ3m+poLyk/X5ZPqwW+WCvmxwWo3aSYFI20qsC7kH5Ka6I+WC7s/OGhxZSI1RJi/N3+UpagBMFD4lL6D3dydnIa2lVcNRMFZGrASSHa99giQ3FCc/MzhKZmgaGcyOKBrMAPQwO2r7V5iFDrM549ThJgfJEKc/B7LMi2LOCsYP46oYg2kq7VvB2SttHCHxIo7ibhCGATARJshBqDNfk2AXoot+Lrpr4RFMk6UXXpwmdLHsCF5s6W/qwkMe+aEO4MW/GzwEPQmvJ3jT0cMDMxNeGA0GLxk7PDQ1H7VDKm82fnh2zoODwdPlz3jphdCUHoyGkx5oUeRdTS+iCQl99BK4QHFHxUoUWfQ89V5v9Fq0FXdOz9ZeMCC9Nk/ckjxVfa7cK1lJTWJ0n4tvje3vcjuotks5k2/Nne/6rJJ8zqsJHQ7Vd6PE6ZEtenJG7I2n1KhQBeYZFU0duJAbECMPRG3jtMAi/2HOwkdW3eGZ5XJ+Z7vECFjOqWevRjWbZOtCCJkXCu2Cvv7NzoUOjj7+7Ha+b1MwXOz7P6IefQwfPr7Qx33WNYNkR5s60nXuANkR9lnXDEIPWc8WNBvu2aJn+8v5JfDkF/V0Ac2nS3e5J/Hkntkj91yWe7S7O/U/OO/9/nzfWC5/+L6d70dfldsrOSEcbiXHs4R9ZeTUGdEXO41E2mX0TD3Rc1+Vue2gq6Mn6S963BX4Z8bF1hGBlLcwPW/GhFJGM4CUCRd5VsrdVDrz8FaoCpY8xcWTOrDOCSnOBWcH8YVmoYHzGDgNWYS+12pdxJe7SP/PVlKQZ1Q0ltVvtVEfXrRSojD3/w4NALvudd9hAN97x7ALQO6ywN90WwUDzqiS903h2CvFiZucZ32xcdvmv6RqMi6zFCtvjuZY4et851lN6g2N221+xAIv8Pb28RQHJhcwG04yOrU1uHxl6au0kLvBYz3dYOTi6S3doPF2irpKNAqe2I96mIIHWJ6Mr20XgF059dcuoD5axWEEoOsRQwDThwAuFECbfvE+Ox5dlxkCSB4CuFAAXbW8vpcRvldRHT4CfAK46WrhKAXQ5quzOxUAdAWAbrpkOEoBuE35E8Eb2Y//XuseVh+CPP1ZR+secvf0dXftkdMn9OjT/w== \ No newline at end of file diff --git a/hexagonal/etc/presentation.html b/hexagonal/etc/presentation.html deleted file mode 100644 index 5c6d1d0a5c6a..000000000000 --- a/hexagonal/etc/presentation.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - - Title - - - - - - - - - - diff --git a/hexagonal/pom.xml b/hexagonal/pom.xml index cb127c91d0f1..4873d0ddb238 100644 --- a/hexagonal/pom.xml +++ b/hexagonal/pom.xml @@ -2,7 +2,7 @@ "-next" Filter +DListener --+ Target +FilterManager --> "-filterChain" FilterChain +FilterChain --> "-chain" Filter +AbstractFilter ..|> Filter +AddressFilter --|> AbstractFilter +ContactFilter --|> AbstractFilter +DepositFilter --|> AbstractFilter +NameFilter --|> AbstractFilter +OrderFilter --|> AbstractFilter +@enduml \ No newline at end of file diff --git a/intercepting-filter/pom.xml b/intercepting-filter/pom.xml index f38e6b4f2ae0..ea8597374576 100644 --- a/intercepting-filter/pom.xml +++ b/intercepting-filter/pom.xml @@ -2,7 +2,7 @@ "-leftExpression" Expression +MinusExpression --> "-leftExpression" Expression +PlusExpression --> "-leftExpression" Expression +MinusExpression --|> Expression +MultiplyExpression --|> Expression +NumberExpression --|> Expression +PlusExpression --|> Expression +@enduml \ No newline at end of file diff --git a/interpreter/pom.xml b/interpreter/pom.xml index 1e958fa3f70f..118cfcdf645e 100644 --- a/interpreter/pom.xml +++ b/interpreter/pom.xml @@ -2,7 +2,7 @@ "-type" ItemType +TreeNode --> "-left" TreeNode +TreasureChestItemIterator --> "-chest" TreasureChest +TreasureChest --> "-items" Item +Item --> "-type" ItemType +App --> "-TREASURE_CHEST" TreasureChest +BstIterator ..|> Iterator +TreasureChestItemIterator ..|> Iterator +@enduml \ No newline at end of file diff --git a/iterator/pom.xml b/iterator/pom.xml index 9d1f4e280dbe..514cedbea90e 100644 --- a/iterator/pom.xml +++ b/iterator/pom.xml @@ -2,7 +2,7 @@ "-cakeLayerInfos" CakeLayerInfo +CakeInfo --> "-cakeToppingInfo" CakeToppingInfo +CakeViewImpl --> "-cakeBakingService" CakeBakingService +App --> "-cakeBakingService" CakeBakingService +Cake --> "-topping" CakeTopping +CakeLayer --> "-cake" Cake +CakeBakingServiceImpl ..|> CakeBakingService +CakeViewImpl ..|> View +@enduml \ No newline at end of file diff --git a/layers/pom.xml b/layers/pom.xml index b3cdf0ac76ba..2ebace18b4f1 100644 --- a/layers/pom.xml +++ b/layers/pom.xml @@ -2,7 +2,7 @@ "-heavy" Heavy +HolderNaive --> "-heavy" Heavy +HeavyFactory --> "-heavyInstance" Heavy +@enduml \ No newline at end of file diff --git a/lazy-loading/pom.xml b/lazy-loading/pom.xml index c7f3679ae32d..a6a5d3a4510a 100644 --- a/lazy-loading/pom.xml +++ b/lazy-loading/pom.xml @@ -2,7 +2,7 @@ "-messageQueue" Message +Message --> "-type" MessageType +AbstractInstance --> "-messageManager" MessageManager +AbstractInstance ..|> Instance +AbstractMessageManager ..|> MessageManager +BullyInstance --|> AbstractInstance +BullyMessageManager --|> AbstractMessageManager +RingInstance --|> AbstractInstance +RingMessageManager --|> AbstractMessageManager +@enduml \ No newline at end of file diff --git a/leader-election/pom.xml b/leader-election/pom.xml new file mode 100644 index 000000000000..8fc833f18072 --- /dev/null +++ b/leader-election/pom.xml @@ -0,0 +1,75 @@ + + + + 4.0.0 + + java-design-patterns + com.iluwatar + 1.23.0-SNAPSHOT + + leader-election + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + bully + + + + com.iluwatar.leaderelection.bully.BullyApp + + + ${project.artifactId}-Bully + + + + ring + + + + com.iluwatar.leaderelection.ring.RingApp + + + ${project.artifactId}-Ring + + + + + + + \ No newline at end of file diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java new file mode 100644 index 000000000000..f30610597b18 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java @@ -0,0 +1,153 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract class of all the instance implementation classes. + */ +public abstract class AbstractInstance implements Instance, Runnable { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractInstance.class); + + protected static final int HEARTBEAT_INTERVAL = 5000; + + protected MessageManager messageManager; + protected Queue messageQueue; + protected final int localId; + protected int leaderId; + protected boolean alive; + + /** + * Constructor of BullyInstance. + */ + public AbstractInstance(MessageManager messageManager, int localId, int leaderId) { + this.messageManager = messageManager; + this.messageQueue = new ConcurrentLinkedQueue<>(); + this.localId = localId; + this.leaderId = leaderId; + this.alive = true; + } + + /** + * The instance will execute the message in its message queue periodically once it is alive. + */ + @Override + @SuppressWarnings("squid:S2189") + public void run() { + while (true) { + if (!this.messageQueue.isEmpty()) { + this.processMessage(this.messageQueue.remove()); + } + } + } + + /** + * Once messages are sent to the certain instance, it will firstly be added to the queue and wait + * to be executed. + * + * @param message Message sent by other instances + */ + @Override + public void onMessage(Message message) { + messageQueue.offer(message); + } + + /** + * Check if the instance is alive or not. + * + * @return {@code true} if the instance is alive. + */ + @Override + public boolean isAlive() { + return alive; + } + + /** + * Set the health status of the certain instance. + * + * @param alive {@code true} for alive. + */ + @Override + public void setAlive(boolean alive) { + this.alive = alive; + } + + /** + * Process the message according to its type. + * + * @param message Message polled from queue. + */ + private void processMessage(Message message) { + switch (message.getType()) { + case ELECTION: + LOGGER.info("Instance " + localId + " - Election Message handling..."); + handleElectionMessage(message); + break; + case LEADER: + LOGGER.info("Instance " + localId + " - Leader Message handling..."); + handleLeaderMessage(message); + break; + case HEARTBEAT: + LOGGER.info("Instance " + localId + " - Heartbeat Message handling..."); + handleHeartbeatMessage(message); + break; + case ELECTION_INVOKE: + LOGGER.info("Instance " + localId + " - Election Invoke Message handling..."); + handleElectionInvokeMessage(); + break; + case LEADER_INVOKE: + LOGGER.info("Instance " + localId + " - Leader Invoke Message handling..."); + handleLeaderInvokeMessage(); + break; + case HEARTBEAT_INVOKE: + LOGGER.info("Instance " + localId + " - Heartbeat Invoke Message handling..."); + handleHeartbeatInvokeMessage(); + break; + default: + break; + } + } + + /** + * Abstract methods to handle different types of message. These methods need to be implemented in + * concrete instance class to implement corresponding leader-selection pattern. + */ + protected abstract void handleElectionMessage(Message message); + + protected abstract void handleElectionInvokeMessage(); + + protected abstract void handleLeaderMessage(Message message); + + protected abstract void handleLeaderInvokeMessage(); + + protected abstract void handleHeartbeatMessage(Message message); + + protected abstract void handleHeartbeatInvokeMessage(); + +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java new file mode 100644 index 000000000000..ae2c2f380108 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java @@ -0,0 +1,74 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Abstract class of all the message manager classes. + */ +public abstract class AbstractMessageManager implements MessageManager { + + /** + * Contain all the instances in the system. Key is its ID, and value is the instance itself. + */ + protected Map instanceMap; + + /** + * Construtor of AbstractMessageManager. + */ + public AbstractMessageManager(Map instanceMap) { + this.instanceMap = instanceMap; + } + + /** + * Find the next instance with smallest ID. + * + * @return The next instance. + */ + protected Instance findNextInstance(int currentId) { + Instance result = null; + List candidateList = instanceMap.keySet() + .stream() + .filter((i) -> i > currentId && instanceMap.get(i).isAlive()) + .sorted() + .collect(Collectors.toList()); + if (candidateList.isEmpty()) { + int index = instanceMap.keySet() + .stream() + .filter((i) -> instanceMap.get(i).isAlive()) + .sorted() + .collect(Collectors.toList()) + .get(0); + result = instanceMap.get(index); + } else { + int index = candidateList.get(0); + result = instanceMap.get(index); + } + return result; + } + +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java new file mode 100644 index 000000000000..3ea57c04b0dc --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java @@ -0,0 +1,52 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection; + +/** + * Instance interface. + */ +public interface Instance { + + /** + * Check if the instance is alive or not. + * + * @return {@code true} if the instance is alive. + */ + boolean isAlive(); + + /** + * Set the health status of the certain instance. + * + * @param alive {@code true} for alive. + */ + void setAlive(boolean alive); + + /** + * Consume messages from other instances. + * + * @param message Message sent by other instances + */ + void onMessage(Message message); + +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java new file mode 100644 index 000000000000..21f021aff900 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java @@ -0,0 +1,77 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection; + +import java.util.Objects; + +/** + * Message used to transport data between instances. + */ +public class Message { + + private MessageType type; + + private String content; + + public Message() { + } + + public Message(MessageType type, String content) { + this.type = type; + this.content = content; + } + + public MessageType getType() { + return type; + } + + public void setType(MessageType type) { + this.type = type; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Message message = (Message) o; + return type == message.type && Objects.equals(content, message.content); + } + + @Override + public int hashCode() { + return Objects.hash(type, content); + } +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java new file mode 100644 index 000000000000..9f10de22e55c --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java @@ -0,0 +1,64 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection; + +/** + * MessageManager interface. + */ +public interface MessageManager { + + /** + * Send heartbeat message to leader instance to check whether the leader instance is alive. + * + * @param leaderId Instance ID of leader instance. + * @return {@code true} if leader instance is alive, or {@code false} if not. + */ + boolean sendHeartbeatMessage(int leaderId); + + /** + * Send election message to other instances. + * + * @param currentId Instance ID of which sends this message. + * @param content Election message content. + * @return {@code true} if the message is accepted by the target instances. + */ + boolean sendElectionMessage(int currentId, String content); + + /** + * Send new leader notification message to other instances. + * + * @param currentId Instance ID of which sends this message. + * @param leaderId Leader message content. + * @return {@code true} if the message is accepted by the target instances. + */ + boolean sendLeaderMessage(int currentId, int leaderId); + + /** + * Send heartbeat invoke message. This will invoke heartbeat task in the target instance. + * + * @param currentId Instance ID of which sends this message. + */ + void sendHeartbeatInvokeMessage(int currentId); + +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java new file mode 100644 index 000000000000..a82bfa38f15d --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java @@ -0,0 +1,62 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection; + +/** + * Message Type enum. + */ +public enum MessageType { + + /** + * Start the election. The content of the message stores ID(s) of the candidate instance(s). + */ + ELECTION, + + /** + * Nodify the new leader. The content of the message should be the leader ID. + */ + LEADER, + + /** + * Check health of current leader instance. + */ + HEARTBEAT, + + /** + * Inform target instance to start election. + */ + ELECTION_INVOKE, + + /** + * Inform target instance to notify all the other instance that it is the new leader. + */ + LEADER_INVOKE, + + /** + * Inform target instance to start heartbeat. + */ + HEARTBEAT_INVOKE + +} + diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java new file mode 100644 index 000000000000..34231b3d5a66 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java @@ -0,0 +1,76 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.bully; + +import com.iluwatar.leaderelection.Instance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageManager; +import com.iluwatar.leaderelection.MessageType; +import java.util.HashMap; +import java.util.Map; + +/** + * Example of how to use bully leader election. Initially 5 instances is created in the clould + * system, and the instance with ID 1 is set as leader. After the system is started stop the leader + * instance, and the new leader will be elected. + */ +public class BullyApp { + + /** + * Program entry point. + */ + public static void main(String[] args) { + + Map instanceMap = new HashMap<>(); + MessageManager messageManager = new BullyMessageManager(instanceMap); + + BullyInstance instance1 = new BullyInstance(messageManager, 1, 1); + BullyInstance instance2 = new BullyInstance(messageManager, 2, 1); + BullyInstance instance3 = new BullyInstance(messageManager, 3, 1); + BullyInstance instance4 = new BullyInstance(messageManager, 4, 1); + BullyInstance instance5 = new BullyInstance(messageManager, 5, 1); + + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + instanceMap.put(4, instance4); + instanceMap.put(5, instance5); + + instance4.onMessage(new Message(MessageType.HEARTBEAT_INVOKE, "")); + + final Thread thread1 = new Thread(instance1); + final Thread thread2 = new Thread(instance2); + final Thread thread3 = new Thread(instance3); + final Thread thread4 = new Thread(instance4); + final Thread thread5 = new Thread(instance5); + + thread1.start(); + thread2.start(); + thread3.start(); + thread4.start(); + thread5.start(); + + instance1.setAlive(false); + } +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java new file mode 100644 index 000000000000..92cb18ab40b7 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java @@ -0,0 +1,125 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.bully; + +import com.iluwatar.leaderelection.AbstractInstance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Impelemetation with bully algorithm. Each instance should have a sequential id and is able to + * communicate with other instances in the system. Initially the instance with smallest (or largest) + * ID is selected to be the leader. All the other instances send heartbeat message to leader + * periodically to check its health. If one certain instance finds the server done, it will send an + * election message to all the instances of which the ID is larger. If the target instance is alive, + * it will return an alive message (in this sample return true) and then send election message with + * its ID. If not, the original instance will send leader message to all the other instances. + */ +public class BullyInstance extends AbstractInstance { + + private static final Logger LOGGER = LoggerFactory.getLogger(BullyInstance.class); + + /** + * Constructor of BullyInstance. + */ + public BullyInstance(MessageManager messageManager, int localId, int leaderId) { + super(messageManager, localId, leaderId); + } + + /** + * Process the heartbeat invoke message. After receiving the message, the instance will send a + * heartbeat to leader to check its health. If alive, it will inform the next instance to do the + * heartbeat. If not, it will start the election process. + */ + @Override + protected void handleHeartbeatInvokeMessage() { + try { + boolean isLeaderAlive = messageManager.sendHeartbeatMessage(leaderId); + if (isLeaderAlive) { + LOGGER.info("Instance " + localId + "- Leader is alive."); + Thread.sleep(HEARTBEAT_INTERVAL); + messageManager.sendHeartbeatInvokeMessage(localId); + } else { + LOGGER.info("Instance " + localId + "- Leader is not alive. Start election."); + boolean electionResult = + messageManager.sendElectionMessage(localId, String.valueOf(localId)); + if (electionResult) { + LOGGER.info("Instance " + localId + "- Succeed in election. Start leader notification."); + messageManager.sendLeaderMessage(localId, localId); + } + } + } catch (InterruptedException e) { + LOGGER.info("Instance " + localId + "- Interrupted."); + } + } + + /** + * Process election invoke message. Send election message to all the instances with smaller ID. If + * any one of them is alive, do nothing. If no instance alive, send leader message to all the + * alive instance and restart heartbeat. + */ + @Override + protected void handleElectionInvokeMessage() { + if (!isLeader()) { + LOGGER.info("Instance " + localId + "- Start election."); + boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId)); + if (electionResult) { + LOGGER.info("Instance " + localId + "- Succeed in election. Start leader notification."); + leaderId = localId; + messageManager.sendLeaderMessage(localId, localId); + messageManager.sendHeartbeatInvokeMessage(localId); + } + } + } + + /** + * Process leader message. Update local leader information. + */ + @Override + protected void handleLeaderMessage(Message message) { + leaderId = Integer.valueOf(message.getContent()); + LOGGER.info("Instance " + localId + " - Leader update done."); + } + + private boolean isLeader() { + return localId == leaderId; + } + + /** + * Not used in Bully instance. + */ + @Override + protected void handleLeaderInvokeMessage() { + } + + @Override + protected void handleHeartbeatMessage(Message message) { + } + + @Override + protected void handleElectionMessage(Message message) { + } +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java new file mode 100644 index 000000000000..d1e4c7db13e8 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java @@ -0,0 +1,121 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.bully; + +import com.iluwatar.leaderelection.AbstractMessageManager; +import com.iluwatar.leaderelection.Instance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageType; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Implementation of BullyMessageManager. + */ +public class BullyMessageManager extends AbstractMessageManager { + + /** + * Constructor of BullyMessageManager. + */ + public BullyMessageManager(Map instanceMap) { + super(instanceMap); + } + + /** + * Send heartbeat message to current leader instance to check the health. + * + * @param leaderId leaderID + * @return {@code true} if the leader is alive. + */ + @Override + public boolean sendHeartbeatMessage(int leaderId) { + Instance leaderInstance = instanceMap.get(leaderId); + boolean alive = leaderInstance.isAlive(); + return alive; + } + + /** + * Send election message to all the instances with smaller ID. + * + * @param currentId Instance ID of which sends this message. + * @param content Election message content. + * @return {@code true} if no alive instance has smaller ID, so that the election is accepted. + */ + @Override + public boolean sendElectionMessage(int currentId, String content) { + List candidateList = findElectionCandidateInstanceList(currentId); + if (candidateList.isEmpty()) { + return true; + } else { + Message electionMessage = new Message(MessageType.ELECTION_INVOKE, ""); + candidateList.stream() + .forEach((i) -> instanceMap.get(i).onMessage(electionMessage)); + return false; + } + } + + /** + * Send leader message to all the instances to notify the new leader. + * + * @param currentId Instance ID of which sends this message. + * @param leaderId Leader message content. + * @return {@code true} if the message is accepted. + */ + @Override + public boolean sendLeaderMessage(int currentId, int leaderId) { + Message leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId)); + instanceMap.keySet() + .stream() + .filter((i) -> i != currentId) + .forEach((i) -> instanceMap.get(i).onMessage(leaderMessage)); + return false; + } + + /** + * Send heartbeat invoke message to the next instance. + * + * @param currentId Instance ID of which sends this message. + */ + @Override + public void sendHeartbeatInvokeMessage(int currentId) { + Instance nextInstance = this.findNextInstance(currentId); + Message heartbeatInvokeMessage = new Message(MessageType.HEARTBEAT_INVOKE, ""); + nextInstance.onMessage(heartbeatInvokeMessage); + } + + /** + * Find all the alive instances with smaller ID than current instance. + * + * @param currentId ID of current instance. + * @return ID list of all the candidate instance. + */ + private List findElectionCandidateInstanceList(int currentId) { + return instanceMap.keySet() + .stream() + .filter((i) -> i < currentId && instanceMap.get(i).isAlive()) + .collect(Collectors.toList()); + } + +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java new file mode 100644 index 000000000000..096beb56641b --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java @@ -0,0 +1,76 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.Instance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageManager; +import com.iluwatar.leaderelection.MessageType; +import java.util.HashMap; +import java.util.Map; + +/** + * Example of how to use ring leader election. Initially 5 instances is created in the clould + * system, and the instance with ID 1 is set as leader. After the system is started stop the leader + * instance, and the new leader will be elected. + */ +public class RingApp { + + /** + * Program entry point. + */ + public static void main(String[] args) { + + Map instanceMap = new HashMap<>(); + MessageManager messageManager = new RingMessageManager(instanceMap); + + RingInstance instance1 = new RingInstance(messageManager, 1, 1); + RingInstance instance2 = new RingInstance(messageManager, 2, 1); + RingInstance instance3 = new RingInstance(messageManager, 3, 1); + RingInstance instance4 = new RingInstance(messageManager, 4, 1); + RingInstance instance5 = new RingInstance(messageManager, 5, 1); + + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + instanceMap.put(4, instance4); + instanceMap.put(5, instance5); + + instance2.onMessage(new Message(MessageType.HEARTBEAT_INVOKE, "")); + + final Thread thread1 = new Thread(instance1); + final Thread thread2 = new Thread(instance2); + final Thread thread3 = new Thread(instance3); + final Thread thread4 = new Thread(instance4); + final Thread thread5 = new Thread(instance5); + + thread1.start(); + thread2.start(); + thread3.start(); + thread4.start(); + thread5.start(); + + instance1.setAlive(false); + } +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java new file mode 100644 index 000000000000..903ac15ce4b1 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -0,0 +1,136 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.AbstractInstance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageManager; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation with token ring algorithm. The instances in the system are organized as a ring. + * Each instance should have a sequential id and the instance with smallest (or largest) id should + * be the initial leader. All the other instances send heartbeat message to leader periodically to + * check its health. If one certain instance finds the server done, it will send an election message + * to the next alive instance in the ring, which contains its own ID. Then the next instance add its + * ID into the message and pass it to the next. After all the alive instances' ID are add to the + * message, the message is send back to the first instance and it will choose the instance with + * smallest ID to be the new leader, and then send a leader message to other instances to inform the + * result. + */ +public class RingInstance extends AbstractInstance { + + private static final Logger LOGGER = LoggerFactory.getLogger(RingInstance.class); + + /** + * Constructor of RingInstance. + */ + public RingInstance(MessageManager messageManager, int localId, int leaderId) { + super(messageManager, localId, leaderId); + } + + /** + * Process the heartbeat invoke message. After receiving the message, the instance will send a + * heartbeat to leader to check its health. If alive, it will inform the next instance to do the + * heartbeat. If not, it will start the election process. + */ + @Override + protected void handleHeartbeatInvokeMessage() { + try { + boolean isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderId); + if (isLeaderAlive) { + LOGGER.info("Instance " + localId + "- Leader is alive. Start next heartbeat in 5 second."); + Thread.sleep(HEARTBEAT_INTERVAL); + messageManager.sendHeartbeatInvokeMessage(this.localId); + } else { + LOGGER.info("Instance " + localId + "- Leader is not alive. Start election."); + messageManager.sendElectionMessage(this.localId, String.valueOf(this.localId)); + } + } catch (InterruptedException e) { + LOGGER.info("Instance " + localId + "- Interrupted."); + } + } + + /** + * Process election message. If the local ID is contained in the ID list, the instance will select + * the alive instance with smallest ID to be the new leader, and send the leader inform message. + * If not, it will add its local ID to the list and send the message to the next instance in the + * ring. + */ + @Override + protected void handleElectionMessage(Message message) { + String content = message.getContent(); + LOGGER.info("Instance " + localId + " - Election Message: " + content); + List candidateList = + Arrays.stream(content.trim().split(",")) + .map(Integer::valueOf) + .sorted() + .collect(Collectors.toList()); + if (candidateList.contains(localId)) { + int newLeaderId = candidateList.get(0); + LOGGER.info("Instance " + localId + " - New leader should be " + newLeaderId + "."); + messageManager.sendLeaderMessage(localId, newLeaderId); + } else { + content += "," + localId; + messageManager.sendElectionMessage(localId, content); + } + } + + /** + * Process leader Message. The instance will set the leader ID to be the new one and send the + * message to the next instance until all the alive instance in the ring is informed. + */ + @Override + protected void handleLeaderMessage(Message message) { + int newLeaderId = Integer.valueOf(message.getContent()); + if (this.leaderId != newLeaderId) { + LOGGER.info("Instance " + localId + " - Update leaderID"); + this.leaderId = newLeaderId; + messageManager.sendLeaderMessage(localId, newLeaderId); + } else { + LOGGER.info("Instance " + localId + " - Leader update done. Start heartbeat."); + messageManager.sendHeartbeatInvokeMessage(localId); + } + } + + /** + * Not used in Ring instance. + */ + @Override + protected void handleLeaderInvokeMessage() { + } + + @Override + protected void handleHeartbeatMessage(Message message) { + } + + @Override + protected void handleElectionInvokeMessage() { + } + +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java new file mode 100644 index 000000000000..6cbadf184a2c --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java @@ -0,0 +1,100 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.AbstractMessageManager; +import com.iluwatar.leaderelection.Instance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageType; +import java.util.Map; + +/** + * Implementation of RingMessageManager. + */ +public class RingMessageManager extends AbstractMessageManager { + + /** + * Constructor of RingMessageManager. + */ + public RingMessageManager(Map instanceMap) { + super(instanceMap); + } + + /** + * Send heartbeat message to current leader instance to check the health. + * + * @param leaderId leaderID + * @return {@code true} if the leader is alive. + */ + @Override + public boolean sendHeartbeatMessage(int leaderId) { + Instance leaderInstance = instanceMap.get(leaderId); + boolean alive = leaderInstance.isAlive(); + return alive; + } + + /** + * Send election message to the next instance. + * + * @param currentId currentID + * @param content list contains all the IDs of instances which have received this election + * message. + * @return {@code true} if the election message is accepted by the target instance. + */ + @Override + public boolean sendElectionMessage(int currentId, String content) { + Instance nextInstance = this.findNextInstance(currentId); + Message electionMessage = new Message(MessageType.ELECTION, content); + nextInstance.onMessage(electionMessage); + return true; + } + + /** + * Send leader message to the next instance. + * + * @param currentId Instance ID of which sends this message. + * @param leaderId Leader message content. + * @return {@code true} if the leader message is accepted by the target instance. + */ + @Override + public boolean sendLeaderMessage(int currentId, int leaderId) { + Instance nextInstance = this.findNextInstance(currentId); + Message leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId)); + nextInstance.onMessage(leaderMessage); + return true; + } + + /** + * Send heartbeat invoke message to the next instance. + * + * @param currentId Instance ID of which sends this message. + */ + @Override + public void sendHeartbeatInvokeMessage(int currentId) { + Instance nextInstance = this.findNextInstance(currentId); + Message heartbeatInvokeMessage = new Message(MessageType.HEARTBEAT_INVOKE, ""); + nextInstance.onMessage(heartbeatInvokeMessage); + } + +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/MessageTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/MessageTest.java new file mode 100644 index 000000000000..272fef29ea3d --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/MessageTest.java @@ -0,0 +1,48 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Message test case. + */ +public class MessageTest { + + @Test + public void testGetType() { + Message message = new Message(MessageType.HEARTBEAT, ""); + assertEquals(MessageType.HEARTBEAT, message.getType()); + } + + @Test + public void testGetContent() { + String content = "test"; + Message message = new Message(MessageType.HEARTBEAT, content); + assertEquals(content, message.getContent()); + } + +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java new file mode 100644 index 000000000000..3ef36f758d97 --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java @@ -0,0 +1,39 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.bully; + +import org.junit.jupiter.api.Test; + +/** + * BullyApp unit test. + */ +public class BullyAppTest { + + @Test + public void test() { + String[] args = {}; + BullyApp.main(args); + } + +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java new file mode 100644 index 000000000000..524cf32170dc --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java @@ -0,0 +1,132 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.bully; + +import com.iluwatar.leaderelection.*; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Queue; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * BullyMessageManager unit test. + */ +public class BullyMessageManagerTest { + + @Test + public void testSendHeartbeatMessage() { + Instance instance1 = new BullyInstance(null, 1, 1); + Map instanceMap = Map.of(1, instance1); + MessageManager messageManager = new BullyMessageManager(instanceMap); + assertTrue(messageManager.sendHeartbeatMessage(1)); + } + + @Test + public void testSendElectionMessageNotAccepted() { + try { + Instance instance1 = new BullyInstance(null, 1, 1); + Instance instance2 = new BullyInstance(null, 1, 2); + Instance instance3 = new BullyInstance(null, 1, 3); + Instance instance4 = new BullyInstance(null, 1, 4); + Map instanceMap = Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); + instance1.setAlive(false); + MessageManager messageManager = new BullyMessageManager(instanceMap); + boolean result = messageManager.sendElectionMessage(3, "3"); + Class instanceClass = AbstractInstance.class; + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message message2 = ((Queue) messageQueueField.get(instance2)).poll(); + int instance4QueueSize = ((Queue) messageQueueField.get(instance4)).size(); + Message expectedMessage = new Message(MessageType.ELECTION_INVOKE, ""); + assertEquals(message2, expectedMessage); + assertEquals(instance4QueueSize, 0); + assertEquals(result, false); + } catch (IllegalAccessException | NoSuchFieldException e) { + fail("Error to access private field."); + } + } + + @Test + public void testElectionMessageAccepted() { + Instance instance1 = new BullyInstance(null, 1, 1); + Instance instance2 = new BullyInstance(null, 1, 2); + Instance instance3 = new BullyInstance(null, 1, 3); + Instance instance4 = new BullyInstance(null, 1, 4); + Map instanceMap = Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); + instance1.setAlive(false); + MessageManager messageManager = new BullyMessageManager(instanceMap); + boolean result = messageManager.sendElectionMessage(2, "2"); + assertEquals(result, true); + } + + @Test + public void testSendLeaderMessage() { + try { + Instance instance1 = new BullyInstance(null, 1, 1); + Instance instance2 = new BullyInstance(null, 1, 2); + Instance instance3 = new BullyInstance(null, 1, 3); + Instance instance4 = new BullyInstance(null, 1, 4); + Map instanceMap = Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); + instance1.setAlive(false); + MessageManager messageManager = new BullyMessageManager(instanceMap); + messageManager.sendLeaderMessage(2, 2); + Class instanceClass = AbstractInstance.class; + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message message3 = ((Queue) messageQueueField.get(instance3)).poll(); + Message message4 = ((Queue) messageQueueField.get(instance4)).poll(); + Message expectedMessage = new Message(MessageType.LEADER, "2"); + assertEquals(message3, expectedMessage); + assertEquals(message4, expectedMessage); + } catch (IllegalAccessException | NoSuchFieldException e) { + fail("Error to access private field."); + } + } + + @Test + public void testSendHeartbeatInvokeMessage() { + try { + Instance instance1 = new BullyInstance(null, 1, 1); + Instance instance2 = new BullyInstance(null, 1, 2); + Instance instance3 = new BullyInstance(null, 1, 3); + Map instanceMap = Map.of(1, instance1, 2, instance2, 3, instance3); + MessageManager messageManager = new BullyMessageManager(instanceMap); + messageManager.sendHeartbeatInvokeMessage(2); + Message message = new Message(MessageType.HEARTBEAT_INVOKE, ""); + Class instanceClass = AbstractInstance.class; + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message messageSent = ((Queue) messageQueueField.get(instance3)).poll(); + assertEquals(messageSent.getType(), message.getType()); + assertEquals(messageSent.getContent(), message.getContent()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Error to access private field."); + } + } + + +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java new file mode 100644 index 000000000000..a2bbf9c59d5d --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java @@ -0,0 +1,78 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.bully; + +import com.iluwatar.leaderelection.AbstractInstance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageType; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.Queue; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * BullyInstance unit test. + */ +public class BullyinstanceTest { + + @Test + public void testOnMessage() { + try { + final BullyInstance bullyInstance = new BullyInstance(null, 1, 1); + Message bullyMessage = new Message(MessageType.HEARTBEAT, ""); + bullyInstance.onMessage(bullyMessage); + Class instanceClass = AbstractInstance.class; + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + assertEquals(bullyMessage, ((Queue) messageQueueField.get(bullyInstance)).poll()); + } catch (IllegalAccessException | NoSuchFieldException e) { + fail("fail to access messasge queue."); + } + + } + + @Test + public void testIsAlive() { + try { + final BullyInstance bullyInstance = new BullyInstance(null, 1, 1); + Class instanceClass = AbstractInstance.class; + Field aliveField = instanceClass.getDeclaredField("alive"); + aliveField.setAccessible(true); + aliveField.set(bullyInstance, false); + assertFalse(bullyInstance.isAlive()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Fail to access field alive."); + } + } + + @Test + public void testSetAlive() { + final BullyInstance bullyInstance = new BullyInstance(null, 1, 1); + bullyInstance.setAlive(false); + assertFalse(bullyInstance.isAlive()); + } + +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java new file mode 100644 index 000000000000..ec3851d9be63 --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java @@ -0,0 +1,39 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.ring; + +import org.junit.jupiter.api.Test; + +/** + * RingApp unit test. + */ +public class RingAppTest { + + @Test + public void test() { + String[] args = {}; + RingApp.main(args); + } + +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java new file mode 100644 index 000000000000..6807b1480d8e --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java @@ -0,0 +1,76 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.AbstractInstance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageType; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.Queue; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * RingInstance unit test. + */ +public class RingInstanceTest { + + @Test + public void testOnMessage() { + try { + final RingInstance ringInstance = new RingInstance(null, 1, 1); + Message ringMessage = new Message(MessageType.HEARTBEAT, ""); + ringInstance.onMessage(ringMessage); + Class ringInstanceClass = AbstractInstance.class; + Field messageQueueField = ringInstanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + assertEquals(ringMessage, ((Queue) messageQueueField.get(ringInstance)).poll()); + } catch (IllegalAccessException | NoSuchFieldException e) { + fail("fail to access messasge queue."); + } + } + + @Test + public void testIsAlive() { + try { + final RingInstance ringInstance = new RingInstance(null, 1, 1); + Class ringInstanceClass = AbstractInstance.class; + Field aliveField = ringInstanceClass.getDeclaredField("alive"); + aliveField.setAccessible(true); + aliveField.set(ringInstance, false); + assertFalse(ringInstance.isAlive()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Fail to access field alive."); + } + } + + @Test + public void testSetAlive() { + final RingInstance ringInstance = new RingInstance(null, 1, 1); + ringInstance.setAlive(false); + assertFalse(ringInstance.isAlive()); + } +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java new file mode 100644 index 000000000000..d8429f02b05f --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java @@ -0,0 +1,112 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.*; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Queue; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * RingMessageManager unit test. + */ +public class RingMessageManagerTest { + + @Test + public void testSendHeartbeatMessage() { + Instance instance1 = new RingInstance(null, 1, 1); + Map instanceMap = Map.of(1, instance1); + MessageManager messageManager = new RingMessageManager(instanceMap); + assertTrue(messageManager.sendHeartbeatMessage(1)); + } + + @Test + public void testSendElectionMessage() { + try { + Instance instance1 = new RingInstance(null, 1, 1); + Instance instance2 = new RingInstance(null, 1, 2); + Instance instance3 = new RingInstance(null, 1, 3); + Map instanceMap = Map.of(1, instance1, 2, instance2, 3, instance3); + MessageManager messageManager = new RingMessageManager(instanceMap); + String messageContent = "2"; + messageManager.sendElectionMessage(2, messageContent); + Message ringMessage = new Message(MessageType.ELECTION, messageContent); + Class instanceClass = AbstractInstance.class; + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message ringMessageSent = ((Queue) messageQueueField.get(instance3)).poll(); + assertEquals(ringMessageSent.getType(), ringMessage.getType()); + assertEquals(ringMessageSent.getContent(), ringMessage.getContent()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Error to access private field."); + } + } + + @Test + public void testSendLeaderMessage() { + try { + Instance instance1 = new RingInstance(null, 1, 1); + Instance instance2 = new RingInstance(null, 1, 2); + Instance instance3 = new RingInstance(null, 1, 3); + Map instanceMap = Map.of(1, instance1, 2, instance2, 3, instance3); + MessageManager messageManager = new RingMessageManager(instanceMap); + String messageContent = "3"; + messageManager.sendLeaderMessage(2, 3); + Message ringMessage = new Message(MessageType.LEADER, messageContent); + Class instanceClass = AbstractInstance.class; + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message ringMessageSent = ((Queue) messageQueueField.get(instance3)).poll(); + assertEquals(ringMessageSent, ringMessage); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Error to access private field."); + } + } + + @Test + public void testSendHeartbeatInvokeMessage() { + try { + Instance instance1 = new RingInstance(null, 1, 1); + Instance instance2 = new RingInstance(null, 1, 2); + Instance instance3 = new RingInstance(null, 1, 3); + Map instanceMap = Map.of(1, instance1, 2, instance2, 3, instance3); + MessageManager messageManager = new RingMessageManager(instanceMap); + messageManager.sendHeartbeatInvokeMessage(2); + Message ringMessage = new Message(MessageType.HEARTBEAT_INVOKE, ""); + Class instanceClass = AbstractInstance.class; + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message ringMessageSent = ((Queue) messageQueueField.get(instance3)).poll(); + assertEquals(ringMessageSent.getType(), ringMessage.getType()); + assertEquals(ringMessageSent.getContent(), ringMessage.getContent()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Error to access private field."); + } + } + +} diff --git a/license-plugin-header-style.xml b/license-plugin-header-style.xml new file mode 100644 index 000000000000..db5e35461711 --- /dev/null +++ b/license-plugin-header-style.xml @@ -0,0 +1,41 @@ + + + + + + /* + * + */EOL + (\\s|\\t)*/\\*.*$ + .*\\*/(\\s|\\t)*$ + false + true + false + + diff --git a/marker/README.md b/marker/README.md index 6f5fc1e91c7a..a411827c3289 100644 --- a/marker/README.md +++ b/marker/README.md @@ -3,15 +3,15 @@ layout: pattern title: Marker Interface folder: marker permalink: /patterns/marker/ -categories: Other +categories: Structural tags: - - Java - - Difficulty-Beginner + - Decoupling --- ## Intent Using empty interfaces as markers to distinguish special treated objects. +## Class diagram ![alt text](./etc/MarkerDiagram.png "Marker Interface") ## Applicability diff --git a/marker/etc/marker.urm.puml b/marker/etc/marker.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/marker/etc/marker.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/marker/pom.xml b/marker/pom.xml index 168abaee8b12..5212832d8476 100644 --- a/marker/pom.xml +++ b/marker/pom.xml @@ -1,15 +1,19 @@ java-design-patterns com.iluwatar - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT 4.0.0 marker - - org.junit.jupiter - junit-jupiter-api - test - org.junit.jupiter junit-jupiter-engine @@ -47,6 +47,23 @@ test - - + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + App + + + + + + + + diff --git a/marker/src/main/java/App.java b/marker/src/main/java/App.java index d8fd54ecb3b9..384c999dc52d 100644 --- a/marker/src/main/java/App.java +++ b/marker/src/main/java/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,31 +20,28 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Created by Alexis on 28-Apr-17. - * With Marker interface idea is to make empty interface and extend it. - * Basically it is just to identify the special objects from normal objects. - * Like in case of serialization , objects that need to be serialized must implement serializable interface - * (it is empty interface) and down the line writeObject() method must be checking - * if it is a instance of serializable or not. - *

- * Marker interface vs annotation - * Marker interfaces and marker annotations both have their uses, - * neither of them is obsolete or always better then the other one. - * If you want to define a type that does not have any new methods associated with it, - * a marker interface is the way to go. - * If you want to mark program elements other than classes and interfaces, - * to allow for the possibility of adding more information to the marker in the future, - * or to fit the marker into a framework that already makes heavy use of annotation types, - * then a marker annotation is the correct choice + * Created by Alexis on 28-Apr-17. With Marker interface idea is to make empty interface and extend + * it. Basically it is just to identify the special objects from normal objects. Like in case of + * serialization , objects that need to be serialized must implement serializable interface (it is + * empty interface) and down the line writeObject() method must be checking if it is a instance of + * serializable or not. + * + *

Marker interface vs annotation Marker interfaces and marker annotations both have their uses, + * neither of them is obsolete or always better then the other one. If you want to define a type + * that does not have any new methods associated with it, a marker interface is the way to go. If + * you want to mark program elements other than classes and interfaces, to allow for the possibility + * of adding more information to the marker in the future, or to fit the marker into a framework + * that already makes heavy use of annotation types, then a marker annotation is the correct choice */ public class App { /** - * Program entry point + * Program entry point. * * @param args command line args */ diff --git a/marker/src/main/java/Guard.java b/marker/src/main/java/Guard.java index 4dd2a48872e6..9a57e15fdfb3 100644 --- a/marker/src/main/java/Guard.java +++ b/marker/src/main/java/Guard.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,11 +20,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Class defining Guard + * Class defining Guard. */ public class Guard implements Permission { diff --git a/marker/src/main/java/Permission.java b/marker/src/main/java/Permission.java index 2a85b336304d..5395ccadf9a1 100644 --- a/marker/src/main/java/Permission.java +++ b/marker/src/main/java/Permission.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,9 +20,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + /** - * Interface without any methods - * Marker interface is based on that assumption + * Interface without any methods Marker interface is based on that assumption. */ public interface Permission { } diff --git a/marker/src/main/java/Thief.java b/marker/src/main/java/Thief.java index b8d6464e51ac..341eae3777d9 100644 --- a/marker/src/main/java/Thief.java +++ b/marker/src/main/java/Thief.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,11 +20,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Class defining Thief + * Class defining Thief. */ public class Thief { diff --git a/marker/src/test/java/AppTest.java b/marker/src/test/java/AppTest.java index 856635007c35..5d63db0ad8d6 100644 --- a/marker/src/test/java/AppTest.java +++ b/marker/src/test/java/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/marker/src/test/java/GuardTest.java b/marker/src/test/java/GuardTest.java index cadd82f827a9..615d4e129886 100644 --- a/marker/src/test/java/GuardTest.java +++ b/marker/src/test/java/GuardTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/marker/src/test/java/ThiefTest.java b/marker/src/test/java/ThiefTest.java index 56492334c957..2732fc78a965 100644 --- a/marker/src/test/java/ThiefTest.java +++ b/marker/src/test/java/ThiefTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/master-worker-pattern/README.md b/master-worker-pattern/README.md index 4d2aa0e38d95..876d9206cf2d 100644 --- a/master-worker-pattern/README.md +++ b/master-worker-pattern/README.md @@ -3,10 +3,9 @@ layout: pattern title: Master-Worker folder: master-worker-pattern permalink: /patterns/master-worker-pattern/ -categories: Centralised Parallel Processing +categories: Concurrency tags: - - Java - - Difficulty-Intermediate + - Performance --- ## Also known as @@ -17,6 +16,9 @@ tags: > Used for centralised parallel processing. +## Class diagram +![alt text](./etc/master-worker-pattern.urm.png "Master-Worker pattern class diagram") + ## Applicability This pattern can be used when data can be divided into multiple parts, all of which need to go through the same computation to give a result, which need to be aggregated to get the final result. @@ -24,5 +26,6 @@ This pattern can be used when data can be divided into multiple parts, all of wh In this pattern, parallel processing is performed using a system consisting of a master and some number of workers, where a master divides the work among the workers, gets the result back from them and assimilates all the results to give final result. The only communication is between the master and the worker - none of the workers communicate among one another and the user only communicates with the master to get the required job done. The master has to maintain a record of how the divided data has been distributed, how many workers have finished their work and returned a result, and the results themselves to be able to aggregate the data correctly. ## Credits + * [https://docs.gigaspaces.com/sbp/master-worker-pattern.html] * [http://www.cs.sjsu.edu/~pearce/oom/patterns/behavioral/masterslave.htm] diff --git a/master-worker-pattern/etc/master-worker-pattern.urm.png b/master-worker-pattern/etc/master-worker-pattern.urm.png new file mode 100644 index 000000000000..a4c7a16d3ac9 Binary files /dev/null and b/master-worker-pattern/etc/master-worker-pattern.urm.png differ diff --git a/master-worker-pattern/etc/master-worker-pattern.urm.puml b/master-worker-pattern/etc/master-worker-pattern.urm.puml new file mode 100644 index 000000000000..b3b5add30671 --- /dev/null +++ b/master-worker-pattern/etc/master-worker-pattern.urm.puml @@ -0,0 +1,78 @@ +@startuml +package com.iluwatar.masterworker.system.systemmaster { + class ArrayTransposeMaster { + + ArrayTransposeMaster(numOfWorkers : int) + ~ aggregateData() : ArrayResult + ~ setWorkers(num : int) : ArrayList + } + abstract class Master { + - allResultData : Hashtable> + - expectedNumResults : int + - finalResult : Result + - numOfWorkers : int + - workers : ArrayList + ~ Master(numOfWorkers : int) + ~ aggregateData() : Result {abstract} + - collectResult(data : Result, workerId : int) + - divideWork(input : Input) + + doWork(input : Input) + ~ getAllResultData() : Hashtable> + ~ getExpectedNumResults() : int + + getFinalResult() : Result + ~ getWorkers() : ArrayList + + receiveData(data : Result, w : Worker) + ~ setWorkers(int) : ArrayList {abstract} + } +} +package com.iluwatar.masterworker.system { + class ArrayTransposeMasterWorker { + + ArrayTransposeMasterWorker() + ~ setMaster(numOfWorkers : int) : Master + } + abstract class MasterWorker { + - master : Master + + MasterWorker(numOfWorkers : int) + + getResult(input : Input) : Result + ~ setMaster(int) : Master {abstract} + } +} +package com.iluwatar.masterworker { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + class ArrayInput { + + ArrayInput(data : int[][]) + + divideData(num : int) : ArrayList> + ~ makeDivisions(data : int[][], num : int) : int[] {static} + } + class ArrayResult { + + ArrayResult(data : int[][]) + } + class ArrayUtilityMethods { + - LOGGER : Logger {static} + - RANDOM : Random {static} + + ArrayUtilityMethods() + + arraysSame(a1 : int[], a2 : int[]) : boolean {static} + + createRandomIntMatrix(rows : int, columns : int) : int[][] {static} + + matricesSame(m1 : int[][], m2 : int[][]) : boolean {static} + + printMatrix(matrix : int[][]) {static} + } + abstract class Input { + + data : T + + Input(data : T) + + divideData(int) : ArrayList> {abstract} + } + abstract class Result { + + data : T + + Result(data : T) + } +} +Master --> "-finalResult" Result +MasterWorker --> "-master" Master +ArrayInput --|> Input +ArrayResult --|> Result +ArrayTransposeMasterWorker --|> MasterWorker +ArrayTransposeMaster --|> Master +@enduml \ No newline at end of file diff --git a/master-worker-pattern/pom.xml b/master-worker-pattern/pom.xml index 6b413ea64c63..9924d6a5a5f0 100644 --- a/master-worker-pattern/pom.xml +++ b/master-worker-pattern/pom.xml @@ -1,7 +1,7 @@ "-members" PartyMember +PartyMemberBase --> "-party" Party +Hobbit --|> PartyMemberBase +Hunter --|> PartyMemberBase +PartyImpl ..|> Party +PartyMemberBase ..|> PartyMember +Rogue --|> PartyMemberBase +Wizard --|> PartyMemberBase +@enduml \ No newline at end of file diff --git a/mediator/pom.xml b/mediator/pom.xml index 83cc0f70d93f..23d28726bdc5 100644 --- a/mediator/pom.xml +++ b/mediator/pom.xml @@ -2,7 +2,7 @@ "-type" StarType +Star --> "-type" StarType +StarMementoInternal ..+ Star +StarMementoInternal ..|> StarMemento +@enduml \ No newline at end of file diff --git a/memento/pom.xml b/memento/pom.xml index 8846bcdcc813..70121cea3e56 100644 --- a/memento/pom.xml +++ b/memento/pom.xml @@ -2,7 +2,7 @@ "-nourishment" Nourishment +GiantController --> "-giant" GiantModel +GiantModel --> "-fatigue" Fatigue +GiantModel --> "-health" Health +GiantController --> "-view" GiantView +@enduml \ No newline at end of file diff --git a/model-view-controller/pom.xml b/model-view-controller/pom.xml index d2b223dc2615..a8ef230e85d6 100644 --- a/model-view-controller/pom.xml +++ b/model-view-controller/pom.xml @@ -2,7 +2,7 @@ "-presenter" FileSelectorPresenter +FileSelectorStub --> "-presenter" FileSelectorPresenter +FileSelectorPresenter --> "-view" FileSelectorView +FileSelectorPresenter --> "-loader" FileLoader +FileSelectorJFrame ..|> FileSelectorView +FileSelectorStub ..|> FileSelectorView +@enduml \ No newline at end of file diff --git a/model-view-presenter/pom.xml b/model-view-presenter/pom.xml index a0f7d31b23b5..97b47f82c361 100644 --- a/model-view-presenter/pom.xml +++ b/model-view-presenter/pom.xml @@ -2,7 +2,7 @@ "-singleton" FileLoggerModule +App --> "-consoleLoggerModule" ConsoleLoggerModule +ConsoleLoggerModule --> "-singleton" ConsoleLoggerModule +App --> "-fileLoggerModule" FileLoggerModule +@enduml \ No newline at end of file diff --git a/module/pom.xml b/module/pom.xml index 56e0a8b35715..25ad707ebc20 100644 --- a/module/pom.xml +++ b/module/pom.xml @@ -2,7 +2,7 @@ "-sex" Sex +@enduml \ No newline at end of file diff --git a/monad/pom.xml b/monad/pom.xml index 2e2e34870830..f553c3079767 100644 --- a/monad/pom.xml +++ b/monad/pom.xml @@ -2,7 +2,7 @@ "-SERVERS" Server +@enduml \ No newline at end of file diff --git a/monostate/pom.xml b/monostate/pom.xml index 920311a8bac7..0e51fc70084a 100644 --- a/monostate/pom.xml +++ b/monostate/pom.xml @@ -2,7 +2,7 @@ "-name" NazgulName +@enduml \ No newline at end of file diff --git a/multiton/pom.xml b/multiton/pom.xml index f1abf43a13f0..ef1e9c8921a9 100644 --- a/multiton/pom.xml +++ b/multiton/pom.xml @@ -2,7 +2,7 @@ + @@ -21,19 +30,33 @@ com.iluwatar java-design-patterns - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT mute-idiom - - org.junit.jupiter - junit-jupiter-api - test - org.junit.jupiter junit-jupiter-engine test + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.mute.App + + + + + + + + diff --git a/mute-idiom/src/main/java/com/iluwatar/mute/App.java b/mute-idiom/src/main/java/com/iluwatar/mute/App.java index c35559dcd97f..d4f140bf089e 100644 --- a/mute-idiom/src/main/java/com/iluwatar/mute/App.java +++ b/mute-idiom/src/main/java/com/iluwatar/mute/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,19 +23,17 @@ package com.iluwatar.mute; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.sql.SQLException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * Mute pattern is utilized when we need to suppress an exception due to an API flaw or in - * situation when all we can do to handle the exception is to log it. - * This pattern should not be used everywhere. It is very important to logically handle the - * exceptions in a system, but some situations like the ones described above require this pattern, - * so that we don't need to repeat + * Mute pattern is utilized when we need to suppress an exception due to an API flaw or in situation + * when all we can do to handle the exception is to log it. This pattern should not be used + * everywhere. It is very important to logically handle the exceptions in a system, but some + * situations like the ones described above require this pattern, so that we don't need to repeat *

  * 
  *   try {
@@ -45,7 +43,6 @@
  *   }
  * 
  * 
every time we need to ignore an exception. - * */ public class App { @@ -53,7 +50,7 @@ public class App { /** * Program entry point. - * + * * @param args command line args. * @throws Exception if any exception occurs */ @@ -65,7 +62,7 @@ public static void main(String[] args) throws Exception { } /* - * Typically used when the API declares some exception but cannot do so. Usually a + * Typically used when the API declares some exception but cannot do so. Usually a * signature mistake.In this example out is not supposed to throw exception as it is a * ByteArrayOutputStream. So we utilize mute, which will throw AssertionError if unexpected * exception occurs. @@ -98,7 +95,7 @@ private static void utilizeResource(Resource resource) throws SQLException { private static Resource acquireResource() throws SQLException { return new Resource() { - + @Override public void close() throws IOException { throw new IOException("Error in closing resource: " + this); diff --git a/mute-idiom/src/main/java/com/iluwatar/mute/CheckedRunnable.java b/mute-idiom/src/main/java/com/iluwatar/mute/CheckedRunnable.java index 7a37ef03dfe0..f15153d0083d 100644 --- a/mute-idiom/src/main/java/com/iluwatar/mute/CheckedRunnable.java +++ b/mute-idiom/src/main/java/com/iluwatar/mute/CheckedRunnable.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,12 +25,12 @@ /** * A runnable which may throw exception on execution. - * */ @FunctionalInterface public interface CheckedRunnable { /** * Same as {@link Runnable#run()} with a possibility of exception in execution. + * * @throws Exception if any exception occurs. */ void run() throws Exception; diff --git a/mute-idiom/src/main/java/com/iluwatar/mute/Mute.java b/mute-idiom/src/main/java/com/iluwatar/mute/Mute.java index d5d9b802849f..30e1698d5d1b 100644 --- a/mute-idiom/src/main/java/com/iluwatar/mute/Mute.java +++ b/mute-idiom/src/main/java/com/iluwatar/mute/Mute.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.mute; import java.io.ByteArrayOutputStream; @@ -29,17 +30,18 @@ * A utility class that allows you to utilize mute idiom. */ public final class Mute { - + // The constructor is never meant to be called. - private Mute() {} + private Mute() { + } /** - * Executes the runnable and throws the exception occurred within a {@link AssertionError}. - * This method should be utilized to mute the operations that are guaranteed not to throw an exception. - * For instance {@link ByteArrayOutputStream#write(byte[])} declares in it's signature that it can throw - * an {@link IOException}, but in reality it cannot. This is because the bulk write method is not overridden - * in {@link ByteArrayOutputStream}. - * + * Executes the runnable and throws the exception occurred within a {@link + * AssertionError}. This method should be utilized to mute the operations that are guaranteed not + * to throw an exception. For instance {@link ByteArrayOutputStream#write(byte[])} declares in + * it's signature that it can throw an {@link IOException}, but in reality it cannot. This is + * because the bulk write method is not overridden in {@link ByteArrayOutputStream}. + * * @param runnable a runnable that should never throw an exception on execution. */ public static void mute(CheckedRunnable runnable) { @@ -51,11 +53,11 @@ public static void mute(CheckedRunnable runnable) { } /** - * Executes the runnable and logs the exception occurred on {@link System#err}. - * This method should be utilized to mute the operations about which most you can do is log. - * For instance while closing a connection to database, or cleaning up a resource, - * all you can do is log the exception occurred. - * + * Executes the runnable and logs the exception occurred on {@link System#err}. This + * method should be utilized to mute the operations about which most you can do is log. For + * instance while closing a connection to database, or cleaning up a resource, all you can do is + * log the exception occurred. + * * @param runnable a runnable that may throw an exception on execution. */ public static void loggedMute(CheckedRunnable runnable) { diff --git a/mute-idiom/src/main/java/com/iluwatar/mute/Resource.java b/mute-idiom/src/main/java/com/iluwatar/mute/Resource.java index 11bb3a6ec875..e56025ce4ea8 100644 --- a/mute-idiom/src/main/java/com/iluwatar/mute/Resource.java +++ b/mute-idiom/src/main/java/com/iluwatar/mute/Resource.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,9 +26,8 @@ import java.io.Closeable; /** - * Represents any resource that the application might acquire and that must be closed - * after it is utilized. Example of such resources can be a database connection, open - * files, sockets. + * Represents any resource that the application might acquire and that must be closed after it is + * utilized. Example of such resources can be a database connection, open files, sockets. */ public interface Resource extends Closeable { diff --git a/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java b/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java index 31624c99449b..5ca525a9d9f5 100644 --- a/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java +++ b/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java b/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java index 07498645e83c..f2743113bea1 100644 --- a/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java +++ b/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/mutex/README.md b/mutex/README.md index 84755872f56f..43ed8d94732d 100644 --- a/mutex/README.md +++ b/mutex/README.md @@ -5,24 +5,25 @@ folder: mutex permalink: /patterns/mutex/ categories: Concurrency tags: - - Java - - Difficulty-Intermediate + - Decoupling --- ## Also known as -Mutual Exclusion Lock -Binary Semaphore + +* Mutual Exclusion Lock +* Binary Semaphore ## Intent Create a lock which only allows a single thread to access a resource at any one instant. +## Class diagram ![alt text](./etc/mutex.png "Mutex") ## Applicability Use a Mutex when -* you need to prevent two threads accessing a critical section at the same time -* concurrent access to a resource could lead to a race condition +* You need to prevent two threads accessing a critical section at the same time +* Concurrent access to a resource could lead to a race condition ## Credits diff --git a/mutex/etc/mutex.urm.puml b/mutex/etc/mutex.urm.puml new file mode 100644 index 000000000000..08cf43b90795 --- /dev/null +++ b/mutex/etc/mutex.urm.puml @@ -0,0 +1,27 @@ +@startuml +package com.iluwatar.mutex { + class App { + + App() + + main(args : String[]) {static} + } + class Jar { + - beans : int + - lock : Lock + + Jar(beans : int, lock : Lock) + + takeBean() : boolean + } + interface Lock { + + acquire() {abstract} + + release() {abstract} + } + class Mutex { + - owner : Object + + Mutex() + + acquire() + + getOwner() : Object + + release() + } +} +Jar --> "-lock" Lock +Mutex ..|> Lock +@enduml \ No newline at end of file diff --git a/mutex/pom.xml b/mutex/pom.xml index ed9e6b264dbc..9cdff25e42e2 100644 --- a/mutex/pom.xml +++ b/mutex/pom.xml @@ -2,7 +2,7 @@ + 4.0.0 @@ -16,7 +30,7 @@ com.iluwatar naked-objects - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT naked-objects-dom @@ -36,6 +50,17 @@ + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler.version} + + 8 + 8 + + + @@ -78,7 +103,7 @@ true - 4.0.1 + 5.2.1 @@ -118,7 +143,6 @@ ${datanucleus-maven-plugin.version} false - ${basedir}/log4j.properties true ${basedir}/datanucleus.properties diff --git a/naked-objects/dom/src/main/java/META-INF/persistence.xml b/naked-objects/dom/src/main/java/META-INF/persistence.xml index 8824aa1ace92..fd916b0456bd 100644 --- a/naked-objects/dom/src/main/java/META-INF/persistence.xml +++ b/naked-objects/dom/src/main/java/META-INF/persistence.xml @@ -1,21 +1,27 @@ updateName (action) /** - * Event used to update the Name in the Domain + * Event used to update the Name in the Domain. */ public static class UpdateNameDomainEvent extends ActionDomainEvent { public UpdateNameDomainEvent(final SimpleObject source, final Identifier identifier, - final Object... arguments) { + final Object... arguments) { super(source, identifier, arguments); } } diff --git a/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.layout.json b/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.layout.json index 3d5e23f7c0db..78b2ac096530 100644 --- a/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.layout.json +++ b/naked-objects/dom/src/main/java/domainapp/dom/modules/simple/SimpleObject.layout.json @@ -15,42 +15,42 @@ * limitations under the License. */ { - "columns": [ - { - "span": 6, - "memberGroups": { - "General": { - "members": { - "name": { - "actions": { - "updateName": { - "actionLayout": { - "position": "BOTTOM" - } - } - } - }, - "versionSequence": { - "propertyLayout": { - "name": "version" - } - } - } + "columns": [ + { + "span": 6, + "memberGroups": { + "General": { + "members": { + "name": { + "actions": { + "updateName": { + "actionLayout": { + "position": "BOTTOM" + } } + } + }, + "versionSequence": { + "propertyLayout": { + "name": "version" + } } - }, - { - "span": 0, - "memberGroups": {} - }, - { - "span": 0, - "memberGroups": {} - }, - { - "span": 6, - "collections": {} + } } - ], - "actions": {} + } + }, + { + "span": 0, + "memberGroups": {} + }, + { + "span": 0, + "memberGroups": {} + }, + { + "span": 6, + "collections": {} + } + ], + "actions": {} } \ No newline at end of file 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 249fbb1f07cb..88a4666e016f 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 @@ -1,21 +1,29 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * http://www.apache.org/licenses/LICENSE-2.0 + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package domainapp.dom.modules.simple; import java.util.List; - import org.apache.isis.applib.DomainObjectContainer; import org.apache.isis.applib.Identifier; import org.apache.isis.applib.annotation.Action; @@ -31,7 +39,7 @@ import org.apache.isis.applib.services.i18n.TranslatableString; /** - * Domain Service for Simple Objects + * Domain Service for Simple Objects. */ @DomainService(repositoryFor = SimpleObject.class) @DomainServiceLayout(menuOrder = "10") @@ -73,18 +81,18 @@ public List findByName(@ParameterLayout(named = "Name") final Stri // endregion /** - * Create Domain Event on SimpleObjects + * Create Domain Event on SimpleObjects. */ // region > create (action) public static class CreateDomainEvent extends ActionDomainEvent { public CreateDomainEvent(final SimpleObjects source, final Identifier identifier, - final Object... arguments) { + final Object... arguments) { super(source, identifier, arguments); } } /** - * Create simple object + * Create simple object. */ @Action(domainEvent = CreateDomainEvent.class) @MemberOrder(sequence = "3") diff --git a/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectTest.java b/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectTest.java index 085a01408d1c..03ab30f7561c 100644 --- a/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectTest.java +++ b/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectTest.java @@ -1,17 +1,26 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * http://www.apache.org/licenses/LICENSE-2.0 + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package domainapp.dom.modules.simple; import static org.junit.Assert.assertEquals; diff --git a/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectsTest.java b/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectsTest.java index 52ea8e0a1485..a95ad5aa3b7d 100644 --- a/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectsTest.java +++ b/naked-objects/dom/src/test/java/domainapp/dom/modules/simple/SimpleObjectsTest.java @@ -1,17 +1,26 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * http://www.apache.org/licenses/LICENSE-2.0 + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package domainapp.dom.modules.simple; import com.google.common.collect.Lists; diff --git a/naked-objects/etc/naked-objects.urm.puml b/naked-objects/etc/naked-objects.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/naked-objects/etc/naked-objects.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/naked-objects/fixture/etc/naked-objects-fixture.urm.puml b/naked-objects/fixture/etc/naked-objects-fixture.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/naked-objects/fixture/etc/naked-objects-fixture.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/naked-objects/fixture/pom.xml b/naked-objects/fixture/pom.xml index cdb77b1b940e..3457ac0a42ab 100644 --- a/naked-objects/fixture/pom.xml +++ b/naked-objects/fixture/pom.xml @@ -1,14 +1,28 @@ - + 4.0.0 @@ -16,7 +30,7 @@ com.iluwatar naked-objects - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT naked-objects-fixture diff --git a/naked-objects/fixture/src/main/java/domainapp/fixture/DomainAppFixturesProvider.java b/naked-objects/fixture/src/main/java/domainapp/fixture/DomainAppFixturesProvider.java index ccc11f2b8ece..d7da5685846d 100644 --- a/naked-objects/fixture/src/main/java/domainapp/fixture/DomainAppFixturesProvider.java +++ b/naked-objects/fixture/src/main/java/domainapp/fixture/DomainAppFixturesProvider.java @@ -1,27 +1,35 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package domainapp.fixture; +import domainapp.fixture.scenarios.RecreateSimpleObjects; import org.apache.isis.applib.annotation.DomainService; import org.apache.isis.applib.annotation.NatureOfService; import org.apache.isis.applib.fixturescripts.FixtureScripts; import org.apache.isis.applib.services.fixturespec.FixtureScriptsSpecification; import org.apache.isis.applib.services.fixturespec.FixtureScriptsSpecificationProvider; -import domainapp.fixture.scenarios.RecreateSimpleObjects; - /** * Specifies where to find fixtures, and other settings. */ 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 58b656a9816f..dc19195acb66 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 @@ -1,27 +1,34 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * http://www.apache.org/licenses/LICENSE-2.0 + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package domainapp.fixture.modules.simple; -import org.apache.isis.applib.fixturescripts.FixtureScript; - import domainapp.dom.modules.simple.SimpleObject; import domainapp.dom.modules.simple.SimpleObjects; +import org.apache.isis.applib.fixturescripts.FixtureScript; /** - * Fixture to create a simple object + * Fixture to create a simple object. */ public class SimpleObjectCreate extends FixtureScript { @@ -38,7 +45,7 @@ public class SimpleObjectCreate extends FixtureScript { private String name; /** - * Name of the object (required) + * Name of the object (required). */ public String getName() { return name; diff --git a/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectsTearDown.java b/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectsTearDown.java index c0319d95383e..b3cea73c4b87 100644 --- a/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectsTearDown.java +++ b/naked-objects/fixture/src/main/java/domainapp/fixture/modules/simple/SimpleObjectsTearDown.java @@ -1,16 +1,24 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * http://www.apache.org/licenses/LICENSE-2.0 + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package domainapp.fixture.modules.simple; @@ -19,7 +27,7 @@ import org.apache.isis.applib.services.jdosupport.IsisJdoSupport; /** - * TearDown/Cleanup for SimpleObjects + * TearDown/Cleanup for SimpleObjects. */ public class SimpleObjectsTearDown extends FixtureScript { 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 be891158a0d7..847f15d017fb 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 @@ -1,39 +1,43 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * http://www.apache.org/licenses/LICENSE-2.0 + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package domainapp.fixture.scenarios; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - import com.google.common.collect.Lists; - -import org.apache.isis.applib.fixturescripts.FixtureScript; - import domainapp.dom.modules.simple.SimpleObject; import domainapp.fixture.modules.simple.SimpleObjectCreate; import domainapp.fixture.modules.simple.SimpleObjectsTearDown; +import java.util.Collections; +import java.util.List; +import org.apache.isis.applib.fixturescripts.FixtureScript; /** - * Create a bunch of simple Objects + * Create a bunch of simple Objects. */ public class RecreateSimpleObjects extends FixtureScript { - public final List names = Collections.unmodifiableList(Arrays.asList("Foo", "Bar", "Baz", + public final List names = Collections.unmodifiableList(List.of("Foo", "Bar", "Baz", "Frodo", "Froyo", "Fizz", "Bip", "Bop", "Bang", "Boo")); // region > number (optional input) diff --git a/naked-objects/integtests/etc/naked-objects-integtests.urm.puml b/naked-objects/integtests/etc/naked-objects-integtests.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/naked-objects/integtests/etc/naked-objects-integtests.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/naked-objects/integtests/pom.xml b/naked-objects/integtests/pom.xml index 460f2f5060cc..f46541f48fb4 100644 --- a/naked-objects/integtests/pom.xml +++ b/naked-objects/integtests/pom.xml @@ -1,14 +1,28 @@ - + 4.0.0 @@ -16,7 +30,7 @@ com.iluwatar naked-objects - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT naked-objects-integtests @@ -90,6 +104,11 @@ hsqldb + + javax.annotation + javax.annotation-api + + - 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 3ac5a1d75f2e..f67c268766c4 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 @@ -1,17 +1,26 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * http://www.apache.org/licenses/LICENSE-2.0 + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package domainapp.integtests.bootstrap; import org.apache.isis.core.commons.config.IsisConfiguration; @@ -41,7 +50,6 @@ public static void initIsft() { private static class SimpleAppSystemBuilder extends IsisSystemForTest.Builder { public SimpleAppSystemBuilder() { - withLoggingAt(org.apache.log4j.Level.INFO); with(testConfiguration()); with(new DataNucleusPersistenceMechanismInstaller()); diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/BootstrappingGlue.java b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/BootstrappingGlue.java index e41399fdd7d8..b3bd973bd0e2 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/BootstrappingGlue.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/BootstrappingGlue.java @@ -1,17 +1,26 @@ -/** - * O * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * http://www.apache.org/licenses/LICENSE-2.0 + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package domainapp.integtests.specglue; import org.apache.isis.core.specsupport.scenarios.ScenarioExecutionScope; @@ -28,7 +37,6 @@ public class BootstrappingGlue extends CukeGlueAbstract { @Before(value = {"@integration"}, order = 100) public void beforeScenarioIntegrationScope() { - org.apache.log4j.PropertyConfigurator.configure("logging.properties"); SimpleAppSystemInitializer.initIsft(); before(ScenarioExecutionScope.INTEGRATION); diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/CatalogOfFixturesGlue.java b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/CatalogOfFixturesGlue.java index 7a75a038139f..025c6724aa32 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/CatalogOfFixturesGlue.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/specglue/CatalogOfFixturesGlue.java @@ -1,17 +1,26 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * http://www.apache.org/licenses/LICENSE-2.0 + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package domainapp.integtests.specglue; import org.apache.isis.core.specsupport.specs.CukeGlueAbstract; 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 b7af9f05232d..7b508faf38cf 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 @@ -1,17 +1,26 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * http://www.apache.org/licenses/LICENSE-2.0 + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package domainapp.integtests.specglue.modules.simple; import static org.hamcrest.CoreMatchers.is; diff --git a/naked-objects/integtests/src/test/java/domainapp/integtests/specs/RunSpecs.java b/naked-objects/integtests/src/test/java/domainapp/integtests/specs/RunSpecs.java index 8a842a0f3140..d92cfa88185c 100644 --- a/naked-objects/integtests/src/test/java/domainapp/integtests/specs/RunSpecs.java +++ b/naked-objects/integtests/src/test/java/domainapp/integtests/specs/RunSpecs.java @@ -1,17 +1,26 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * http://www.apache.org/licenses/LICENSE-2.0 + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package domainapp.integtests.specs; import org.junit.runner.RunWith; 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 66deaeb84034..24d8d207ce02 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 @@ -1,21 +1,26 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * http://www.apache.org/licenses/LICENSE-2.0 + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package domainapp.integtests.tests; import org.junit.BeforeClass; @@ -32,7 +37,6 @@ public abstract class SimpleAppIntegTest extends IntegrationTestAbstract { @BeforeClass public static void initClass() { - org.apache.log4j.PropertyConfigurator.configure("logging.properties"); SimpleAppSystemInitializer.initIsft(); // instantiating will install onto ThreadLocal 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 3d9009bf88af..11ff6a47d400 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 @@ -1,21 +1,26 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * http://www.apache.org/licenses/LICENSE-2.0 + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package domainapp.integtests.tests.modules.simple; import static org.junit.Assert.assertEquals; 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 168d4865a574..c762dd88f9b5 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 @@ -1,21 +1,26 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * http://www.apache.org/licenses/LICENSE-2.0 + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + package domainapp.integtests.tests.modules.simple; import static org.junit.Assert.assertEquals; diff --git a/naked-objects/pom.xml b/naked-objects/pom.xml index 2a1832ea7e63..0ca5a3ef3cb4 100644 --- a/naked-objects/pom.xml +++ b/naked-objects/pom.xml @@ -1,39 +1,44 @@ - + 4.0.0 java-design-patterns com.iluwatar - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT - naked-objects - pom - - - 3.0.4 - - 1.9.0 - UTF-8 UTF-8 2.0.0 - apache.snapshots @@ -90,31 +95,9 @@ - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.8 - 1.8 - -parameters - - - - source - compile - - - test - test-compile - - - - org.apache.maven.plugins maven-surefire-plugin - 2.16 **/*Test.java @@ -350,17 +333,17 @@ ${project.groupId} naked-objects-dom - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT ${project.groupId} naked-objects-fixture - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT ${project.groupId} naked-objects-webapp - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT diff --git a/naked-objects/webapp/etc/naked-objects-webapp.urm.puml b/naked-objects/webapp/etc/naked-objects-webapp.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/naked-objects/webapp/etc/naked-objects-webapp.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/naked-objects/webapp/ide/intellij/launch/README.txt b/naked-objects/webapp/ide/intellij/launch/README.txt index 6659454efdbe..2977f3068716 100644 --- a/naked-objects/webapp/ide/intellij/launch/README.txt +++ b/naked-objects/webapp/ide/intellij/launch/README.txt @@ -1,6 +1,6 @@ ==== The MIT License - Copyright (c) 2014-2016 Ilkka Seppälä + Copyright © 2014-2019 Ilkka Seppälä Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/naked-objects/webapp/ide/intellij/launch/SimpleApp_PROTOTYPE.xml b/naked-objects/webapp/ide/intellij/launch/SimpleApp_PROTOTYPE.xml index 9831f9a1f7bf..c27b5fbdd00a 100644 --- a/naked-objects/webapp/ide/intellij/launch/SimpleApp_PROTOTYPE.xml +++ b/naked-objects/webapp/ide/intellij/launch/SimpleApp_PROTOTYPE.xml @@ -1,7 +1,7 @@ + 4.0.0 @@ -16,7 +30,7 @@ com.iluwatar naked-objects - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT naked-objects-webapp @@ -202,26 +216,6 @@ hsqldb - - - - - - - org.lazyluke - log4jdbc-remix - - - org.slf4j - slf4j-api - - - - - junit junit 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 459e4b7de9e0..8425712dcc31 100644 --- a/naked-objects/webapp/src/main/java/domainapp/webapp/SimpleApplication.java +++ b/naked-objects/webapp/src/main/java/domainapp/webapp/SimpleApplication.java @@ -1,25 +1,27 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ -package domainapp.webapp; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.List; - -import javax.servlet.http.HttpServletRequest; +package domainapp.webapp; import com.google.common.base.Joiner; import com.google.common.io.Resources; @@ -28,28 +30,31 @@ import com.google.inject.name.Names; import com.google.inject.util.Modules; import com.google.inject.util.Providers; - +import de.agilecoders.wicket.core.Bootstrap; +import de.agilecoders.wicket.core.settings.IBootstrapSettings; +import de.agilecoders.wicket.themes.markup.html.bootswatch.BootswatchTheme; +import de.agilecoders.wicket.themes.markup.html.bootswatch.BootswatchThemeProvider; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import org.apache.isis.viewer.wicket.viewer.IsisWicketApplication; +import org.apache.isis.viewer.wicket.viewer.integration.wicket.AuthenticatedWebSessionForIsis; import org.apache.wicket.Session; import org.apache.wicket.request.IRequestParameters; import org.apache.wicket.request.Request; import org.apache.wicket.request.Response; import org.apache.wicket.request.http.WebRequest; - -import org.apache.isis.viewer.wicket.viewer.IsisWicketApplication; -import org.apache.isis.viewer.wicket.viewer.integration.wicket.AuthenticatedWebSessionForIsis; - -import de.agilecoders.wicket.core.Bootstrap; -import de.agilecoders.wicket.core.settings.IBootstrapSettings; -import de.agilecoders.wicket.themes.markup.html.bootswatch.BootswatchTheme; -import de.agilecoders.wicket.themes.markup.html.bootswatch.BootswatchThemeProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * As specified in web.xml. - * - *

- * See: - * + * + *

See: + * *

  * <filter>
  *   <filter-name>wicket</filter-name>
@@ -60,19 +65,18 @@
  *    </init-param>
  * </filter>
  * 
- * */ public class SimpleApplication extends IsisWicketApplication { + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleApplication.class); private static final long serialVersionUID = 1L; /** * uncomment for a (slightly hacky) way of allowing logins using query args, eg: - * + * * {@code ?user=sven&pass=pass} - * - *

- * for demos only, obvious. + * + *

for demos only, obvious. */ private static final boolean DEMO_MODE_USING_CREDENTIALS_AS_QUERYARGS = false; @@ -116,7 +120,7 @@ public WebRequest newWebRequest(HttpServletRequest servletRequest, String filter servletRequest.getSession().invalidate(); } } catch (Exception e) { - System.out.println(e); + LOGGER.error(e.getMessage()); } return super.newWebRequest(servletRequest, filterPath); } diff --git a/naked-objects/webapp/src/main/resources/domainapp/webapp/welcome.html b/naked-objects/webapp/src/main/resources/domainapp/webapp/welcome.html index a87d67384a9f..e81389640efc 100644 --- a/naked-objects/webapp/src/main/resources/domainapp/webapp/welcome.html +++ b/naked-objects/webapp/src/main/resources/domainapp/webapp/welcome.html @@ -1,21 +1,27 @@

Apache Isis™ is a platform to let you rapidly develop diff --git a/naked-objects/webapp/src/main/webapp/WEB-INF/isis.properties b/naked-objects/webapp/src/main/webapp/WEB-INF/isis.properties index 929d1775a8b9..2ebd42968626 100644 --- a/naked-objects/webapp/src/main/webapp/WEB-INF/isis.properties +++ b/naked-objects/webapp/src/main/webapp/WEB-INF/isis.properties @@ -1,19 +1,25 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. +# +# The MIT License +# Copyright © 2014-2019 Ilkka Seppälä +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# ################################################################################# diff --git a/naked-objects/webapp/src/main/webapp/WEB-INF/persistor.properties b/naked-objects/webapp/src/main/webapp/WEB-INF/persistor.properties index c73af73c78d4..594b97edec95 100644 --- a/naked-objects/webapp/src/main/webapp/WEB-INF/persistor.properties +++ b/naked-objects/webapp/src/main/webapp/WEB-INF/persistor.properties @@ -1,19 +1,25 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. +# +# The MIT License +# Copyright © 2014-2019 Ilkka Seppälä +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# diff --git a/naked-objects/webapp/src/main/webapp/WEB-INF/persistor_datanucleus.properties b/naked-objects/webapp/src/main/webapp/WEB-INF/persistor_datanucleus.properties index 675ced3bf8c0..572b2e194c95 100644 --- a/naked-objects/webapp/src/main/webapp/WEB-INF/persistor_datanucleus.properties +++ b/naked-objects/webapp/src/main/webapp/WEB-INF/persistor_datanucleus.properties @@ -1,19 +1,25 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. +# +# The MIT License +# Copyright © 2014-2019 Ilkka Seppälä +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# # # configuration file for the JDO/DataNucleus objectstore diff --git a/naked-objects/webapp/src/main/webapp/WEB-INF/viewer_restfulobjects.properties b/naked-objects/webapp/src/main/webapp/WEB-INF/viewer_restfulobjects.properties index 0a85fb681bc9..34f639a549fd 100644 --- a/naked-objects/webapp/src/main/webapp/WEB-INF/viewer_restfulobjects.properties +++ b/naked-objects/webapp/src/main/webapp/WEB-INF/viewer_restfulobjects.properties @@ -1,19 +1,25 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. +# +# The MIT License +# Copyright © 2014-2019 Ilkka Seppälä +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# # # configuration file for the Restful Objects viewer diff --git a/naked-objects/webapp/src/main/webapp/WEB-INF/viewer_wicket.properties b/naked-objects/webapp/src/main/webapp/WEB-INF/viewer_wicket.properties index ba9eaaffb873..b52c7fa6e42e 100644 --- a/naked-objects/webapp/src/main/webapp/WEB-INF/viewer_wicket.properties +++ b/naked-objects/webapp/src/main/webapp/WEB-INF/viewer_wicket.properties @@ -1,19 +1,25 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. +# +# The MIT License +# Copyright © 2014-2019 Ilkka Seppälä +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# # # configuration file for the Wicket viewer diff --git a/naked-objects/webapp/src/main/webapp/WEB-INF/web.xml b/naked-objects/webapp/src/main/webapp/WEB-INF/web.xml index bb6098f0bcfa..6a3ecd65dfdc 100644 --- a/naked-objects/webapp/src/main/webapp/WEB-INF/web.xml +++ b/naked-objects/webapp/src/main/webapp/WEB-INF/web.xml @@ -1,21 +1,27 @@ This app has been generated using Apache Isis' - SimpleApp archetype, + SimpleApp archetype, to create a purposefully minimal application that nevertheless includes fixture data, integration tests and BDD specs.

- The app itself consists of a single domain class, SimpleObject, - along with an equally simple (factory/repository) domain service, SimpleObjects. + The app itself consists of a single domain class, SimpleObject, + along with an equally simple (factory/repository) domain service, SimpleObjects.

To access the app:

@@ -98,8 +98,8 @@

provides accesses to a generic UI for end-users, - Isis' Wicket Viewer. - As its name suggests, this viewer is built on top of Apache Wicket™. + Isis' Wicket Viewer. + As its name suggests, this viewer is built on top of Apache Wicket™.

  • @@ -118,7 +118,7 @@

    The default user/password is sven/pass (as configured in the - shiro.ini file). + shiro.ini file).

    diff --git a/naked-objects/webapp/src/main/webapp/css/application.css b/naked-objects/webapp/src/main/webapp/css/application.css index 9f1612af1a24..b41165a5c680 100644 --- a/naked-objects/webapp/src/main/webapp/css/application.css +++ b/naked-objects/webapp/src/main/webapp/css/application.css @@ -1,19 +1,22 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at +/** + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * - * http://www.apache.org/licenses/LICENSE-2.0 + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ - diff --git a/naked-objects/webapp/src/main/webapp/scripts/application.js b/naked-objects/webapp/src/main/webapp/scripts/application.js index 3611ec357912..5ea0e2de6fa6 100644 --- a/naked-objects/webapp/src/main/webapp/scripts/application.js +++ b/naked-objects/webapp/src/main/webapp/scripts/application.js @@ -1,6 +1,6 @@ /* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/null-object/README.md b/null-object/README.md index 0ed28a0af33c..d0a6f530dc5a 100644 --- a/null-object/README.md +++ b/null-object/README.md @@ -5,8 +5,7 @@ folder: null-object permalink: /patterns/null-object/ categories: Behavioral tags: - - Java - - Difficulty-Beginner + - Extensibility --- ## Intent @@ -19,12 +18,14 @@ implements the expected interface, but whose method body is empty. The advantage of this approach over a working default implementation is that a Null Object is very predictable and has no side effects: it does nothing. +## Class diagram ![alt text](./etc/null-object.png "Null Object") ## Applicability Use the Null Object pattern when -* you want to avoid explicit null checks and keep the algorithm elegant and easy to read. +* You want to avoid explicit null checks and keep the algorithm elegant and easy to read. ## Credits + * [Pattern Languages of Program Design](http://www.amazon.com/Pattern-Languages-Program-Design-Coplien/dp/0201607344/ref=sr_1_1) diff --git a/null-object/etc/null-object.urm.puml b/null-object/etc/null-object.urm.puml new file mode 100644 index 000000000000..a937a2b178e2 --- /dev/null +++ b/null-object/etc/null-object.urm.puml @@ -0,0 +1,41 @@ +@startuml +package com.iluwatar.nullobject { + class App { + + App() + + main(args : String[]) {static} + } + interface Node { + + getLeft() : Node {abstract} + + getName() : String {abstract} + + getRight() : Node {abstract} + + getTreeSize() : int {abstract} + + walk() {abstract} + } + class NodeImpl { + - LOGGER : Logger {static} + - left : Node + - name : String + - right : Node + + NodeImpl(name : String, left : Node, right : Node) + + getLeft() : Node + + getName() : String + + getRight() : Node + + getTreeSize() : int + + walk() + } + class NullNode { + - instance : NullNode {static} + - NullNode() + + getInstance() : NullNode {static} + + getLeft() : Node + + getName() : String + + getRight() : Node + + getTreeSize() : int + + walk() + } +} +NullNode --> "-instance" NullNode +NodeImpl --> "-left" Node +NodeImpl ..|> Node +NullNode ..|> Node +@enduml \ No newline at end of file diff --git a/null-object/pom.xml b/null-object/pom.xml index 099fcfce32eb..7b88fca790a5 100644 --- a/null-object/pom.xml +++ b/null-object/pom.xml @@ -2,7 +2,7 @@ "-currentWeather" WeatherType +GWeather --> "-currentWeather" WeatherType +Weather --> "-observers" WeatherObserver +Hobbits ..|> WeatherObserver +Orcs ..|> WeatherObserver +GHobbits ..|> Race +GOrcs ..|> Race +GWeather --|> Observable +Race --|> Observer +@enduml \ No newline at end of file diff --git a/observer/pom.xml b/observer/pom.xml index 80720f6a2d0a..1e48268d885d 100644 --- a/observer/pom.xml +++ b/observer/pom.xml @@ -2,7 +2,7 @@ "-fieldJsonMapper" FieldJsonMapper +@enduml \ No newline at end of file diff --git a/partial-response/pom.xml b/partial-response/pom.xml index 04b760ca833e..83d81bf828f1 100644 --- a/partial-response/pom.xml +++ b/partial-response/pom.xml @@ -2,7 +2,7 @@ "-currentHandler" Handler +ConvertToCharArrayHandler ..|> Handler +RemoveAlphabetsHandler ..|> Handler +RemoveDigitsHandler ..|> Handler +@enduml \ No newline at end of file diff --git a/pipeline/pom.xml b/pipeline/pom.xml new file mode 100644 index 000000000000..8c511cd8aff5 --- /dev/null +++ b/pipeline/pom.xml @@ -0,0 +1,66 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + pipeline + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.pipeline.App + + + + + + + + + diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/App.java b/pipeline/src/main/java/com/iluwatar/pipeline/App.java new file mode 100644 index 000000000000..1b1e443e6456 --- /dev/null +++ b/pipeline/src/main/java/com/iluwatar/pipeline/App.java @@ -0,0 +1,66 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.pipeline; + +/** + * The Pipeline pattern uses ordered stages to process a sequence of input values. Each implemented + * task is represented by a stage of the pipeline. You can think of pipelines as similar to assembly + * lines in a factory, where each item in the assembly line is constructed in stages. The partially + * assembled item is passed from one assembly stage to another. The outputs of the assembly line + * occur in the same order as that of the inputs. + * + *

    Classes used in this example are suffixed with "Handlers", and synonymously refers to the + * "stage". + */ +public class App { + /** + * Specify the initial input type for the first stage handler and the expected output type of the + * last stage handler as type parameters for Pipeline. Use the fluent builder by calling + * addHandler to add more stage handlers on the pipeline. + */ + public static void main(String[] args) { + /* + Suppose we wanted to pass through a String to a series of filtering stages and convert it + as a char array on the last stage. + + - Stage handler 1 (pipe): Removing the alphabets, accepts a String input and returns the + processed String output. This will be used by the next handler as its input. + + - Stage handler 2 (pipe): Removing the digits, accepts a String input and returns the + processed String output. This shall also be used by the last handler we have. + + - Stage handler 3 (pipe): Converting the String input to a char array handler. We would + be returning a different type in here since that is what's specified by the requirement. + This means that at any stages along the pipeline, the handler can return any type of data + as long as it fulfills the requirements for the next handler's input. + + Suppose we wanted to add another handler after ConvertToCharArrayHandler. That handler + then is expected to receive an input of char[] array since that is the type being returned + by the previous handler, ConvertToCharArrayHandler. + */ + new Pipeline<>(new RemoveAlphabetsHandler()) + .addHandler(new RemoveDigitsHandler()) + .addHandler(new ConvertToCharArrayHandler()); + } +} diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/ConvertToCharArrayHandler.java b/pipeline/src/main/java/com/iluwatar/pipeline/ConvertToCharArrayHandler.java new file mode 100644 index 000000000000..365fc1cdc2e4 --- /dev/null +++ b/pipeline/src/main/java/com/iluwatar/pipeline/ConvertToCharArrayHandler.java @@ -0,0 +1,47 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.pipeline; + +import java.util.Arrays; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Stage handler that converts an input String to its char[] array counterpart. + */ +class ConvertToCharArrayHandler implements Handler { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConvertToCharArrayHandler.class); + + @Override + public char[] process(String input) { + char[] characters = input.toCharArray(); + LOGGER + .info(String.format("Current handler: %s, input is %s of type %s, output is %s, of type %s", + ConvertToCharArrayHandler.class, input, String.class, Arrays + .toString(characters), Character[].class)); + + return characters; + } +} diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/Handler.java b/pipeline/src/main/java/com/iluwatar/pipeline/Handler.java new file mode 100644 index 000000000000..5591632e08ba --- /dev/null +++ b/pipeline/src/main/java/com/iluwatar/pipeline/Handler.java @@ -0,0 +1,35 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.pipeline; + +/** + * Forms a contract to all stage handlers to accept a certain type of input and return a processed + * output. + * + * @param the input type of the handler + * @param the processed output type of the handler + */ +interface Handler { + O process(I input); +} \ No newline at end of file diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/Pipeline.java b/pipeline/src/main/java/com/iluwatar/pipeline/Pipeline.java new file mode 100644 index 000000000000..81b63e6cfdcf --- /dev/null +++ b/pipeline/src/main/java/com/iluwatar/pipeline/Pipeline.java @@ -0,0 +1,48 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.pipeline; + +/** + * Main Pipeline class that initially sets the current handler. Processed output of the initial + * handler is then passed as the input to the next stage handlers. + * + * @param the type of the input for the first stage handler + * @param the final stage handler's output type + */ +class Pipeline { + + private final Handler currentHandler; + + Pipeline(Handler currentHandler) { + this.currentHandler = currentHandler; + } + + Pipeline addHandler(Handler newHandler) { + return new Pipeline<>(input -> newHandler.process(currentHandler.process(input))); + } + + O execute(I input) { + return currentHandler.process(input); + } +} \ No newline at end of file diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/RemoveAlphabetsHandler.java b/pipeline/src/main/java/com/iluwatar/pipeline/RemoveAlphabetsHandler.java new file mode 100644 index 000000000000..b2ed938cf100 --- /dev/null +++ b/pipeline/src/main/java/com/iluwatar/pipeline/RemoveAlphabetsHandler.java @@ -0,0 +1,61 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.pipeline; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Stage handler that returns a new instance of String without the alphabet characters of the input + * string. + */ +class RemoveAlphabetsHandler implements Handler { + + private static final Logger LOGGER = LoggerFactory.getLogger(RemoveAlphabetsHandler.class); + + @Override + public String process(String input) { + StringBuilder inputWithoutAlphabets = new StringBuilder(); + + for (int index = 0; index < input.length(); index++) { + char currentCharacter = input.charAt(index); + if (Character.isAlphabetic(currentCharacter)) { + continue; + } + + inputWithoutAlphabets.append(currentCharacter); + } + + String inputWithoutAlphabetsStr = inputWithoutAlphabets.toString(); + LOGGER.info( + String.format( + "Current handler: %s, input is %s of type %s, output is %s, of type %s", + RemoveAlphabetsHandler.class, input, + String.class, inputWithoutAlphabetsStr, String.class + ) + ); + + return inputWithoutAlphabetsStr; + } +} \ No newline at end of file diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/RemoveDigitsHandler.java b/pipeline/src/main/java/com/iluwatar/pipeline/RemoveDigitsHandler.java new file mode 100644 index 000000000000..450bef7177ea --- /dev/null +++ b/pipeline/src/main/java/com/iluwatar/pipeline/RemoveDigitsHandler.java @@ -0,0 +1,57 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.pipeline; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Stage handler that returns a new instance of String without the digit characters of the input + * string. + */ +class RemoveDigitsHandler implements Handler { + + private static final Logger LOGGER = LoggerFactory.getLogger(RemoveDigitsHandler.class); + + @Override + public String process(String input) { + StringBuilder inputWithoutDigits = new StringBuilder(); + + for (int index = 0; index < input.length(); index++) { + char currentCharacter = input.charAt(index); + if (Character.isDigit(currentCharacter)) { + continue; + } + + inputWithoutDigits.append(currentCharacter); + } + + String inputWithoutDigitsStr = inputWithoutDigits.toString(); + LOGGER + .info(String.format("Current handler: %s, input is %s of type %s, output is %s, of type %s", + RemoveDigitsHandler.class, input, String.class, inputWithoutDigitsStr, String.class)); + + return inputWithoutDigitsStr; + } +} \ No newline at end of file diff --git a/layers/src/test/java/com/iluwatar/layers/AppTest.java b/pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java similarity index 92% rename from layers/src/test/java/com/iluwatar/layers/AppTest.java rename to pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java index aa14e039d050..d5a892d91809 100644 --- a/layers/src/test/java/com/iluwatar/layers/AppTest.java +++ b/pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,14 +20,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.layers; + +package com.iluwatar.pipeline; import org.junit.jupiter.api.Test; /** - * - * Application test - * + * Application Test */ public class AppTest { diff --git a/pipeline/src/test/java/com/iluwatar/pipeline/PipelineTest.java b/pipeline/src/test/java/com/iluwatar/pipeline/PipelineTest.java new file mode 100644 index 000000000000..1de692222dbe --- /dev/null +++ b/pipeline/src/test/java/com/iluwatar/pipeline/PipelineTest.java @@ -0,0 +1,46 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.pipeline; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +/** + * Test for {@link Pipeline} + */ +public class PipelineTest { + + @Test + public void testAddHandlersToPipeline() { + Pipeline filters = new Pipeline<>(new RemoveAlphabetsHandler()) + .addHandler(new RemoveDigitsHandler()) + .addHandler(new ConvertToCharArrayHandler()); + + assertArrayEquals( + new char[] {'#', '!', '(', '&', '%', '#', '!'}, + filters.execute("#H!E(L&L0O%THE3R#34E!") + ); + } +} diff --git a/poison-pill/README.md b/poison-pill/README.md index 0815b376edf8..7fd152891008 100644 --- a/poison-pill/README.md +++ b/poison-pill/README.md @@ -3,10 +3,9 @@ layout: pattern title: Poison Pill folder: poison-pill permalink: /patterns/poison-pill/ -categories: Other +categories: Behavioral tags: - - Java - - Difficulty-Intermediate + - Cloud distributed - Reactive --- @@ -14,12 +13,13 @@ tags: Poison Pill is known predefined data item that allows to provide graceful shutdown for separate distributed consumption process. +## Class diagram ![alt text](./etc/poison-pill.png "Poison Pill") ## Applicability Use the Poison Pill idiom when -* need to send signal from one thread/process to another to terminate +* Need to send signal from one thread/process to another to terminate ## Real world examples diff --git a/poison-pill/etc/poison-pill.urm.puml b/poison-pill/etc/poison-pill.urm.puml new file mode 100644 index 000000000000..923449e10b4a --- /dev/null +++ b/poison-pill/etc/poison-pill.urm.puml @@ -0,0 +1,71 @@ +@startuml +package com.iluwatar.poison.pill { + class App { + + App() + + main(args : String[]) {static} + } + class Consumer { + - LOGGER : Logger {static} + - name : String + - queue : MqSubscribePoint + + Consumer(name : String, queue : MqSubscribePoint) + + consume() + } + interface Message { + + POISON_PILL : Message {static} + + addHeader(Headers, String) {abstract} + + getBody() : String {abstract} + + getHeader(Headers) : String {abstract} + + getHeaders() : Map {abstract} + + setBody(String) {abstract} + } + enum Headers { + + DATE {static} + + SENDER {static} + + valueOf(name : String) : Headers {static} + + values() : Headers[] {static} + } + interface MessageQueue { + } + interface MqPublishPoint { + + put(Message) {abstract} + } + interface MqSubscribePoint { + + take() : Message {abstract} + } + class Producer { + - LOGGER : Logger {static} + - isStopped : boolean + - name : String + - queue : MqPublishPoint + + Producer(name : String, queue : MqPublishPoint) + + send(body : String) + + stop() + } + class SimpleMessage { + - body : String + - headers : Map + + SimpleMessage() + + addHeader(header : Headers, value : String) + + getBody() : String + + getHeader(header : Headers) : String + + getHeaders() : Map + + setBody(body : String) + } + class SimpleMessageQueue { + - queue : BlockingQueue + + SimpleMessageQueue(bound : int) + + put(msg : Message) + + take() : Message + } +} +SimpleMessageQueue --> "-queue" Message +Headers ..+ Message +Consumer --> "-queue" MqSubscribePoint +Producer --> "-queue" MqPublishPoint +Message --> "-POISON_PILL" Message +MessageQueue --|> MqPublishPoint +MessageQueue --|> MqSubscribePoint +SimpleMessage ..|> Message +SimpleMessageQueue ..|> MessageQueue +@enduml \ No newline at end of file diff --git a/poison-pill/pom.xml b/poison-pill/pom.xml index fa014d6544c7..4989581d7b97 100644 --- a/poison-pill/pom.xml +++ b/poison-pill/pom.xml @@ -2,7 +2,7 @@ + + 4.0.0 com.iluwatar java-design-patterns - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT pom - 2014 + 2014-2019 UTF-8 - 5.0.1.Final - 4.2.4.RELEASE - 1.3.3.RELEASE - 1.9.2.RELEASE + 5.2.18.Final + 5.0.13.RELEASE + 2.0.9.RELEASE + 2.0.14.RELEASE 1.4.190 4.12 - 5.0.2 + 5.5.2 ${junit.version}.2 - 1.0.2 1.0.2 - 3.0 - 0.7.2.201409121644 + 3.8.1 + 0.8.4 1.4 - 2.16.1 + 2.24.0 19.0 1.10.19 - 4.5.2 2.22 4.0 3.3.0 - 1.7.21 - 1.1.7 + 1.7.28 + 1.2.3 1.1.0 1.11.289 - 1.0.0 2.0.1 2.8.5 + 2.3.1 + 2.3.2 + 1.3.2 + 1.19.0 + 1.4.8 abstract-factory @@ -92,6 +93,7 @@ property intercepting-filter producer-consumer + pipeline poison-pill reader-writer-lock lazy-loading @@ -162,10 +164,25 @@ trampoline serverless ambassador - acyclic-visitor - collection-pipeline - master-worker-pattern - spatial-partition + acyclic-visitor + collection-pipeline + master-worker-pattern + spatial-partition + priority-queue + commander + typeobjectpattern + bytecode + leader-election + data-locality + subclass-sandbox + circuit-breaker + role-object + saga + double-buffer + sharding + game-loop + combinator + update-method @@ -182,11 +199,6 @@ hibernate-core ${hibernate.version} - - org.hibernate - hibernate-entitymanager - ${hibernate.version} - org.springframework.boot spring-boot-dependencies @@ -204,16 +216,6 @@ spring-webmvc ${spring.version} - - org.springframework.boot - spring-boot-starter-web - ${spring-boot.version} - - - org.apache.httpcomponents - httpclient - ${apache-httpcomponents.version} - com.h2database h2 @@ -234,18 +236,18 @@ camel-stream ${camel.version} - - org.junit.jupiter - junit-jupiter-api - ${junit-jupiter.version} - test - junit junit ${junit.version} test + + org.junit.jupiter + junit-jupiter-api + ${junit-jupiter.version} + test + org.junit.jupiter junit-jupiter-engine @@ -302,6 +304,32 @@ mongo-java-driver ${mongo-java-driver.version} + + javax.xml.bind + jaxb-api + ${jaxb-api.version} + + + javax.annotation + javax.annotation-api + ${annotation-api.version} + + + com.sun.xml.bind + jaxb-impl + ${jaxb-impl.version} + + + org.javassist + javassist + 3.25.0-GA + + + com.github.stefanbirkner + system-rules + ${system-rules.version} + test + @@ -326,72 +354,58 @@ - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 + org.apache.maven.plugins + maven-compiler-plugin + ${compiler.version} - - - - - org.jacoco - - jacoco-maven-plugin - - - [0.6.2,) - - - prepare-agent - - - - - - - - + 11 + 11 + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M3 + + -Xmx1024M ${argLine} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + jar-with-dependencies + + + ${project.artifactId} + false + + + + - - - org.apache.maven.plugins - maven-compiler-plugin - ${compiler.version} - - 1.8 - 1.8 - - - - org.jacoco - jacoco-maven-plugin - ${jacoco.version} - - - prepare-agent - - prepare-agent - - - - - - org.apache.maven.plugins maven-checkstyle-plugin - 2.17 + 3.1.0 validate @@ -400,62 +414,60 @@ validate - checkstyle.xml + google_checks.xml checkstyle-suppressions.xml UTF-8 - true - true - true + true + warning + false + + - org.apache.maven.plugins - maven-surefire-plugin - 2.19.1 - - - org.junit.platform - junit-platform-surefire-provider - ${junit-platform.version} - - - - -Xmx1024M ${argLine} - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.6 - - true - 5 - true - + org.commonjava.maven.plugins + directory-maven-plugin + 0.3.1 + directories - check + directory-of + initialize - exclude-pmd.properties + projectRoot + + com.iluwatar + java-design-patterns + - com.mycila license-maven-plugin - 2.11 + 3.0

    com/mycila/maven/plugin/license/templates/MIT.txt
    Ilkka Seppälä true + + ${projectRoot}${file.separator}license-plugin-header-style.xml + + + SLASHSTAR_CUSTOM_STYLE + + + .github/FUNDING.yml + @@ -467,15 +479,59 @@ + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + prepare-agent + + prepare-agent + + + + report + + report + + + + + + + com.iluwatar.urm + urm-maven-plugin + 1.4.8 + + + ${project.basedir}/etc + + com.iluwatar + + true + false + plantuml + + + + process-classes + + map + + + + + - org.apache.maven.plugins - maven-pmd-plugin - 3.6 + maven-jxr-plugin + 3.0.0 diff --git a/priority-queue/README.md b/priority-queue/README.md new file mode 100644 index 000000000000..c8d1f7773cb9 --- /dev/null +++ b/priority-queue/README.md @@ -0,0 +1,29 @@ +--- +layout: pattern +title: Priority Queue Pattern +folder: priority-queue +permalink: /patterns/priority-queue/ +categories: Behavioral +tags: + - Decoupling +--- + +## Intent +Prioritize requests sent to services so that requests with a higher priority are received and processed more quickly than those of a lower priority. This pattern is useful in applications that offer different service level guarantees to individual clients. + +## Explanation +Applications may delegate specific tasks to other services; for example, to perform background processing or to integrate with other applications or services. In the cloud, a message queue is typically used to delegate tasks to background processing. In many cases the order in which requests are received by a service is not important. However, in some cases it may be necessary to prioritize specific requests. These requests should be processed earlier than others of a lower priority that may have been sent previously by the application. + +## Class diagram +![alt text](./etc/priority-queue.urm.png "Priority Queue pattern class diagram") + +## Applicability +Use the Property pattern when + +* The system must handle multiple tasks that might have different priorities. +* Different users or tenants should be served with different priority.. + +## Real world examples + +* [ Priority Queue Pattern](https://docs.microsoft.com/en-us/previous-versions/msp-n-p/dn589794(v=pandp.10)) +Microsoft Azure does not provide a queuing mechanism that natively support automatic prioritization of messages through sorting. However, it does provide Azure Service Bus topics and subscriptions, which support a queuing mechanism that provides message filtering, together with a wide range of flexible capabilities that make it ideal for use in almost all priority queue implementations. diff --git a/priority-queue/etc/priority-queue.urm.png b/priority-queue/etc/priority-queue.urm.png new file mode 100644 index 000000000000..e0b4295a5986 Binary files /dev/null and b/priority-queue/etc/priority-queue.urm.png differ diff --git a/priority-queue/etc/priority-queue.urm.puml b/priority-queue/etc/priority-queue.urm.puml new file mode 100644 index 000000000000..cee118f57671 --- /dev/null +++ b/priority-queue/etc/priority-queue.urm.puml @@ -0,0 +1,54 @@ +@startuml +package com.iluwatar.priority.queue { + class Application { + + Application() + + main(args : String[]) {static} + } + class Message { + - message : String + - priority : int + + Message(message : String, priority : int) + + compareTo(o : Message) : int + + toString() : String + } + class PriorityMessageQueue { + - LOGGER : Logger {static} + - capacity : int + - queue : T[] + - size : int + + PriorityMessageQueue(queue : T[]) + + add(t : T extends Comparable) + - ensureCapacity() + - hasLeftChild(index : int) : boolean + - hasParent(index : int) : boolean + - hasRightChild(index : int) : boolean + + isEmpty() : boolean + - left(parentIndex : int) : T extends Comparable + - leftChildIndex(parentPos : int) : int + - maxHeapifyDown() + - maxHeapifyUp() + - parent(childIndex : int) : T extends Comparable + - parentIndex(pos : int) : int + + print() + + remove() : T extends Comparable + - right(parentIndex : int) : T extends Comparable + - rightChildIndex(parentPos : int) : int + - swap(fpos : int, tpos : int) + } + class QueueManager { + - messagePriorityMessageQueue : PriorityMessageQueue + + QueueManager(initialCapacity : int) + + publishMessage(message : Message) + + receiveMessage() : Message + } + class Worker { + - LOGGER : Logger {static} + - queueManager : QueueManager + + Worker(queueManager : QueueManager) + - processMessage(message : Message) + + run() + } +} +QueueManager --> "-messagePriorityMessageQueue" PriorityMessageQueue +Worker --> "-queueManager" QueueManager +@enduml \ No newline at end of file diff --git a/priority-queue/pom.xml b/priority-queue/pom.xml new file mode 100644 index 000000000000..7f435f489883 --- /dev/null +++ b/priority-queue/pom.xml @@ -0,0 +1,63 @@ + + + + 4.0.0 + priority-queue + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.priority.queue.Application + + + + + + + + + \ No newline at end of file diff --git a/priority-queue/src/main/java/com/iluwatar/priority/queue/Application.java b/priority-queue/src/main/java/com/iluwatar/priority/queue/Application.java new file mode 100644 index 000000000000..e803489b28d2 --- /dev/null +++ b/priority-queue/src/main/java/com/iluwatar/priority/queue/Application.java @@ -0,0 +1,60 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.priority.queue; + +/** + * Prioritize requests sent to services so that requests with a higher priority are received and + * processed more quickly than those of a lower priority. This pattern is useful in applications + * that offer different service level guarantees to individual clients. Example :Send multiple + * message with different priority to worker queue. Worker execute higher priority message first + * + * @see "https://docs.microsoft.com/en-us/previous-versions/msp-n-p/dn589794(v=pandp.10)" + */ +public class Application { + /** + * main entry. + */ + public static void main(String[] args) throws Exception { + + QueueManager queueManager = new QueueManager(100); + + // push some message to queue + // Low Priority message + for (int i = 0; i < 100; i++) { + queueManager.publishMessage(new Message("Low Message Priority", 0)); + } + + // High Priority message + for (int i = 0; i < 100; i++) { + queueManager.publishMessage(new Message("High Message Priority", 1)); + } + + + // run worker + Worker worker = new Worker(queueManager); + worker.run(); + + + } +} diff --git a/priority-queue/src/main/java/com/iluwatar/priority/queue/Message.java b/priority-queue/src/main/java/com/iluwatar/priority/queue/Message.java new file mode 100644 index 000000000000..a9a3dcfa630c --- /dev/null +++ b/priority-queue/src/main/java/com/iluwatar/priority/queue/Message.java @@ -0,0 +1,51 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.priority.queue; + +/** + * Message bean. + */ +public class Message implements Comparable { + private final String message; + private final int priority; // define message priority in queue + + + public Message(String message, int priority) { + this.message = message; + this.priority = priority; + } + + @Override + public int compareTo(Message o) { + return priority - o.priority; + } + + @Override + public String toString() { + return "Message{" + + "message='" + message + '\'' + + ", priority=" + priority + + '}'; + } +} diff --git a/priority-queue/src/main/java/com/iluwatar/priority/queue/PriorityMessageQueue.java b/priority-queue/src/main/java/com/iluwatar/priority/queue/PriorityMessageQueue.java new file mode 100644 index 000000000000..66f4d4f591f6 --- /dev/null +++ b/priority-queue/src/main/java/com/iluwatar/priority/queue/PriorityMessageQueue.java @@ -0,0 +1,179 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.priority.queue; + +import static java.util.Arrays.copyOf; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Keep high Priority message on top using maxHeap. + * + * @param : DataType to push in Queue + */ +public class PriorityMessageQueue { + + private static final Logger LOGGER = LoggerFactory.getLogger(PriorityMessageQueue.class); + + private int size = 0; + + private int capacity; + + + private T[] queue; + + public PriorityMessageQueue(T[] queue) { + this.queue = queue; + this.capacity = queue.length; + } + + /** + * Remove top message from queue. + */ + public T remove() { + if (isEmpty()) { + return null; + } + + final T root = queue[0]; + queue[0] = queue[size - 1]; + size--; + maxHeapifyDown(); + return root; + } + + /** + * Add message to queue. + */ + public void add(T t) { + ensureCapacity(); + queue[size] = t; + size++; + maxHeapifyUp(); + } + + /** + * Check queue size. + */ + public boolean isEmpty() { + return size == 0; + } + + + private void maxHeapifyDown() { + int index = 0; + while (hasLeftChild(index)) { + + int smallerIndex = leftChildIndex(index); + + if (hasRightChild(index) && right(index).compareTo(left(index)) > 0) { + smallerIndex = rightChildIndex(index); + } + + if (queue[index].compareTo(queue[smallerIndex]) > 0) { + break; + } else { + swap(index, smallerIndex); + } + + index = smallerIndex; + + + } + + } + + private void maxHeapifyUp() { + int index = size - 1; + while (hasParent(index) && parent(index).compareTo(queue[index]) < 0) { + swap(parentIndex(index), index); + index = parentIndex(index); + } + } + + + // index + private int parentIndex(int pos) { + return (pos - 1) / 2; + } + + private int leftChildIndex(int parentPos) { + return 2 * parentPos + 1; + } + + private int rightChildIndex(int parentPos) { + return 2 * parentPos + 2; + } + + // value + private T parent(int childIndex) { + return queue[parentIndex(childIndex)]; + } + + private T left(int parentIndex) { + return queue[leftChildIndex(parentIndex)]; + } + + private T right(int parentIndex) { + return queue[rightChildIndex(parentIndex)]; + } + + // check + private boolean hasLeftChild(int index) { + return leftChildIndex(index) < size; + } + + private boolean hasRightChild(int index) { + return rightChildIndex(index) < size; + } + + private boolean hasParent(int index) { + return parentIndex(index) >= 0; + } + + private void swap(int fpos, int tpos) { + T tmp = queue[fpos]; + queue[fpos] = queue[tpos]; + queue[tpos] = tmp; + } + + private void ensureCapacity() { + if (size == capacity) { + capacity = capacity * 2; + queue = copyOf(queue, capacity); + } + } + + /** + * For debug .. print current state of queue + */ + public void print() { + for (int i = 0; i <= size / 2; i++) { + LOGGER.info(" PARENT : " + queue[i] + " LEFT CHILD : " + + left(i) + " RIGHT CHILD :" + right(i)); + } + } + +} diff --git a/priority-queue/src/main/java/com/iluwatar/priority/queue/QueueManager.java b/priority-queue/src/main/java/com/iluwatar/priority/queue/QueueManager.java new file mode 100644 index 000000000000..7eca0aca6d3d --- /dev/null +++ b/priority-queue/src/main/java/com/iluwatar/priority/queue/QueueManager.java @@ -0,0 +1,58 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.priority.queue; + +/** + * Manage priority queue. + */ +public class QueueManager { + /* + Priority message + */ + private final PriorityMessageQueue messagePriorityMessageQueue; + + public QueueManager(int initialCapacity) { + messagePriorityMessageQueue = new PriorityMessageQueue(new Message[initialCapacity]); + } + + /** + * Publish message to queue. + */ + public void publishMessage(Message message) { + messagePriorityMessageQueue.add(message); + } + + + /** + * recive message from queue. + */ + public Message receiveMessage() { + if (messagePriorityMessageQueue.isEmpty()) { + return null; + } + return messagePriorityMessageQueue.remove(); + } + + +} diff --git a/priority-queue/src/main/java/com/iluwatar/priority/queue/Worker.java b/priority-queue/src/main/java/com/iluwatar/priority/queue/Worker.java new file mode 100644 index 000000000000..18ed16199584 --- /dev/null +++ b/priority-queue/src/main/java/com/iluwatar/priority/queue/Worker.java @@ -0,0 +1,65 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.priority.queue; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Message Worker. + */ +public class Worker { + + private static final Logger LOGGER = LoggerFactory.getLogger(Worker.class); + + private final QueueManager queueManager; + + public Worker(QueueManager queueManager) { + this.queueManager = queueManager; + } + + /** + * Keep checking queue for message. + */ + @SuppressWarnings("squid:S2189") + public void run() throws Exception { + while (true) { + Message message = queueManager.receiveMessage(); + if (message == null) { + LOGGER.info("No Message ... waiting"); + Thread.sleep(200); + } else { + processMessage(message); + } + } + } + + /** + * Process message. + */ + private void processMessage(Message message) { + LOGGER.info(message.toString()); + } + +} diff --git a/priority-queue/src/test/java/com/iluwatar/priority/queue/PriorityMessageQueueTest.java b/priority-queue/src/test/java/com/iluwatar/priority/queue/PriorityMessageQueueTest.java new file mode 100644 index 000000000000..4fd1f06a6454 --- /dev/null +++ b/priority-queue/src/test/java/com/iluwatar/priority/queue/PriorityMessageQueueTest.java @@ -0,0 +1,74 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.priority.queue; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case for order of messages + */ +public class PriorityMessageQueueTest { + + + @Test + public void remove() { + PriorityMessageQueue stringPriorityMessageQueue = new PriorityMessageQueue<>(new String[2]); + String pushMessage = "test"; + stringPriorityMessageQueue.add(pushMessage); + assertEquals(stringPriorityMessageQueue.remove(), pushMessage); + } + + @Test + public void add() { + PriorityMessageQueue stringPriorityMessageQueue = new PriorityMessageQueue<>(new Integer[2]); + stringPriorityMessageQueue.add(1); + stringPriorityMessageQueue.add(5); + stringPriorityMessageQueue.add(10); + stringPriorityMessageQueue.add(3); + assertTrue(stringPriorityMessageQueue.remove() == 10); + } + + @Test + public void isEmpty() { + PriorityMessageQueue stringPriorityMessageQueue = new PriorityMessageQueue<>(new Integer[2]); + assertTrue(stringPriorityMessageQueue.isEmpty()); + stringPriorityMessageQueue.add(1); + stringPriorityMessageQueue.remove(); + assertTrue(stringPriorityMessageQueue.isEmpty()); + } + + @Test + public void testEnsureSize() { + PriorityMessageQueue stringPriorityMessageQueue = new PriorityMessageQueue<>(new Integer[2]); + assertTrue(stringPriorityMessageQueue.isEmpty()); + stringPriorityMessageQueue.add(1); + stringPriorityMessageQueue.add(2); + stringPriorityMessageQueue.add(2); + stringPriorityMessageQueue.add(3); + assertTrue(stringPriorityMessageQueue.remove() == 3); + } +} \ No newline at end of file diff --git a/priority-queue/src/test/java/com/iluwatar/priority/queue/QueueManagerTest.java b/priority-queue/src/test/java/com/iluwatar/priority/queue/QueueManagerTest.java new file mode 100644 index 000000000000..17aad88b396c --- /dev/null +++ b/priority-queue/src/test/java/com/iluwatar/priority/queue/QueueManagerTest.java @@ -0,0 +1,54 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.priority.queue; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Check queue manager + */ +public class QueueManagerTest { + + @Test + public void publishMessage() { + QueueManager queueManager = new QueueManager(2); + Message testMessage = new Message("Test Message", 1); + queueManager.publishMessage(testMessage); + Message recivedMessage = queueManager.receiveMessage(); + assertEquals(testMessage, recivedMessage); + } + + @Test + public void receiveMessage() { + QueueManager queueManager = new QueueManager(2); + Message testMessage1 = new Message("Test Message 1", 1); + queueManager.publishMessage(testMessage1); + Message testMessage2 = new Message("Test Message 2", 2); + queueManager.publishMessage(testMessage2); + Message recivedMessage = queueManager.receiveMessage(); + assertEquals(testMessage2, recivedMessage); + } +} \ No newline at end of file diff --git a/private-class-data/README.md b/private-class-data/README.md index 981208fa3f0e..20e34328522e 100644 --- a/private-class-data/README.md +++ b/private-class-data/README.md @@ -3,11 +3,9 @@ layout: pattern title: Private Class Data folder: private-class-data permalink: /patterns/private-class-data/ -categories: Other +categories: Idiom tags: - - Java - - Difficulty-Beginner - - Idiom + - Data access --- ## Intent @@ -15,9 +13,10 @@ Private Class Data design pattern seeks to reduce exposure of attributes by limiting their visibility. It reduces the number of class attributes by encapsulating them in single Data object. +## Class diagram ![alt text](./etc/private-class-data.png "Private Class Data") ## Applicability Use the Private Class Data pattern when -* you want to prevent write access to class data members +* You want to prevent write access to class data members diff --git a/private-class-data/etc/private-class-data.urm.puml b/private-class-data/etc/private-class-data.urm.puml new file mode 100644 index 000000000000..990b53342521 --- /dev/null +++ b/private-class-data/etc/private-class-data.urm.puml @@ -0,0 +1,36 @@ +@startuml +package com.iluwatar.privateclassdata { + class App { + + App() + + main(args : String[]) {static} + } + class ImmutableStew { + - LOGGER : Logger {static} + - data : StewData + + ImmutableStew(numPotatoes : int, numCarrots : int, numMeat : int, numPeppers : int) + + mix() + } + class Stew { + - LOGGER : Logger {static} + - numCarrots : int + - numMeat : int + - numPeppers : int + - numPotatoes : int + + Stew(numPotatoes : int, numCarrots : int, numMeat : int, numPeppers : int) + + mix() + + taste() + } + class StewData { + - numCarrots : int + - numMeat : int + - numPeppers : int + - numPotatoes : int + + StewData(numPotatoes : int, numCarrots : int, numMeat : int, numPeppers : int) + + getNumCarrots() : int + + getNumMeat() : int + + getNumPeppers() : int + + getNumPotatoes() : int + } +} +ImmutableStew --> "-data" StewData +@enduml \ No newline at end of file diff --git a/private-class-data/pom.xml b/private-class-data/pom.xml index 219709d3119c..cb81ca3fc9e1 100644 --- a/private-class-data/pom.xml +++ b/private-class-data/pom.xml @@ -2,7 +2,7 @@ "-queue" ItemQueue +Producer --> "-queue" ItemQueue +ItemQueue --> "-queue" Item +@enduml \ No newline at end of file diff --git a/producer-consumer/pom.xml b/producer-consumer/pom.xml index 3d75f41a99ba..ab1872c51562 100644 --- a/producer-consumer/pom.xml +++ b/producer-consumer/pom.xml @@ -2,7 +2,7 @@ "-src" Promise +ConsumeAction --+ Promise +ConsumeAction --> "-src" Promise +Promise --|> PromiseSupport +@enduml \ No newline at end of file diff --git a/promise/pom.xml b/promise/pom.xml index 7b069c74632e..4a9d76df1c84 100644 --- a/promise/pom.xml +++ b/promise/pom.xml @@ -2,7 +2,7 @@ "-prototype" Prototype +Type ..+ Character +Character --> "-type" Type +Character ..|> Prototype +@enduml \ No newline at end of file diff --git a/property/pom.xml b/property/pom.xml index 2df23c88f95f..d271af036b47 100644 --- a/property/pom.xml +++ b/property/pom.xml @@ -2,7 +2,7 @@ "-beast" Beast +HeroFactoryImpl --> "-warlord" Warlord +HeroFactoryImpl --> "-mage" Mage +Beast ..|> Prototype +ElfBeast --|> Beast +ElfMage --|> Mage +ElfWarlord --|> Warlord +HeroFactoryImpl ..|> HeroFactory +Mage ..|> Prototype +OrcBeast --|> Beast +OrcMage --|> Mage +OrcWarlord --|> Warlord +Warlord ..|> Prototype +@enduml \ No newline at end of file diff --git a/prototype/pom.xml b/prototype/pom.xml index 473ba22740a2..e68b11892ef2 100644 --- a/prototype/pom.xml +++ b/prototype/pom.xml @@ -2,7 +2,7 @@ - - - - Title - - - - - - - - - diff --git a/proxy/etc/proxy-concept.png b/proxy/etc/proxy-concept.png deleted file mode 100644 index dac5d954bc58..000000000000 Binary files a/proxy/etc/proxy-concept.png and /dev/null differ diff --git a/proxy/etc/proxy-concept.xml b/proxy/etc/proxy-concept.xml deleted file mode 100644 index 83373a92959b..000000000000 --- a/proxy/etc/proxy-concept.xml +++ /dev/null @@ -1,25 +0,0 @@ - -7Vhtb5swEP41kbYPnQJuaPexeVv3YVK1aur60YEL8WowM05K+utnYxswkC6p2rSbmkoNfnx3vpfnLsAATZLiC8fZ6huLgA78YVQM0HTg+x4KPPmlkK1GziwQcxIZoRq4Jg9gwKFB1ySC3BEUjFFBMhcMWZpCKBwMc87uXbElo+6pGY6hA1yHmHbRGxKJlUbPR8MavwQSr+zJ3tDsLHB4F3O2Ts15Ax8ty4/eTrC1ZeTzFY7YfQNCswGacMaEvkqKCVCVW5s2rTffsVv5zSEV+yica4UNpmuwHgdUqo4z5Z3YmowEv9fKpXGCeUzSAbqQu8OskP8lWAam8BPBMr132tgTUIgTTEls9ELpG/DapryKzXd5MmkAOJEGx7S7+qpsLHEIFeyqOBZl/KR9yoJ3EAtcrxe/FKtq7UVbVmJZG1txlTLLZRudtzvQJ6WYwlI0crzLdje8PpdfxgMlW9b4ww15wDz6qKU3TLJuT/d8xzF/A1wQ2Z4XmkXTkmdjw6mpdmjMpNSSlt20JJLlaLxkqTDDxfPNeo4TQtVYugS6AWVVlU4kVAkpGak6YZTx8mTbvWicC87uoLEzLD+KahxHRMbb2Juezrz5qIpEuQ/Fzhb1qsaXAxVYAoJvpYhR8M/NrDCz1A6h+3owVeNk5Q4lMxDNMIwr0/VAkBdmJvTPB+vumxoQVTd+B0yP1awR2fQGbFOuHPcf68g8w+lBPSLddnUquPTl9VvlhYmPfJf4fcz3TnuYHzwH8f23TPwrzortO+X/O8oHnkv5s9ERGY/eIuN336VNqCrE/k3wj99SzAL19zw0G70iy7pPHLNCQBrlnQpJ8EI9y8nVgrLwTsYuIZv8QC/nZVmmQzfjMg98+1Phn0Z2eWv3CiIaW3J1awxoDyDqPBu28iq9ZGsegnuLJCTtQTQeq7rpbybYJpMDxYJs3CP7MmzMXTGiWL+jlAi1aqQ9NUrNB8GWHd/7iyEdXsdQWe8qxP0o8PmoFPCaBKiL/tOoVAQodw5v1SeSxn8nzUGkQd37sR85PEYYlkHq8kXN6wjnK4gMLZp0MZTwOpSoZ0mXSE8sPnov/mHF77s12V35lKVQ9izmomd8NOZFKWLWbULs8RPSJcvz/YToEI/CiKD1hqF643AsSsgi4W1DLFMC+eEO1wzTFvfkm1zWb1y1eP1aG83+AA== \ No newline at end of file diff --git a/proxy/etc/proxy.urm.png b/proxy/etc/proxy.urm.png new file mode 100644 index 000000000000..a0c94fc7c717 Binary files /dev/null and b/proxy/etc/proxy.urm.png differ diff --git a/proxy/etc/proxy.urm.puml b/proxy/etc/proxy.urm.puml new file mode 100644 index 000000000000..ffe0fa446074 --- /dev/null +++ b/proxy/etc/proxy.urm.puml @@ -0,0 +1,32 @@ +@startuml +package com.iluwatar.proxy { + class App { + + App() + + main(args : String[]) {static} + } + class IvoryTower { + - LOGGER : Logger {static} + + IvoryTower() + + enter(wizard : Wizard) + } + class Wizard { + - name : String + + Wizard(name : String) + + toString() : String + } + interface WizardTower { + + enter(Wizard) {abstract} + } + class WizardTowerProxy { + - LOGGER : Logger {static} + - NUM_WIZARDS_ALLOWED : int {static} + - numWizards : int + - tower : WizardTower + + WizardTowerProxy(tower : WizardTower) + + enter(wizard : Wizard) + } +} +WizardTowerProxy --> "-tower" WizardTower +IvoryTower ..|> WizardTower +WizardTowerProxy ..|> WizardTower +@enduml \ No newline at end of file diff --git a/proxy/pom.xml b/proxy/pom.xml index d2d1f552eb1c..f54c77dcf137 100644 --- a/proxy/pom.xml +++ b/proxy/pom.xml @@ -2,7 +2,7 @@ "-blkQueue" Message +ServiceExecutor --> "-msgQueue" MessageQueue +TaskGenerator --> "-msgQueue" MessageQueue +TaskGenerator ..|> Task +@enduml \ No newline at end of file diff --git a/queue-load-leveling/pom.xml b/queue-load-leveling/pom.xml index 15d81ec6dd94..ee6e6c623288 100644 --- a/queue-load-leveling/pom.xml +++ b/queue-load-leveling/pom.xml @@ -2,7 +2,7 @@ "-handler" ChannelHandler +UdpLoggingClient ..+ AppClient +TcpLoggingClient ..+ AppClient +AbstractNioChannel --> "-reactor" NioReactor +NioReactor --> "-dispatcher" Dispatcher +App --> "-reactor" NioReactor +App --> "-channels" AbstractNioChannel +DatagramPacket ..+ NioDatagramChannel +App --> "-dispatcher" Dispatcher +ChangeKeyOpsCommand --+ NioReactor +LoggingHandler ..|> ChannelHandler +NioDatagramChannel --|> AbstractNioChannel +NioServerSocketChannel --|> AbstractNioChannel +SameThreadDispatcher ..|> Dispatcher +ThreadPoolDispatcher ..|> Dispatcher +@enduml \ No newline at end of file diff --git a/reactor/pom.xml b/reactor/pom.xml index f50ff1391ea6..7610804309e5 100644 --- a/reactor/pom.xml +++ b/reactor/pom.xml @@ -2,7 +2,7 @@ "-readerLock" ReadLock +ReadLock --+ ReaderWriterLock +WriteLock --+ ReaderWriterLock +ReaderWriterLock --> "-writerLock" WriteLock +@enduml \ No newline at end of file diff --git a/reader-writer-lock/pom.xml b/reader-writer-lock/pom.xml index de6e57dc4569..3892e5c62fee 100644 --- a/reader-writer-lock/pom.xml +++ b/reader-writer-lock/pom.xml @@ -2,7 +2,7 @@ - 4.0.0 com.iluwatar java-design-patterns - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT reader-writer-lock - - org.junit.jupiter - junit-jupiter-api - test - org.junit.jupiter junit-jupiter-engine diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java index 42335f313f1c..f59ba10cc0bb 100644 --- a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java +++ b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,26 +28,23 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * * In a multiple thread applications, the threads may try to synchronize the shared resources * regardless of read or write operation. It leads to a low performance especially in a "read more * write less" system as indeed the read operations are thread-safe to another read operation. - *

    - * Reader writer lock is a synchronization primitive that try to resolve this problem. This pattern - * allows concurrent access for read-only operations, while write operations require exclusive - * access. This means that multiple threads can read the data in parallel but an exclusive lock is - * needed for writing or modifying data. When a writer is writing the data, all other writers or - * readers will be blocked until the writer is finished writing. - * - *

    - * This example use two mutex to demonstrate the concurrent access of multiple readers and writers. - * - * + * + *

    Reader writer lock is a synchronization primitive that try to resolve this problem. This + * pattern allows concurrent access for read-only operations, while write operations require + * exclusive access. This means that multiple threads can read the data in parallel but an exclusive + * lock is needed for writing or modifying data. When a writer is writing the data, all other + * writers or readers will be blocked until the writer is finished writing. + * + *

    This example use two mutex to demonstrate the concurrent access of multiple readers and + * writers. + * * @author hongshuwei@gmail.com */ public class App { @@ -55,27 +52,27 @@ public class App { private static final Logger LOGGER = LoggerFactory.getLogger(App.class); /** - * Program entry point - * + * Program entry point. + * * @param args command line args */ public static void main(String[] args) { ExecutorService executeService = Executors.newFixedThreadPool(10); ReaderWriterLock lock = new ReaderWriterLock(); - + // Start writers IntStream.range(0, 5) - .forEach(i -> executeService.submit(new Writer("Writer " + i, lock.writeLock(), + .forEach(i -> executeService.submit(new Writer("Writer " + i, lock.writeLock(), ThreadLocalRandom.current().nextLong(5000)))); LOGGER.info("Writers added..."); // Start readers IntStream.range(0, 5) - .forEach(i -> executeService.submit(new Reader("Reader " + i, lock.readLock(), + .forEach(i -> executeService.submit(new Reader("Reader " + i, lock.readLock(), ThreadLocalRandom.current().nextLong(10)))); LOGGER.info("Readers added..."); - + try { Thread.sleep(5000L); } catch (InterruptedException e) { @@ -85,11 +82,10 @@ public static void main(String[] args) { // Start readers IntStream.range(6, 10) - .forEach(i -> executeService.submit(new Reader("Reader " + i, lock.readLock(), + .forEach(i -> executeService.submit(new Reader("Reader " + i, lock.readLock(), ThreadLocalRandom.current().nextLong(10)))); LOGGER.info("More readers added..."); - - + // In the system console, it can see that the read operations are executed concurrently while // write operations are exclusive. diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java index b0ccecabadaf..6d705de2fb85 100644 --- a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java +++ b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,15 +20,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.reader.writer.lock; import java.util.concurrent.locks.Lock; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Reader class, read when it acquired the read lock + * Reader class, read when it acquired the read lock. */ public class Reader implements Runnable { @@ -37,14 +37,14 @@ public class Reader implements Runnable { private Lock readLock; private String name; - + private long readingTime; /** - * Create new Reader - * - * @param name - Name of the thread owning the reader - * @param readLock - Lock for this reader + * Create new Reader. + * + * @param name - Name of the thread owning the reader + * @param readLock - Lock for this reader * @param readingTime - amount of time (in milliseconds) for this reader to engage reading */ public Reader(String name, Lock readLock, long readingTime) { @@ -52,11 +52,11 @@ public Reader(String name, Lock readLock, long readingTime) { this.readLock = readLock; this.readingTime = readingTime; } - + /** - * Create new Reader who reads for 250ms - * - * @param name - Name of the thread owning the reader + * Create new Reader who reads for 250ms. + * + * @param name - Name of the thread owning the reader * @param readLock - Lock for this reader */ public Reader(String name, Lock readLock) { @@ -77,8 +77,7 @@ public void run() { } /** - * Simulate the read operation - * + * Simulate the read operation. */ public void read() throws InterruptedException { LOGGER.info("{} begin", name); diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java index f0f5a009040a..d578b0f21435 100644 --- a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java +++ b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.reader.writer.lock; import java.util.HashSet; @@ -28,18 +29,17 @@ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class responsible for control the access for reader or writer - * - * Allows multiple readers to hold the lock at same time, but if any writer holds the lock then readers wait. If reader - * holds the lock then writer waits. This lock is not fair. + * + *

    Allows multiple readers to hold the lock at same time, but if any writer holds the lock then + * readers wait. If reader holds the lock then writer waits. This lock is not fair. */ public class ReaderWriterLock implements ReadWriteLock { - + private static final Logger LOGGER = LoggerFactory.getLogger(ReaderWriterLock.class); @@ -49,13 +49,13 @@ public class ReaderWriterLock implements ReadWriteLock { /** * Global mutex is used to indicate that whether reader or writer gets the lock in the moment. - *

    - * 1. When it contains the reference of {@link #readerLock}, it means that the lock is acquired by the reader, another - * reader can also do the read operation concurrently.
    - * 2. When it contains the reference of reference of {@link #writerLock}, it means that the lock is acquired by the - * writer exclusively, no more reader or writer can get the lock. - *

    - * This is the most important field in this class to control the access for reader/writer. + * + *

    1. When it contains the reference of {@link #readerLock}, it means that the lock is + * acquired by the reader, another reader can also do the read operation concurrently.
    2. + * When it contains the reference of reference of {@link #writerLock}, it means that the lock is + * acquired by the writer exclusively, no more reader or writer can get the lock. + * + *

    This is the most important field in this class to control the access for reader/writer. */ private Set globalMutex = new HashSet<>(); @@ -73,22 +73,21 @@ public Lock writeLock() { } /** - * return true when globalMutex hold the reference of writerLock + * return true when globalMutex hold the reference of writerLock. */ private boolean doesWriterOwnThisLock() { return globalMutex.contains(writerLock); } /** - * Nobody get the lock when globalMutex contains nothing - * + * Nobody get the lock when globalMutex contains nothing. */ private boolean isLockFree() { return globalMutex.isEmpty(); } /** - * Reader Lock, can be access for more than one reader concurrently if no writer get the lock + * Reader Lock, can be access for more than one reader concurrently if no writer get the lock. */ private class ReadLock implements Lock { @@ -103,8 +102,8 @@ public void lock() { } /** - * Acquire the globalMutex lock on behalf of current and future concurrent readers. Make sure no writers currently - * owns the lock. + * Acquire the globalMutex lock on behalf of current and future concurrent readers. Make sure no + * writers currently owns the lock. */ private void acquireForReaders() { // Try to get the globalMutex lock for the first reader @@ -115,7 +114,8 @@ private void acquireForReaders() { try { globalMutex.wait(); } catch (InterruptedException e) { - LOGGER.info("InterruptedException while waiting for globalMutex in acquireForReaders", e); + LOGGER + .info("InterruptedException while waiting for globalMutex in acquireForReaders", e); Thread.currentThread().interrupt(); } } @@ -164,7 +164,7 @@ public Condition newCondition() { } /** - * Writer Lock, can only be accessed by one writer concurrently + * Writer Lock, can only be accessed by one writer concurrently. */ private class WriteLock implements Lock { diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java index dc379eef97ba..7a971b28bb6d 100644 --- a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java +++ b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,15 +20,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.reader.writer.lock; import java.util.concurrent.locks.Lock; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Writer class, write when it acquired the write lock + * Writer class, write when it acquired the write lock. */ public class Writer implements Runnable { @@ -37,24 +37,24 @@ public class Writer implements Runnable { private Lock writeLock; private String name; - + private long writingTime; /** - * Create new Writer who writes for 250ms - * - * @param name - Name of the thread owning the writer + * Create new Writer who writes for 250ms. + * + * @param name - Name of the thread owning the writer * @param writeLock - Lock for this writer */ public Writer(String name, Lock writeLock) { this(name, writeLock, 250L); } - + /** - * Create new Writer - * - * @param name - Name of the thread owning the writer - * @param writeLock - Lock for this writer + * Create new Writer. + * + * @param name - Name of the thread owning the writer + * @param writeLock - Lock for this writer * @param writingTime - amount of time (in milliseconds) for this reader to engage writing */ public Writer(String name, Lock writeLock, long writingTime) { @@ -76,9 +76,9 @@ public void run() { writeLock.unlock(); } } - + /** - * Simulate the write operation + * Simulate the write operation. */ public void write() throws InterruptedException { LOGGER.info("{} begin", name); diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/AppTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/AppTest.java index fbdf3f846a97..0c323724e471 100644 --- a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/AppTest.java +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.reader.writer.lock; import org.junit.jupiter.api.Test; diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderAndWriterTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderAndWriterTest.java index c8de8c511b55..1f6edfa3cf3f 100644 --- a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderAndWriterTest.java +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderAndWriterTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderTest.java index 8fe5912ea3a5..849cf32ba802 100644 --- a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderTest.java +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.reader.writer.lock; import com.iluwatar.reader.writer.lock.utils.InMemoryAppender; diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/WriterTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/WriterTest.java index bb01e11b09eb..60c78e57313a 100644 --- a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/WriterTest.java +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/WriterTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.reader.writer.lock; import com.iluwatar.reader.writer.lock.utils.InMemoryAppender; diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java index b8ad531ce856..98f14a82bcc0 100644 --- a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.reader.writer.lock.utils; import ch.qos.logback.classic.Logger; diff --git a/repository/README.md b/repository/README.md index 67b3ea44e828..b500d2efd870 100644 --- a/repository/README.md +++ b/repository/README.md @@ -3,11 +3,9 @@ layout: pattern title: Repository folder: repository permalink: /patterns/repository/ -categories: Persistence Tier +categories: Architectural tags: - - Java - - Difficulty-Intermediate - - Spring + - Data access --- ## Intent @@ -17,15 +15,16 @@ to minimize scattering and duplication of query code. The Repository pattern is especially useful in systems where number of domain classes is large or heavy querying is utilized. +## Class diagram ![alt text](./etc/repository.png "Repository") ## Applicability Use the Repository pattern when -* the number of domain objects is large -* you want to avoid duplication of query code -* you want to keep the database querying code in single place -* you have multiple data sources +* The number of domain objects is large +* You want to avoid duplication of query code +* You want to keep the database querying code in single place +* You have multiple data sources ## Real world examples @@ -35,3 +34,4 @@ Use the Repository pattern when * [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/) +* [Repository Pattern Benefits and Spring Implementation](https://stackoverflow.com/questions/40068965/repository-pattern-benefits-and-spring-implementation) diff --git a/repository/etc/repository.urm.puml b/repository/etc/repository.urm.puml new file mode 100644 index 000000000000..10768260cefc --- /dev/null +++ b/repository/etc/repository.urm.puml @@ -0,0 +1,56 @@ +@startuml +package com.iluwatar.repository { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + class AppConfig { + - LOGGER : Logger {static} + + AppConfig() + + dataSource() : DataSource + + entityManagerFactory() : LocalContainerEntityManagerFactoryBean + - jpaProperties() : Properties {static} + + main(args : String[]) {static} + + transactionManager() : JpaTransactionManager + } + class Person { + - age : int + - id : Long + - name : String + - surname : String + + Person() + + Person(name : String, surname : String, age : int) + + equals(obj : Object) : boolean + + getAge() : int + + getId() : Long + + getName() : String + + getSurname() : String + + hashCode() : int + + setAge(age : int) + + setId(id : Long) + + setName(name : String) + + setSurname(surname : String) + + toString() : String + } + interface PersonRepository { + + findByName(String) : Person {abstract} + } + class PersonSpecifications { + + PersonSpecifications() + } + class AgeBetweenSpec { + - from : int + - to : int + + AgeBetweenSpec(from : int, to : int) + + toPredicate(root : Root, query : CriteriaQuery, cb : CriteriaBuilder) : Predicate + } + class NameEqualSpec { + + name : String + + NameEqualSpec(name : String) + + toPredicate(root : Root, query : CriteriaQuery, cb : CriteriaBuilder) : Predicate + } +} +NameEqualSpec ..+ PersonSpecifications +AgeBetweenSpec ..+ PersonSpecifications +@enduml \ No newline at end of file diff --git a/repository/pom.xml b/repository/pom.xml index 6b0ab438eba4..3bae29a70ba9 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -2,7 +2,7 @@ "-op" BusinessOperation +Retry --> "-op" BusinessOperation +App --> "-op" BusinessOperation +FindCustomer ..|> BusinessOperation +Retry ..|> BusinessOperation +RetryExponentialBackoff ..|> BusinessOperation +@enduml \ No newline at end of file diff --git a/retry/pom.xml b/retry/pom.xml index 80a40a45f600..d1dc9531f71b 100644 --- a/retry/pom.xml +++ b/retry/pom.xml @@ -1,7 +1,8 @@ 4.0.0 com.iluwatar java-design-patterns - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT retry jar - - org.junit.jupiter - junit-jupiter-api - test - org.junit.jupiter junit-jupiter-engine @@ -47,4 +44,4 @@ test - \ No newline at end of file + diff --git a/retry/src/main/java/com/iluwatar/retry/App.java b/retry/src/main/java/com/iluwatar/retry/App.java index 20205bdf3973..594c482953b3 100644 --- a/retry/src/main/java/com/iluwatar/retry/App.java +++ b/retry/src/main/java/com/iluwatar/retry/App.java @@ -1,25 +1,24 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2014-2016 Ilkka Seppälä - * + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package com.iluwatar.retry; @@ -28,34 +27,35 @@ import org.slf4j.LoggerFactory; /** - * The Retry pattern enables applications to handle potentially recoverable failures from - * the environment if the business requirements and nature of the failures allow it. By retrying + * The Retry pattern enables applications to handle potentially recoverable failures from + * the environment if the business requirements and nature of the failures allow it. By retrying * failed operations on external dependencies, the application may maintain stability and minimize * negative impact on the user experience. - *

    - * In our example, we have the {@link BusinessOperation} interface as an abstraction over - * all operations that our application performs involving remote systems. The calling code should - * remain decoupled from implementations. - *

    - * {@link FindCustomer} is a business operation that looks up a customer's record and returns - * its ID. Imagine its job is performed by looking up the customer in our local database and - * returning its ID. We can pass {@link CustomerNotFoundException} as one of its - * {@link FindCustomer#FindCustomer(java.lang.String, com.iluwatar.retry.BusinessException...) - * constructor parameters} in order to simulate not finding the customer. - *

    - * Imagine that, lately, this operation has experienced intermittent failures due to some weird - * corruption and/or locking in the data. After retrying a few times the customer is found. The - * database is still, however, expected to always be available. While a definitive solution is - * found to the problem, our engineers advise us to retry the operation a set number - * of times with a set delay between retries, although not too many retries otherwise the end user - * will be left waiting for a long time, while delays that are too short will not allow the database - * to recover from the load. - *

    - * To keep the calling code as decoupled as possible from this workaround, we have implemented the - * retry mechanism as a {@link BusinessOperation} named {@link Retry}. - * + * + *

    In our example, we have the {@link BusinessOperation} interface as an abstraction over all + * operations that our application performs involving remote systems. The calling code should remain + * decoupled from implementations. + * + *

    {@link FindCustomer} is a business operation that looks up a customer's record and returns + * its ID. Imagine its job is performed by looking up the customer in our local database and + * returning its ID. We can pass {@link CustomerNotFoundException} as one of its {@link + * FindCustomer#FindCustomer(java.lang.String, com.iluwatar.retry.BusinessException...) constructor + * parameters} in order to simulate not finding the customer. + * + *

    Imagine that, lately, this operation has experienced intermittent failures due to some weird + * corruption and/or locking in the data. After retrying a few times the customer is found. The + * database is still, however, expected to always be available. While a definitive solution is + * found to the problem, our engineers advise us to retry the operation a set number of times with a + * set delay between retries, although not too many retries otherwise the end user will be left + * waiting for a long time, while delays that are too short will not allow the database to recover + * from the load. + * + *

    To keep the calling code as decoupled as possible from this workaround, we have implemented + * the retry mechanism as a {@link BusinessOperation} named {@link Retry}. + * * @author George Aristy (george.aristy@gmail.com) - * @see Retry pattern (Microsoft Azure Docs) + * @see Retry pattern + * (Microsoft Azure Docs) */ public final class App { private static final Logger LOG = LoggerFactory.getLogger(App.class); @@ -63,7 +63,7 @@ public final class App { /** * Entry point. - * + * * @param args not used * @throws Exception not expected */ @@ -100,22 +100,22 @@ private static void errorWithRetry() throws Exception { final String customerId = op.perform(); LOG.info(String.format( "However, retrying the operation while ignoring a recoverable error will eventually yield " - + "the result %s after a number of attempts %s", customerId, retry.attempts() + + "the result %s after a number of attempts %s", customerId, retry.attempts() )); } - + private static void errorWithRetryExponentialBackoff() throws Exception { final RetryExponentialBackoff retry = new RetryExponentialBackoff<>( new FindCustomer("123", new CustomerNotFoundException("not found")), 6, //6 attempts 30000, //30 s max delay between attempts e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()) - ); + ); op = retry; final String customerId = op.perform(); LOG.info(String.format( - "However, retrying the operation while ignoring a recoverable error will eventually yield " - + "the result %s after a number of attempts %s", customerId, retry.attempts() - )); + "However, retrying the operation while ignoring a recoverable error will eventually yield " + + "the result %s after a number of attempts %s", customerId, retry.attempts() + )); } } diff --git a/retry/src/main/java/com/iluwatar/retry/BusinessException.java b/retry/src/main/java/com/iluwatar/retry/BusinessException.java index eefbf2813cbf..397a4623b0ed 100644 --- a/retry/src/main/java/com/iluwatar/retry/BusinessException.java +++ b/retry/src/main/java/com/iluwatar/retry/BusinessException.java @@ -1,34 +1,33 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2014-2016 Ilkka Seppälä - * + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package com.iluwatar.retry; /** - * The top-most type in our exception hierarchy that signifies that an unexpected error - * condition occurred. Its use is reserved as a "catch-all" for cases where no other subtype - * captures the specificity of the error condition in question. Calling code is not expected to - * be able to handle this error and should be reported to the maintainers immediately. + * The top-most type in our exception hierarchy that signifies that an unexpected error condition + * occurred. Its use is reserved as a "catch-all" for cases where no other subtype captures the + * specificity of the error condition in question. Calling code is not expected to be able to handle + * this error and should be reported to the maintainers immediately. * * @author George Aristy (george.aristy@gmail.com) */ @@ -36,8 +35,8 @@ public class BusinessException extends Exception { private static final long serialVersionUID = 6235833142062144336L; /** - * Ctor - * + * Ctor. + * * @param message the error message */ public BusinessException(String message) { diff --git a/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java b/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java index aefb589c79c3..a67240d3c761 100644 --- a/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java +++ b/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java @@ -1,25 +1,24 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2014-2016 Ilkka Seppälä - * + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package com.iluwatar.retry; @@ -27,18 +26,18 @@ /** * Performs some business operation. * - * @author George Aristy (george.aristy@gmail.com) * @param the return type + * @author George Aristy (george.aristy@gmail.com) */ @FunctionalInterface public interface BusinessOperation { /** * Performs some business operation, returning a value {@code T} if successful, otherwise throwing * an exception if an error occurs. - * + * * @return the return value * @throws BusinessException if the operation fails. Implementations are allowed to throw more - * specific subtypes depending on the error conditions + * specific subtypes depending on the error conditions */ T perform() throws BusinessException; } diff --git a/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java b/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java index 596d8584d67b..3826fe0e1a37 100644 --- a/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java +++ b/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java @@ -1,34 +1,34 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2014-2016 Ilkka Seppälä - * + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package com.iluwatar.retry; /** - * Indicates that the customer was not found. - *

    - * The severity of this error is bounded by its context: was the search for the customer triggered - * by an input from some end user, or were the search parameters pulled from your database? + * Indicates that the customer was not found. + * + *

    The severity of this error is bounded by its context: was the search for the customer + * triggered by an input from some end user, or were the search parameters pulled from your + * database? * * @author George Aristy (george.aristy@gmail.com) */ @@ -37,7 +37,7 @@ public final class CustomerNotFoundException extends BusinessException { /** * Ctor. - * + * * @param message the error message */ public CustomerNotFoundException(String message) { diff --git a/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java b/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java index 2a93e992d333..37fcecf32edb 100644 --- a/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java +++ b/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java @@ -1,25 +1,24 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2014-2016 Ilkka Seppälä - * + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package com.iluwatar.retry; @@ -34,7 +33,7 @@ public final class DatabaseNotAvailableException extends BusinessException { /** * Ctor. - * + * * @param message the error message */ public DatabaseNotAvailableException(String message) { diff --git a/retry/src/main/java/com/iluwatar/retry/FindCustomer.java b/retry/src/main/java/com/iluwatar/retry/FindCustomer.java index 421f450e59f1..af3e5469a35e 100644 --- a/retry/src/main/java/com/iluwatar/retry/FindCustomer.java +++ b/retry/src/main/java/com/iluwatar/retry/FindCustomer.java @@ -1,38 +1,37 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2014-2016 Ilkka Seppälä - * + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package com.iluwatar.retry; import java.util.ArrayDeque; -import java.util.Arrays; import java.util.Deque; +import java.util.List; /** * Finds a customer, returning its ID from our records. - *

    - * This is an imaginary operation that, for some imagined input, returns the ID for a customer. - * However, this is a "flaky" operation that is supposed to fail intermittently, but for the + * + *

    This is an imaginary operation that, for some imagined input, returns the ID for a customer. + * However, this is a "flaky" operation that is supposed to fail intermittently, but for the * purposes of this example it fails in a programmed way depending on the constructor parameters. * * @author George Aristy (george.aristy@gmail.com) @@ -43,15 +42,15 @@ public final class FindCustomer implements BusinessOperation { /** * Ctor. - * + * * @param customerId the final result of the remote operation - * @param errors the errors to throw before returning {@code customerId} + * @param errors the errors to throw before returning {@code customerId} */ public FindCustomer(String customerId, BusinessException... errors) { this.customerId = customerId; - this.errors = new ArrayDeque<>(Arrays.asList(errors)); + this.errors = new ArrayDeque<>(List.of(errors)); } - + @Override public String perform() throws BusinessException { if (!this.errors.isEmpty()) { diff --git a/retry/src/main/java/com/iluwatar/retry/Retry.java b/retry/src/main/java/com/iluwatar/retry/Retry.java index da5c76d5d3ac..f54c51301269 100644 --- a/retry/src/main/java/com/iluwatar/retry/Retry.java +++ b/retry/src/main/java/com/iluwatar/retry/Retry.java @@ -1,25 +1,24 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2014-2016 Ilkka Seppälä - * + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package com.iluwatar.retry; @@ -34,8 +33,8 @@ /** * Decorates {@link BusinessOperation business operation} with "retry" capabilities. * - * @author George Aristy (george.aristy@gmail.com) * @param the remote op's return type + * @author George Aristy (george.aristy@gmail.com) */ public final class Retry implements BusinessOperation { private final BusinessOperation op; @@ -47,18 +46,18 @@ public final class Retry implements BusinessOperation { /** * Ctor. - * - * @param op the {@link BusinessOperation} to retry + * + * @param op the {@link BusinessOperation} to retry * @param maxAttempts number of times to retry - * @param delay delay (in milliseconds) between attempts + * @param delay delay (in milliseconds) between attempts * @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions - * will be ignored if no tests are given + * will be ignored if no tests are given */ @SafeVarargs public Retry( - BusinessOperation op, - int maxAttempts, - long delay, + BusinessOperation op, + int maxAttempts, + long delay, Predicate... ignoreTests ) { this.op = op; @@ -71,7 +70,7 @@ public Retry( /** * The errors encountered while retrying, in the encounter order. - * + * * @return the errors encountered while retrying */ public List errors() { @@ -80,7 +79,7 @@ public List errors() { /** * The number of retries performed. - * + * * @return the number of retries performed */ public int attempts() { @@ -94,7 +93,7 @@ public T perform() throws BusinessException { return this.op.perform(); } catch (BusinessException e) { this.errors.add(e); - + if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) { throw e; } @@ -105,7 +104,6 @@ public T perform() throws BusinessException { //ignore } } - } - while (true); + } while (true); } } diff --git a/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java b/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java index b24bebbce192..c074f9cd49ff 100644 --- a/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java +++ b/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java @@ -1,7 +1,6 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2014-2016 Ilkka Seppälä + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -10,16 +9,16 @@ * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package com.iluwatar.retry; @@ -35,10 +34,11 @@ /** * Decorates {@link BusinessOperation business operation} with "retry" capabilities. * - * @author George Aristy (george.aristy@gmail.com) * @param the remote op's return type + * @author George Aristy (george.aristy@gmail.com) */ public final class RetryExponentialBackoff implements BusinessOperation { + private static final Random RANDOM = new Random(); private final BusinessOperation op; private final int maxAttempts; private final long maxDelay; @@ -46,20 +46,20 @@ public final class RetryExponentialBackoff implements BusinessOperation { private final Predicate test; private final List errors; - /** - * Ctor. - * - * @param op the {@link BusinessOperation} to retry - * @param maxAttempts number of times to retry - * @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions - * will be ignored if no tests are given - */ + /** + * Ctor. + * + * @param op the {@link BusinessOperation} to retry + * @param maxAttempts number of times to retry + * @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions + * will be ignored if no tests are given + */ @SafeVarargs public RetryExponentialBackoff( - BusinessOperation op, - int maxAttempts, - long maxDelay, - Predicate... ignoreTests + BusinessOperation op, + int maxAttempts, + long maxDelay, + Predicate... ignoreTests ) { this.op = op; this.maxAttempts = maxAttempts; @@ -69,20 +69,20 @@ public RetryExponentialBackoff( this.errors = new ArrayList<>(); } - /** - * The errors encountered while retrying, in the encounter order. - * - * @return the errors encountered while retrying - */ + /** + * The errors encountered while retrying, in the encounter order. + * + * @return the errors encountered while retrying + */ public List errors() { return Collections.unmodifiableList(this.errors); } - /** - * The number of retries performed. - * - * @return the number of retries performed - */ + /** + * The number of retries performed. + * + * @return the number of retries performed + */ public int attempts() { return this.attempts.intValue(); } @@ -100,16 +100,14 @@ public T perform() throws BusinessException { } try { - Random rand = new Random(); - long testDelay = (long) Math.pow(2, this.attempts()) * 1000 + rand.nextInt(1000); + long testDelay = (long) Math.pow(2, this.attempts()) * 1000 + RANDOM.nextInt(1000); long delay = testDelay < this.maxDelay ? testDelay : maxDelay; Thread.sleep(delay); } catch (InterruptedException f) { //ignore } } - } - while (true); + } while (true); } } diff --git a/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java b/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java index 5c0cc66ed36b..b52763099bd4 100644 --- a/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java +++ b/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java @@ -1,25 +1,24 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2014-2016 Ilkka Seppälä - * + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package com.iluwatar.retry; diff --git a/retry/src/test/java/com/iluwatar/retry/RetryExponentialBackoffTest.java b/retry/src/test/java/com/iluwatar/retry/RetryExponentialBackoffTest.java index d14b1eef68a6..f07d665af6a3 100644 --- a/retry/src/test/java/com/iluwatar/retry/RetryExponentialBackoffTest.java +++ b/retry/src/test/java/com/iluwatar/retry/RetryExponentialBackoffTest.java @@ -1,7 +1,6 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2014-2016 Ilkka Seppälä + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -10,16 +9,16 @@ * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package com.iluwatar.retry; diff --git a/retry/src/test/java/com/iluwatar/retry/RetryTest.java b/retry/src/test/java/com/iluwatar/retry/RetryTest.java index a8307d1cd9f3..64df9d14d8f9 100644 --- a/retry/src/test/java/com/iluwatar/retry/RetryTest.java +++ b/retry/src/test/java/com/iluwatar/retry/RetryTest.java @@ -1,25 +1,24 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2014-2016 Ilkka Seppälä - * + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package com.iluwatar.retry; @@ -43,7 +42,8 @@ public class RetryTest { public void errors() { final BusinessException e = new BusinessException("unhandled"); final Retry retry = new Retry<>( - () -> { throw e; }, + () -> { + throw e; }, 2, 0 ); @@ -67,7 +67,8 @@ public void errors() { public void attempts() { final BusinessException e = new BusinessException("unhandled"); final Retry retry = new Retry<>( - () -> { throw e; }, + () -> { + throw e; }, 2, 0 ); @@ -91,7 +92,8 @@ public void attempts() { public void ignore() throws Exception { final BusinessException e = new CustomerNotFoundException("customer not found"); final Retry retry = new Retry<>( - () -> { throw e; }, + () -> { + throw e; }, 2, 0, ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass()) diff --git a/role-object/README.md b/role-object/README.md new file mode 100644 index 000000000000..424c64cda69b --- /dev/null +++ b/role-object/README.md @@ -0,0 +1,34 @@ +--- +layout: pattern +title: Role Object +folder: role-object +permalink: /patterns/role-object/ +categories: Structural +tags: + - Extensibility +--- + +## Also known as +Post pattern, Extension Object pattern + +## Intent +Adapt an object to different client’s needs through transparently attached role objects, each one representing a role +the object has to play in that client’s context. The object manages its role set dynamically. By representing roles as +individual objects, different contexts are kept separate and system configuration is simplified. + +## Class diagram +![alt text](./etc/role-object.urm.png "Role Object pattern class diagram") + +## Applicability +Use the Role Object pattern, if: + +- You want to handle a key abstraction in different contexts and you do not want to put the resulting context specific interfaces into the same class interface. +- You want to handle the available roles dynamically so that they can be attached and removed on demand, that is at runtime, rather than fixing them statically at compile-time. +- You want to treat the extensions transparently and need to preserve the logical object identity of the resultingobject conglomerate. +- You want to keep role/client pairs independent from each other so that changes to a role do not affect clients that are not interested in that role. + +## Credits + +- [Hillside - Role object pattern](https://hillside.net/plop/plop97/Proceedings/riehle.pdf) +- [Role object](http://wiki.c2.com/?RoleObject) +- [Fowler - Dealing with roles](https://martinfowler.com/apsupp/roles.pdf) diff --git a/role-object/etc/role-object.urm.png b/role-object/etc/role-object.urm.png new file mode 100644 index 000000000000..65201c68aff4 Binary files /dev/null and b/role-object/etc/role-object.urm.png differ diff --git a/role-object/etc/role-object.urm.puml b/role-object/etc/role-object.urm.puml new file mode 100644 index 000000000000..241a146f0b9f --- /dev/null +++ b/role-object/etc/role-object.urm.puml @@ -0,0 +1,60 @@ +@startuml +package com.iluwatar.roleobject { + class ApplicationRoleObject { + - logger : Logger {static} + + ApplicationRoleObject() + + main(args : String[]) {static} + } + class BorrowerRole { + - name : String + + BorrowerRole() + + borrow() : String + + getName() : String + + setName(name : String) + } + abstract class Customer { + + Customer() + + addRole(Role) : boolean {abstract} + + getRole(Role, Class) : Optional {abstract} + + hasRole(Role) : boolean {abstract} + + newCustomer() : Customer {static} + + newCustomer(role : Role[]) : Customer {static} + + remRole(Role) : boolean {abstract} + } + class CustomerCore { + - roles : Map + + CustomerCore() + + addRole(role : Role) : boolean + + getRole(role : Role, expectedRole : Class) : Optional + + hasRole(role : Role) : boolean + + remRole(role : Role) : boolean + + toString() : String + } + abstract class CustomerRole { + + CustomerRole() + } + class InvestorRole { + - amountToInvest : long + - name : String + + InvestorRole() + + getAmountToInvest() : long + + getName() : String + + invest() : String + + setAmountToInvest(amountToInvest : long) + + setName(name : String) + } + enum Role { + + Borrower {static} + + Investor {static} + - logger : Logger {static} + - typeCst : Class + + instance() : Optional + + valueOf(name : String) : Role {static} + + values() : Role[] {static} + } +} +BorrowerRole --|> CustomerRole +CustomerCore --|> Customer +CustomerRole --|> CustomerCore +InvestorRole --|> CustomerRole +@enduml \ No newline at end of file diff --git a/data-mapper/src/main/resources/log4j.xml b/role-object/pom.xml similarity index 62% rename from data-mapper/src/main/resources/log4j.xml rename to role-object/pom.xml index 14f043ff7576..322122897394 100644 --- a/data-mapper/src/main/resources/log4j.xml +++ b/role-object/pom.xml @@ -1,8 +1,8 @@ - + - - - - - - - - - - - - - - - \ No newline at end of file + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + + role-object + + + junit + junit + test + + + + diff --git a/role-object/src/main/java/com/iluwatar/roleobject/ApplicationRoleObject.java b/role-object/src/main/java/com/iluwatar/roleobject/ApplicationRoleObject.java new file mode 100644 index 000000000000..eb76ef34a522 --- /dev/null +++ b/role-object/src/main/java/com/iluwatar/roleobject/ApplicationRoleObject.java @@ -0,0 +1,100 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.roleobject; + +import static com.iluwatar.roleobject.Role.Borrower; +import static com.iluwatar.roleobject.Role.Investor; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Role Object pattern suggests to model context-specific views + * of an object as separate role objects which are + * dynamically attached to and removed from the core object. + * We call the resulting composite object structure, + * consisting of the core and its role objects, a subject. + * A subject often plays several roles and the same role is likely to + * be played by different subjects. + * As an example consider two different customers playing the role of borrower and + * investor, respectively. Both roles could as well be played by a single {@link Customer} object. + * The common superclass for customer-specific roles is provided by {@link CustomerRole}, + * which also supports the {@link Customer} interface. + * + *

    The {@link CustomerRole} class is abstract and not meant to be instantiated. + * Concrete subclasses of {@link CustomerRole}, for example {@link BorrowerRole} + * or {@link InvestorRole}, define and implement the interface for specific roles. It is only + * these subclasses which are instantiated at runtime. + * The {@link BorrowerRole} class defines the context-specific view of {@link Customer} + * objects as needed by the loan department. + * It defines additional operations to manage the customer’s + * credits and securities. Similarly, the {@link InvestorRole} class adds operations specific + * to the investment department’s view of customers. + * A client like the loan application may either work with objects of the {@link CustomerRole} + * class, using the interface class {@link Customer}, or with objects of concrete + * {@link CustomerRole} subclasses. Suppose the loan application knows a particular + * {@link Customer} instance through its {@link Customer} interface. The loan application + * may want to check whether the {@link Customer} object plays the role of Borrower. + * To this end it calls {@link Customer#hasRole(Role)} with a suitable role specification. For + * the purpose of our example, let’s assume we can name roles with enum. + * If the {@link Customer} object can play the role named “Borrower,” the loan application will + * ask it to return a reference to the corresponding object. + * The loan application may now use this reference to call Borrower-specific operations. + */ +public class ApplicationRoleObject { + + private static final Logger logger = LoggerFactory.getLogger(Role.class); + + /** + * Main entry point. + * + * @param args program arguments + */ + public static void main(String[] args) { + Customer customer = Customer.newCustomer(Borrower, Investor); + + logger.info(" the new customer created : {}", customer); + + boolean hasBorrowerRole = customer.hasRole(Borrower); + logger.info(" customer has a borrowed role - {}", hasBorrowerRole); + boolean hasInvestorRole = customer.hasRole(Investor); + logger.info(" customer has an investor role - {}", hasInvestorRole); + + customer.getRole(Investor, InvestorRole.class) + .ifPresent(inv -> { + inv.setAmountToInvest(1000); + inv.setName("Billy"); + }); + customer.getRole(Borrower, BorrowerRole.class) + .ifPresent(inv -> inv.setName("Johny")); + + customer.getRole(Investor, InvestorRole.class) + .map(InvestorRole::invest) + .ifPresent(logger::info); + + customer.getRole(Borrower, BorrowerRole.class) + .map(BorrowerRole::borrow) + .ifPresent(logger::info); + } +} diff --git a/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java b/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java new file mode 100644 index 000000000000..19db1c823a79 --- /dev/null +++ b/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java @@ -0,0 +1,42 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.roleobject; + +public class BorrowerRole extends CustomerRole { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String borrow() { + return String.format("Borrower %s wants to get some money.", name); + } + +} diff --git a/role-object/src/main/java/com/iluwatar/roleobject/Customer.java b/role-object/src/main/java/com/iluwatar/roleobject/Customer.java new file mode 100644 index 000000000000..1d21c5cc80a9 --- /dev/null +++ b/role-object/src/main/java/com/iluwatar/roleobject/Customer.java @@ -0,0 +1,81 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.roleobject; + +import java.util.Optional; + +/** + * The main abstraction to work with Customer. + */ +public abstract class Customer { + + /** + * Add specific role @see {@link Role}. + * @param role to add + * @return true if the operation has been successful otherwise false + */ + public abstract boolean addRole(Role role); + + /** + * Check specific role @see {@link Role}. + * @param role to check + * @return true if the role exists otherwise false + */ + + public abstract boolean hasRole(Role role); + + /** + * Remove specific role @see {@link Role}. + * @param role to remove + * @return true if the operation has been successful otherwise false + */ + public abstract boolean remRole(Role role); + + /** + * Get specific instance associated with this role @see {@link Role}. + * @param role to get + * @param expectedRole instance class expected to get + * @return optional with value if the instance exists and corresponds expected class + */ + public abstract Optional getRole(Role role, Class expectedRole); + + + public static Customer newCustomer() { + return new CustomerCore(); + } + + /** + * Create {@link Customer} with given roles. + * @param role roles + * @return Customer + */ + public static Customer newCustomer(Role... role) { + Customer customer = newCustomer(); + for (Role r : role) { + customer.addRole(r); + } + return customer; + } + +} diff --git a/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java b/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java new file mode 100644 index 000000000000..670a53904ef7 --- /dev/null +++ b/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java @@ -0,0 +1,79 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.roleobject; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * Core class to store different customer roles. + * + * @see CustomerRole Note: not thread safe + */ +public class CustomerCore extends Customer { + + private Map roles; + + public CustomerCore() { + roles = new HashMap<>(); + } + + @Override + public boolean addRole(Role role) { + return role + .instance() + .map(inst -> { + roles.put(role, inst); + return true; + }) + .orElse(false); + } + + @Override + public boolean hasRole(Role role) { + return roles.containsKey(role); + } + + @Override + public boolean remRole(Role role) { + return Objects.nonNull(roles.remove(role)); + } + + @Override + public Optional getRole(Role role, Class expectedRole) { + return Optional + .ofNullable(roles.get(role)) + .filter(expectedRole::isInstance) + .map(expectedRole::cast); + } + + @Override + public String toString() { + String roles = Arrays.toString(this.roles.keySet().toArray()); + return "Customer{roles=" + roles + "}"; + } +} diff --git a/role-object/src/main/java/com/iluwatar/roleobject/CustomerRole.java b/role-object/src/main/java/com/iluwatar/roleobject/CustomerRole.java new file mode 100644 index 000000000000..7f1805924fdc --- /dev/null +++ b/role-object/src/main/java/com/iluwatar/roleobject/CustomerRole.java @@ -0,0 +1,30 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.roleobject; + +/** + * Key abstraction for segregated roles. + */ +public abstract class CustomerRole extends CustomerCore{ +} diff --git a/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java b/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java new file mode 100644 index 000000000000..d95289a07140 --- /dev/null +++ b/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java @@ -0,0 +1,51 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.roleobject; + +public class InvestorRole extends CustomerRole { + + private String name; + + private long amountToInvest; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getAmountToInvest() { + return amountToInvest; + } + + public void setAmountToInvest(long amountToInvest) { + this.amountToInvest = amountToInvest; + } + + public String invest() { + return String.format("Investor %s has invested %d dollars", name, amountToInvest); + } +} diff --git a/role-object/src/main/java/com/iluwatar/roleobject/Role.java b/role-object/src/main/java/com/iluwatar/roleobject/Role.java new file mode 100644 index 000000000000..74863ad84051 --- /dev/null +++ b/role-object/src/main/java/com/iluwatar/roleobject/Role.java @@ -0,0 +1,60 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.roleobject; + +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Possible roles. + */ +public enum Role { + + Borrower(BorrowerRole.class), Investor(InvestorRole.class); + + private Class typeCst; + + Role(Class typeCst) { + this.typeCst = typeCst; + } + + private static final Logger logger = LoggerFactory.getLogger(Role.class); + + /** + * Get instance. + */ + @SuppressWarnings("unchecked") + public Optional instance() { + Class typeCst = this.typeCst; + try { + return (Optional) Optional.of(typeCst.newInstance()); + } catch (InstantiationException | IllegalAccessException e) { + logger.error("error creating an object", e); + } + return Optional.empty(); + } + +} diff --git a/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java b/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java new file mode 100644 index 000000000000..831781d7103b --- /dev/null +++ b/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java @@ -0,0 +1,33 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.roleobject; + +import org.junit.Test; + +public class ApplicationRoleObjectTest { + + @Test + public void mainTest() { + ApplicationRoleObject.main(new String[]{}); + } +} \ No newline at end of file diff --git a/role-object/src/test/java/com/iluwatar/roleobject/BorrowerRoleTest.java b/role-object/src/test/java/com/iluwatar/roleobject/BorrowerRoleTest.java new file mode 100644 index 000000000000..0c0f92fc2322 --- /dev/null +++ b/role-object/src/test/java/com/iluwatar/roleobject/BorrowerRoleTest.java @@ -0,0 +1,40 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.roleobject; + +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class BorrowerRoleTest { + + @Test + public void borrowTest() { + BorrowerRole borrowerRole = new BorrowerRole(); + borrowerRole.setName("test"); + String res = "Borrower test wants to get some money."; + + Assert.assertEquals(borrowerRole.borrow(),res); + } +} \ No newline at end of file diff --git a/role-object/src/test/java/com/iluwatar/roleobject/CustomerCoreTest.java b/role-object/src/test/java/com/iluwatar/roleobject/CustomerCoreTest.java new file mode 100644 index 000000000000..1b2987400d05 --- /dev/null +++ b/role-object/src/test/java/com/iluwatar/roleobject/CustomerCoreTest.java @@ -0,0 +1,103 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.roleobject; + +import org.junit.Test; + +import java.util.Optional; + +import static org.junit.Assert.*; + +public class CustomerCoreTest { + + @Test + public void addRole() { + CustomerCore core = new CustomerCore(); + boolean add = core.addRole(Role.Borrower); + assertTrue(add); + + } + + @Test + public void hasRole() { + CustomerCore core = new CustomerCore(); + core.addRole(Role.Borrower); + + boolean has = core.hasRole(Role.Borrower); + assertTrue(has); + + boolean notHas = core.hasRole(Role.Investor); + assertFalse(notHas); + } + + @Test + public void remRole() { + CustomerCore core = new CustomerCore(); + core.addRole(Role.Borrower); + + Optional bRole = core.getRole(Role.Borrower, BorrowerRole.class); + assertTrue(bRole.isPresent()); + + boolean res = core.remRole(Role.Borrower); + assertTrue(res); + + Optional empt = core.getRole(Role.Borrower, BorrowerRole.class); + assertFalse(empt.isPresent()); + + } + + @Test + public void getRole() { + CustomerCore core = new CustomerCore(); + core.addRole(Role.Borrower); + + Optional bRole = core.getRole(Role.Borrower, BorrowerRole.class); + assertTrue(bRole.isPresent()); + + Optional nonRole = core.getRole(Role.Borrower, InvestorRole.class); + assertFalse(nonRole.isPresent()); + + Optional invRole = core.getRole(Role.Investor, InvestorRole.class); + assertFalse(invRole.isPresent()); + + + } + + + @Test + public void toStringTest() { + CustomerCore core = new CustomerCore(); + core.addRole(Role.Borrower); + assertEquals(core.toString(), "Customer{roles=[Borrower]}"); + + core = new CustomerCore(); + core.addRole(Role.Investor); + assertEquals(core.toString(), "Customer{roles=[Investor]}"); + + core = new CustomerCore(); + assertEquals(core.toString(), "Customer{roles=[]}"); + + + } + +} \ No newline at end of file diff --git a/role-object/src/test/java/com/iluwatar/roleobject/InvestorRoleTest.java b/role-object/src/test/java/com/iluwatar/roleobject/InvestorRoleTest.java new file mode 100644 index 000000000000..06afa1016275 --- /dev/null +++ b/role-object/src/test/java/com/iluwatar/roleobject/InvestorRoleTest.java @@ -0,0 +1,38 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.roleobject; + +import org.junit.Assert; +import org.junit.Test; + +public class InvestorRoleTest { + + @Test + public void investTest() { + InvestorRole investorRole = new InvestorRole(); + investorRole.setName("test"); + investorRole.setAmountToInvest(10); + String res = "Investor test has invested 10 dollars"; + Assert.assertEquals(investorRole.invest(), res); + } +} \ No newline at end of file diff --git a/role-object/src/test/java/com/iluwatar/roleobject/RoleTest.java b/role-object/src/test/java/com/iluwatar/roleobject/RoleTest.java new file mode 100644 index 000000000000..6ae5b0cd8857 --- /dev/null +++ b/role-object/src/test/java/com/iluwatar/roleobject/RoleTest.java @@ -0,0 +1,40 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.roleobject; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Optional; + +import static org.junit.Assert.*; + +public class RoleTest { + + @Test + public void instanceTest() { + Optional instance = Role.Borrower.instance(); + Assert.assertTrue(instance.isPresent()); + Assert.assertEquals(instance.get().getClass(),BorrowerRole.class); + } +} \ No newline at end of file diff --git a/saga/README.md b/saga/README.md new file mode 100644 index 000000000000..50aeb7d7375f --- /dev/null +++ b/saga/README.md @@ -0,0 +1,48 @@ +--- +layout: pattern +title: Saga +folder: saga +permalink: /patterns/saga/ +categories: Concurrency +tags: + - Cloud distributed +--- + +## Also known as +This pattern has a similar goal with two-phase commit (XA transaction) + +## Intent +This pattern is used in distributed services to perform a group of operations atomically. +This is an analog of transaction in a database but in terms of microservices architecture this is executed +in a distributed environment + +## Explanation +A saga is a sequence of local transactions in a certain context. If one transaction fails for some reason, +the saga executes compensating transactions(rollbacks) to undo the impact of the preceding transactions. +There are two types of Saga: + +- Choreography-Based Saga. +In this approach, there is no central orchestrator. +Each service participating in the Saga performs their transaction and publish events. +The other services act upon those events and perform their transactions. +Also, they may or not publish other events based on the situation. + +- Orchestration-Based Saga +In this approach, there is a Saga orchestrator that manages all the transactions and directs +the participant services to execute local transactions based on events. +This orchestrator can also be though of as a Saga Manager. + +## Class diagram +![alt text](./etc/saga.urm.png "Saga pattern class diagram") + +## Applicability +Use the Saga pattern, if: + +- you need to perform a group of operations related to different microservices atomically +- you need to rollback changes in different places in case of failure one of the operation +- you need to take care of data consistency in different places including different databases +- you can not use 2PC(two phase commit) + +## Credits + +- [Pattern: Saga](https://microservices.io/patterns/data/saga.html) diff --git a/saga/etc/saga.urm.png b/saga/etc/saga.urm.png new file mode 100644 index 000000000000..025be6bbdedf Binary files /dev/null and b/saga/etc/saga.urm.png differ diff --git a/saga/etc/saga.urm.puml b/saga/etc/saga.urm.puml new file mode 100644 index 000000000000..e44b5366b021 --- /dev/null +++ b/saga/etc/saga.urm.puml @@ -0,0 +1,222 @@ +@startuml +package com.iluwatar.saga.orchestration { + class ChapterResult { + - state : State + - value : K + ~ ChapterResult(value : K, state : State) + + failure(val : K) : ChapterResult {static} + + getValue() : K + + isSuccess() : boolean + + success(val : K) : ChapterResult {static} + } + enum State { + + FAILURE {static} + + SUCCESS {static} + + valueOf(name : String) : State {static} + + values() : State[] {static} + } + class FlyBookingService { + + FlyBookingService() + + getName() : String + } + class HotelBookingService { + + HotelBookingService() + + getName() : String + + rollback(value : String) : ChapterResult + } + interface OrchestrationChapter { + + getName() : String {abstract} + + process(K) : ChapterResult {abstract} + + rollback(K) : ChapterResult {abstract} + } + class OrderService { + + OrderService() + + getName() : String + } + class Saga { + - chapters : List + - Saga() + + chapter(name : String) : Saga + + create() : Saga {static} + + get(idx : int) : Chapter + + isPresent(idx : int) : boolean + } + class Chapter { + ~ name : String + + Chapter(name : String) + + getName() : String + } + enum Result { + + CRASHED {static} + + FINISHED {static} + + ROLLBACK {static} + + valueOf(name : String) : Result {static} + + values() : Result[] {static} + } + class SagaApplication { + - LOGGER : Logger {static} + + SagaApplication() + + main(args : String[]) {static} + - newSaga() : Saga {static} + - serviceDiscovery() : ServiceDiscoveryService {static} + } + class SagaOrchestrator { + - LOGGER : Logger {static} + - saga : Saga + - sd : ServiceDiscoveryService + - state : CurrentState + + SagaOrchestrator(saga : Saga, sd : ServiceDiscoveryService) + + execute(value : K) : Result + } + -class CurrentState { + ~ currentNumber : int + ~ isForward : boolean + ~ CurrentState() + ~ back() : int + ~ cleanUp() + ~ current() : int + ~ directionToBack() + ~ forward() : int + ~ isForward() : boolean + } + abstract class Service { + # LOGGER : Logger {static} + + Service() + + getName() : String {abstract} + + process(value : K) : ChapterResult + + rollback(value : K) : ChapterResult + } + class ServiceDiscoveryService { + - services : Map> + + ServiceDiscoveryService() + + discover(orchestrationChapterService : OrchestrationChapter) : ServiceDiscoveryService + + find(service : String) : Optional> + } + class WithdrawMoneyService { + + WithdrawMoneyService() + + getName() : String + + process(value : String) : ChapterResult + } +} +package com.iluwatar.saga.choreography { + interface ChoreographyChapter { + + execute(Saga) : Saga {abstract} + + getName() : String {abstract} + + process(Saga) : Saga {abstract} + + rollback(Saga) : Saga {abstract} + } + class FlyBookingService { + + FlyBookingService(service : ServiceDiscoveryService) + + getName() : String + } + class HotelBookingService { + + HotelBookingService(service : ServiceDiscoveryService) + + getName() : String + } + class OrderService { + + OrderService(service : ServiceDiscoveryService) + + getName() : String + } + class Saga { + - chapters : List + - finished : boolean + - forward : boolean + - pos : int + - Saga() + ~ back() : int + + chapter(name : String) : Saga + + create() : Saga {static} + ~ forward() : int + ~ getCurrent() : Chapter + + getCurrentValue() : Object + + getResult() : SagaResult + ~ isCurrentSuccess() : boolean + ~ isForward() : boolean + ~ isPresent() : boolean + + setCurrentStatus(result : ChapterResult) + + setCurrentValue(value : Object) + ~ setFinished(finished : boolean) + + setInValue(value : Object) : Saga + + toString() : String + } + class Chapter { + - inValue : Object + - name : String + - result : ChapterResult + + Chapter(name : String) + + getInValue() : Object + + getName() : String + + isSuccess() : boolean + + setInValue(object : Object) + + setResult(result : ChapterResult) + } + enum ChapterResult { + + INIT {static} + + ROLLBACK {static} + + SUCCESS {static} + + valueOf(name : String) : ChapterResult {static} + + values() : ChapterResult[] {static} + } + enum SagaResult { + + FINISHED {static} + + PROGRESS {static} + + ROLLBACKED {static} + + valueOf(name : String) : SagaResult {static} + + values() : SagaResult[] {static} + } + class SagaApplication { + - LOGGER : Logger {static} + + SagaApplication() + + main(args : String[]) {static} + - newSaga(value : Object) : Saga {static} + - serviceDiscovery() : ServiceDiscoveryService {static} + } + abstract class Service { + # LOGGER : Logger {static} + - sd : ServiceDiscoveryService + + Service(service : ServiceDiscoveryService) + + execute(saga : Saga) : Saga + - isSagaFinished(saga : Saga) : boolean + + process(saga : Saga) : Saga + + rollback(saga : Saga) : Saga + - serviceNotFoundException(chServiceName : String) : Supplier + } + class ServiceDiscoveryService { + - services : Map + + ServiceDiscoveryService() + + discover(chapterService : ChoreographyChapter) : ServiceDiscoveryService + + find(service : String) : Optional + + findAny() : ChoreographyChapter + } + class WithdrawMoneyService { + + WithdrawMoneyService(service : ServiceDiscoveryService) + + getName() : String + + process(saga : Saga) : Saga + } +} +SagaOrchestrator --> "-saga" Saga +SagaOrchestrator --> "-sd" ServiceDiscoveryService +SagaOrchestrator --> "-state" CurrentState +CurrentState ..+ SagaOrchestrator +Chapter ..+ Saga +Saga --> "-chapters" Chapter +Chapter --> "-result" ChapterResult +ChapterResult ..+ Saga +ChapterResult --> "-state" State +State ..+ ChapterResult +Result ..+ Saga +Service --> "-sd" ServiceDiscoveryService +SagaResult ..+ Saga +Saga --> "-chapters" Chapter +Chapter ..+ Saga +FlyBookingService --|> Service +HotelBookingService --|> Service +OrderService --|> Service +Service ..|> ChoreographyChapter +WithdrawMoneyService --|> Service +FlyBookingService --|> Service +HotelBookingService --|> Service +OrderService --|> Service +Service ..|> OrchestrationChapter +WithdrawMoneyService --|> Service +@enduml \ No newline at end of file diff --git a/saga/pom.xml b/saga/pom.xml new file mode 100644 index 000000000000..a22ec9797f61 --- /dev/null +++ b/saga/pom.xml @@ -0,0 +1,45 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + + saga + + + junit + junit + test + + + + diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/ChoreographyChapter.java b/saga/src/main/java/com/iluwatar/saga/choreography/ChoreographyChapter.java new file mode 100644 index 000000000000..c79a77a9ac46 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/ChoreographyChapter.java @@ -0,0 +1,65 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.choreography; + + +/** + * ChoreographyChapter is an interface representing a contract for an external service. + * In that case, a service needs to make a decision what to do further + * hence the server needs to get all context representing {@link Saga} + */ +public interface ChoreographyChapter { + + /** + * In that case, every method is responsible to make a decision on what to do then. + * + * @param saga incoming saga + * @return saga result + */ + Saga execute(Saga saga); + + /** + * get name method. + * @return service name. + */ + String getName(); + + /** + * The operation executed in general case. + * + * @param saga incoming saga + * @return result {@link Saga} + */ + Saga process(Saga saga); + + /** + * The operation executed in rollback case. + * + * @param saga incoming saga + * @return result {@link Saga} + */ + Saga rollback(Saga saga); + + +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/FlyBookingService.java b/saga/src/main/java/com/iluwatar/saga/choreography/FlyBookingService.java new file mode 100644 index 000000000000..4a9b1e8040c0 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/FlyBookingService.java @@ -0,0 +1,39 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.choreography; + + +/** + * Class representing a service to book a fly. + */ +public class FlyBookingService extends Service { + public FlyBookingService(ServiceDiscoveryService service) { + super(service); + } + + @Override + public String getName() { + return "booking a Fly"; + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/HotelBookingService.java b/saga/src/main/java/com/iluwatar/saga/choreography/HotelBookingService.java new file mode 100644 index 000000000000..ee623482e72d --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/HotelBookingService.java @@ -0,0 +1,41 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.choreography; + + +/** + * Class representing a service to book a hotel. + */ +public class HotelBookingService extends Service { + public HotelBookingService(ServiceDiscoveryService service) { + super(service); + } + + @Override + public String getName() { + return "booking a Hotel"; + } + + +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/OrderService.java b/saga/src/main/java/com/iluwatar/saga/choreography/OrderService.java new file mode 100644 index 000000000000..19586a5a3de7 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/OrderService.java @@ -0,0 +1,40 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.choreography; + + +/** + * Class representing a service to init a new order. + */ +public class OrderService extends Service { + + public OrderService(ServiceDiscoveryService service) { + super(service); + } + + @Override + public String getName() { + return "init an order"; + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java b/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java new file mode 100644 index 000000000000..b591adaf278a --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java @@ -0,0 +1,217 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.choreography; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Saga representation. + * Saga consists of chapters. + * Every ChoreographyChapter is executed a certain service. + */ +public class Saga { + + private List chapters; + private int pos; + private boolean forward; + private boolean finished; + + + public static Saga create() { + return new Saga(); + } + + /** + * get resuzlt of saga. + * + * @return result of saga @see {@link SagaResult} + */ + public SagaResult getResult() { + if (finished) { + return forward + ? SagaResult.FINISHED + : SagaResult.ROLLBACKED; + } + + return SagaResult.PROGRESS; + } + + /** + * add chapter to saga. + * @param name chapter name + * @return this + */ + public Saga chapter(String name) { + this.chapters.add(new Chapter(name)); + return this; + } + + /** + * set value to last chapter. + * @param value invalue + * @return this + */ + public Saga setInValue(Object value) { + if (chapters.isEmpty()) { + return this; + } + chapters.get(chapters.size() - 1).setInValue(value); + return this; + } + + /** + * get value from current chapter. + * @return value + */ + public Object getCurrentValue() { + return chapters.get(pos).getInValue(); + } + + /** + * set value to current chapter. + * @param value to set + */ + public void setCurrentValue(Object value) { + chapters.get(pos).setInValue(value); + } + + /** + * set status for current chapter. + * @param result to set + */ + public void setCurrentStatus(ChapterResult result) { + chapters.get(pos).setResult(result); + } + + void setFinished(boolean finished) { + this.finished = finished; + } + + boolean isForward() { + return forward; + } + + int forward() { + return ++pos; + } + + int back() { + this.forward = false; + return --pos; + } + + + private Saga() { + this.chapters = new ArrayList<>(); + this.pos = 0; + this.forward = true; + this.finished = false; + } + + Chapter getCurrent() { + return chapters.get(pos); + } + + + boolean isPresent() { + return pos >= 0 && pos < chapters.size(); + } + + boolean isCurrentSuccess() { + return chapters.get(pos).isSuccess(); + } + + /** + * Class presents a chapter status and incoming + * parameters(incoming parameter transforms to outcoming parameter). + */ + public static class Chapter { + private String name; + private ChapterResult result; + private Object inValue; + + + public Chapter(String name) { + this.name = name; + this.result = ChapterResult.INIT; + } + + public Object getInValue() { + return inValue; + } + + public void setInValue(Object object) { + this.inValue = object; + } + + public String getName() { + return name; + } + + /** + * set result. + * @param result {@link ChapterResult} + */ + public void setResult(ChapterResult result) { + this.result = result; + } + + /** + * the result for chapter is good. + * @return true if is good otherwise bad + */ + public boolean isSuccess() { + return result == ChapterResult.SUCCESS; + } + } + + + /** + * result for chapter. + */ + public enum ChapterResult { + INIT, SUCCESS, ROLLBACK + } + + /** + * result for saga. + */ + public enum SagaResult { + PROGRESS, FINISHED, ROLLBACKED + } + + @Override + public String toString() { + return "Saga{" + + "chapters=" + + Arrays.toString(chapters.toArray()) + + ", pos=" + + pos + + ", forward=" + + forward + + '}'; + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/SagaApplication.java b/saga/src/main/java/com/iluwatar/saga/choreography/SagaApplication.java new file mode 100644 index 000000000000..d8844c864ae4 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/SagaApplication.java @@ -0,0 +1,84 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.choreography; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This pattern is used in distributed services to perform a group of operations atomically. + * This is an analog of transaction in a database but in terms + * of microservices architecture this is executed + * in a distributed environment + * + *

    A saga is a sequence of local transactions in a certain context. + * If one transaction fails for some reason, + * the saga executes compensating transactions(rollbacks) + * to undo the impact of the preceding transactions. + * + *

    In this approach, there are no mediators or orchestrators services. + * All chapters are handled and moved by services manually. + * + *

    The major difference with choreography saga is an ability to handle crashed services + * (otherwise in choreography services very hard to prevent a saga + * if one of them has been crashed) + * + * @see com.iluwatar.saga.choreography.Saga + * @see Service + */ +public class SagaApplication { + private static final Logger LOGGER = LoggerFactory.getLogger(SagaApplication.class); + + /** + * main method. + */ + public static void main(String[] args) { + ServiceDiscoveryService sd = serviceDiscovery(); + ChoreographyChapter service = sd.findAny(); + Saga goodOrderSaga = service.execute(newSaga("good_order")); + Saga badOrderSaga = service.execute(newSaga("bad_order")); + LOGGER.info("orders: goodOrder is {}, badOrder is {}", + goodOrderSaga.getResult(), badOrderSaga.getResult()); + + } + + + private static Saga newSaga(Object value) { + return Saga + .create() + .chapter("init an order").setInValue(value) + .chapter("booking a Fly") + .chapter("booking a Hotel") + .chapter("withdrawing Money"); + } + + private static ServiceDiscoveryService serviceDiscovery() { + ServiceDiscoveryService sd = new ServiceDiscoveryService(); + return sd + .discover(new OrderService(sd)) + .discover(new FlyBookingService(sd)) + .discover(new HotelBookingService(sd)) + .discover(new WithdrawMoneyService(sd)); + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/Service.java b/saga/src/main/java/com/iluwatar/saga/choreography/Service.java new file mode 100644 index 000000000000..2e932b5288c0 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/Service.java @@ -0,0 +1,113 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.choreography; + +import java.util.function.Supplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Common abstraction class representing services. + * implementing a general contract @see {@link ChoreographyChapter} + */ +public abstract class Service implements ChoreographyChapter { + protected static final Logger LOGGER = LoggerFactory.getLogger(Service.class); + + private final ServiceDiscoveryService sd; + + public Service(ServiceDiscoveryService service) { + this.sd = service; + } + + @Override + public Saga execute(Saga saga) { + Saga nextSaga = saga; + Object nextVal; + String chapterName = saga.getCurrent().getName(); + if (chapterName.equals(getName())) { + if (saga.isForward()) { + nextSaga = process(saga); + nextVal = nextSaga.getCurrentValue(); + if (nextSaga.isCurrentSuccess()) { + nextSaga.forward(); + } else { + nextSaga.back(); + } + } else { + nextSaga = rollback(saga); + nextVal = nextSaga.getCurrentValue(); + nextSaga.back(); + } + + if (isSagaFinished(nextSaga)) { + return nextSaga; + } + + nextSaga.setCurrentValue(nextVal); + } + Saga finalNextSaga = nextSaga; + + return sd.find(chapterName).map(ch -> ch.execute(finalNextSaga)) + .orElseThrow(serviceNotFoundException(chapterName)); + } + + private Supplier serviceNotFoundException(String chServiceName) { + return () -> new RuntimeException( + String.format("the service %s has not been found", chServiceName)); + } + + @Override + public Saga process(Saga saga) { + Object inValue = saga.getCurrentValue(); + LOGGER.info("The chapter '{}' has been started. " + + "The data {} has been stored or calculated successfully", + getName(), inValue); + saga.setCurrentStatus(Saga.ChapterResult.SUCCESS); + saga.setCurrentValue(inValue); + return saga; + } + + @Override + public Saga rollback(Saga saga) { + Object inValue = saga.getCurrentValue(); + LOGGER.info("The Rollback for a chapter '{}' has been started. " + + "The data {} has been rollbacked successfully", + getName(), inValue); + + saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK); + saga.setCurrentValue(inValue); + return saga; + } + + private boolean isSagaFinished(Saga saga) { + if (!saga.isPresent()) { + saga.setFinished(true); + LOGGER.info(" the saga has been finished with {} status", saga.getResult()); + return true; + } + return false; + } + +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java b/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java new file mode 100644 index 000000000000..a616ff4a53b7 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java @@ -0,0 +1,61 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.choreography; + +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +/** + * The class representing a service discovery pattern. + */ +public class ServiceDiscoveryService { + private Map services; + + /** + * find any service. + * + * @return any service + * @throws NoSuchElementException if no elements further + */ + public ChoreographyChapter findAny() { + return services.values().iterator().next(); + } + + public Optional find(String service) { + return Optional.ofNullable(services.getOrDefault(service, null)); + } + + public ServiceDiscoveryService discover(ChoreographyChapter chapterService) { + services.put(chapterService.getName(), chapterService); + return this; + } + + public ServiceDiscoveryService() { + this.services = new HashMap<>(); + } + + +} diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/WithdrawMoneyService.java b/saga/src/main/java/com/iluwatar/saga/choreography/WithdrawMoneyService.java new file mode 100644 index 000000000000..637e8ac1bbf4 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/choreography/WithdrawMoneyService.java @@ -0,0 +1,53 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.choreography; + +/** + * Class representing a service to withdraw a money. + */ +public class WithdrawMoneyService extends Service { + + public WithdrawMoneyService(ServiceDiscoveryService service) { + super(service); + } + + @Override + public String getName() { + return "withdrawing Money"; + } + + @Override + public Saga process(Saga saga) { + Object inValue = saga.getCurrentValue(); + + if (inValue.equals("bad_order")) { + LOGGER.info("The chapter '{}' has been started. But the exception has been raised." + + "The rollback is about to start", + getName(), inValue); + saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK); + return saga; + } + return super.process(saga); + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java b/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java new file mode 100644 index 000000000000..ef34ddb987a5 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java @@ -0,0 +1,62 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.orchestration; + +/** + * Executing result for chapter. + * + * @param incoming value + */ +public class ChapterResult { + private K value; + private State state; + + public K getValue() { + return value; + } + + ChapterResult(K value, State state) { + this.value = value; + this.state = state; + } + + public boolean isSuccess() { + return state == State.SUCCESS; + } + + public static ChapterResult success(K val) { + return new ChapterResult<>(val, State.SUCCESS); + } + + public static ChapterResult failure(K val) { + return new ChapterResult<>(val, State.FAILURE); + } + + /** + * state for chapter. + */ + public enum State { + SUCCESS, FAILURE + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java new file mode 100644 index 000000000000..23b612957af3 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java @@ -0,0 +1,34 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.orchestration; + +/** + * Class representing a service to book a fly. + */ +public class FlyBookingService extends Service { + @Override + public String getName() { + return "booking a Fly"; + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java new file mode 100644 index 000000000000..2d6ba13899c3 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java @@ -0,0 +1,52 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.orchestration; + +/** + * Class representing a service to book a hotel. + */ +public class HotelBookingService extends Service { + @Override + public String getName() { + return "booking a Hotel"; + } + + + @Override + public ChapterResult rollback(String value) { + if (value.equals("crashed_order")) { + LOGGER.info("The Rollback for a chapter '{}' has been started. " + + "The data {} has been failed.The saga has been crashed.", + getName(), value); + + return ChapterResult.failure(value); + } + + LOGGER.info("The Rollback for a chapter '{}' has been started. " + + "The data {} has been rollbacked successfully", + getName(), value); + + return super.rollback(value); + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/OrchestrationChapter.java b/saga/src/main/java/com/iluwatar/saga/orchestration/OrchestrationChapter.java new file mode 100644 index 000000000000..7e9e3581f54d --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/OrchestrationChapter.java @@ -0,0 +1,55 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.orchestration; + +/** + * ChoreographyChapter is an interface representing a contract for an external service. + * + * @param is type for passing params + */ +public interface OrchestrationChapter { + + /** + * method get name. + * @return service name. + */ + String getName(); + + /** + * The operation executed in general case. + * + * @param value incoming value + * @return result {@link ChapterResult} + */ + ChapterResult process(K value); + + /** + * The operation executed in rollback case. + * + * @param value incoming value + * @return result {@link ChapterResult} + */ + ChapterResult rollback(K value); + +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java new file mode 100644 index 000000000000..c42a9d7a1c6c --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java @@ -0,0 +1,34 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.orchestration; + +/** + * Class representing a service to init a new order. + */ +public class OrderService extends Service { + @Override + public String getName() { + return "init an order"; + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java b/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java new file mode 100644 index 000000000000..0d53362aa8e2 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java @@ -0,0 +1,84 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.orchestration; + +import java.util.ArrayList; +import java.util.List; + +/** + * Saga representation. + * Saga consists of chapters. + * Every ChoreographyChapter is executed by a certain service. + */ +public class Saga { + + private List chapters; + + + private Saga() { + this.chapters = new ArrayList<>(); + } + + + public Saga chapter(String name) { + this.chapters.add(new Chapter(name)); + return this; + } + + + public Chapter get(int idx) { + return chapters.get(idx); + } + + public boolean isPresent(int idx) { + return idx >= 0 && idx < chapters.size(); + } + + + public static Saga create() { + return new Saga(); + } + + /** + * result for saga. + */ + public enum Result { + FINISHED, ROLLBACK, CRASHED + } + + /** + * class represents chapter name. + */ + public static class Chapter { + String name; + + public Chapter(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java new file mode 100644 index 000000000000..830f5e653cce --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java @@ -0,0 +1,87 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.orchestration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This pattern is used in distributed services to perform + * a group of operations atomically. + * This is an analog of transaction in a database but in terms + * of microservices architecture this is executed + * in a distributed environment + * + *

    A saga is a sequence of local transactions in a certain context. + * If one transaction fails for some reason, + * the saga executes compensating transactions(rollbacks) + * to undo the impact of the preceding transactions. + * + *

    In this approach, there is an orchestrator @see {@link SagaOrchestrator} + * that manages all the transactions and directs + * the participant services to execute local transactions based on events. + * The major difference with choreography saga is an ability to handle crashed services + * (otherwise in choreography services very hard to prevent a saga + * if one of them has been crashed) + * + * @see Saga + * @see SagaOrchestrator + * @see Service + */ +public class SagaApplication { + private static final Logger LOGGER = LoggerFactory.getLogger(SagaApplication.class); + + /** + * method to show common saga logic. + */ + public static void main(String[] args) { + SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery()); + + Saga.Result goodOrder = sagaOrchestrator.execute("good_order"); + Saga.Result badOrder = sagaOrchestrator.execute("bad_order"); + Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_order"); + + LOGGER.info("orders: goodOrder is {}, badOrder is {},crashedOrder is {}", + goodOrder, badOrder, crashedOrder); + } + + + private static Saga newSaga() { + return Saga + .create() + .chapter("init an order") + .chapter("booking a Fly") + .chapter("booking a Hotel") + .chapter("withdrawing Money"); + } + + private static ServiceDiscoveryService serviceDiscovery() { + return + new ServiceDiscoveryService() + .discover(new OrderService()) + .discover(new FlyBookingService()) + .discover(new HotelBookingService()) + .discover(new WithdrawMoneyService()); + } +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java new file mode 100644 index 000000000000..beec37655534 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java @@ -0,0 +1,148 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.orchestration; + +import static com.iluwatar.saga.orchestration.Saga.Result.CRASHED; +import static com.iluwatar.saga.orchestration.Saga.Result.FINISHED; +import static com.iluwatar.saga.orchestration.Saga.Result.ROLLBACK; + +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The orchestrator that manages all the transactions and directs + * the participant services to execute local transactions based on events. + */ +public class SagaOrchestrator { + private static final Logger LOGGER = LoggerFactory.getLogger(SagaOrchestrator.class); + private final Saga saga; + private final ServiceDiscoveryService sd; + private final CurrentState state; + + + /** + * Create a new service to orchetrate sagas. + * @param saga saga to process + * @param sd service discovery @see {@link ServiceDiscoveryService} + */ + public SagaOrchestrator(Saga saga, ServiceDiscoveryService sd) { + this.saga = saga; + this.sd = sd; + this.state = new CurrentState(); + } + + /** + * pipeline to execute saga process/story. + * + * @param value incoming value + * @param type for incoming value + * @return result @see {@link Saga.Result} + */ + @SuppressWarnings("unchecked") + public Saga.Result execute(K value) { + state.cleanUp(); + LOGGER.info(" The new saga is about to start"); + Saga.Result result = FINISHED; + K tempVal = value; + + while (true) { + int next = state.current(); + Saga.Chapter ch = saga.get(next); + Optional srvOpt = sd.find(ch.name); + + if (!srvOpt.isPresent()) { + state.directionToBack(); + state.back(); + continue; + } + + OrchestrationChapter srv = srvOpt.get(); + + if (state.isForward()) { + ChapterResult processRes = srv.process(tempVal); + if (processRes.isSuccess()) { + next = state.forward(); + tempVal = (K) processRes.getValue(); + } else { + state.directionToBack(); + } + } else { + ChapterResult rlRes = srv.rollback(tempVal); + if (rlRes.isSuccess()) { + next = state.back(); + tempVal = (K) rlRes.getValue(); + } else { + result = CRASHED; + next = state.back(); + } + } + + + if (!saga.isPresent(next)) { + return state.isForward() ? FINISHED : result == CRASHED ? CRASHED : ROLLBACK; + } + } + + } + + + private static class CurrentState { + int currentNumber; + boolean isForward; + + void cleanUp() { + currentNumber = 0; + isForward = true; + } + + CurrentState() { + this.currentNumber = 0; + this.isForward = true; + } + + + boolean isForward() { + return isForward; + } + + void directionToBack() { + isForward = false; + } + + int forward() { + return ++currentNumber; + } + + int back() { + return --currentNumber; + } + + int current() { + return currentNumber; + } + } + +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java b/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java new file mode 100644 index 000000000000..d2b0652015c1 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java @@ -0,0 +1,59 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.orchestration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Common abstraction class representing services. + * implementing a general contract @see {@link OrchestrationChapter} + * + * @param type of incoming param + */ +public abstract class Service implements OrchestrationChapter { + protected static final Logger LOGGER = LoggerFactory.getLogger(Service.class); + + @Override + public abstract String getName(); + + + @Override + public ChapterResult process(K value) { + LOGGER.info("The chapter '{}' has been started. " + + "The data {} has been stored or calculated successfully", + getName(), value); + return ChapterResult.success(value); + } + + @Override + public ChapterResult rollback(K value) { + LOGGER.info("The Rollback for a chapter '{}' has been started. " + + "The data {} has been rollbacked successfully", + getName(), value); + return ChapterResult.success(value); + } + + +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java new file mode 100644 index 000000000000..dbc6e7eb5ac9 --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java @@ -0,0 +1,50 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.orchestration; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * The class representing a service discovery pattern. + */ +public class ServiceDiscoveryService { + private Map> services; + + public Optional find(String service) { + return Optional.ofNullable(services.getOrDefault(service, null)); + } + + public ServiceDiscoveryService discover(OrchestrationChapter orchestrationChapterService) { + services.put(orchestrationChapterService.getName(), orchestrationChapterService); + return this; + } + + public ServiceDiscoveryService() { + this.services = new HashMap<>(); + } + + +} diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java new file mode 100644 index 000000000000..7eb5634efece --- /dev/null +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java @@ -0,0 +1,45 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.saga.orchestration; + +/** + * Class representing a service to withdraw a money. + */ +public class WithdrawMoneyService extends Service { + @Override + public String getName() { + return "withdrawing Money"; + } + + @Override + public ChapterResult process(String value) { + if (value.equals("bad_order") || value.equals("crashed_order")) { + LOGGER.info("The chapter '{}' has been started. But the exception has been raised." + + "The rollback is about to start", + getName(), value); + return ChapterResult.failure(value); + } + return super.process(value); + } +} diff --git a/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java b/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java new file mode 100644 index 000000000000..d7a2a34b2787 --- /dev/null +++ b/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java @@ -0,0 +1,37 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.saga.choreography; + +import com.iluwatar.saga.orchestration.SagaApplication; +import org.junit.Test; + + +/*** + * empty test + */ +public class SagaApplicationTest { + @Test + public void mainTest() { + SagaApplication.main(new String[]{}); + } +} \ No newline at end of file diff --git a/saga/src/test/java/com/iluwatar/saga/choreography/SagaChoreographyTest.java b/saga/src/test/java/com/iluwatar/saga/choreography/SagaChoreographyTest.java new file mode 100644 index 000000000000..4300d4057780 --- /dev/null +++ b/saga/src/test/java/com/iluwatar/saga/choreography/SagaChoreographyTest.java @@ -0,0 +1,62 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.saga.choreography; + +import org.junit.Assert; +import org.junit.Test; + +/** + * test to check choreography saga + */ +public class SagaChoreographyTest { + + + @Test + public void executeTest() { + ServiceDiscoveryService sd = serviceDiscovery(); + ChoreographyChapter service = sd.findAny(); + Saga badOrderSaga = service.execute(newSaga("bad_order")); + Saga goodOrderSaga = service.execute(newSaga("good_order")); + + Assert.assertEquals(badOrderSaga.getResult(), Saga.SagaResult.ROLLBACKED); + Assert.assertEquals(goodOrderSaga.getResult(), Saga.SagaResult.FINISHED); + } + + private static Saga newSaga(Object value) { + return Saga + .create() + .chapter("init an order").setInValue(value) + .chapter("booking a Fly") + .chapter("booking a Hotel") + .chapter("withdrawing Money"); + } + + private static ServiceDiscoveryService serviceDiscovery() { + ServiceDiscoveryService sd = new ServiceDiscoveryService(); + return sd + .discover(new OrderService(sd)) + .discover(new FlyBookingService(sd)) + .discover(new HotelBookingService(sd)) + .discover(new WithdrawMoneyService(sd)); + } +} diff --git a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java new file mode 100644 index 000000000000..aa3c8773f46a --- /dev/null +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java @@ -0,0 +1,38 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.saga.orchestration; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * empty test + */ +public class SagaApplicationTest { + + @Test + public void mainTest() { + SagaApplication.main(new String[]{}); + } +} \ No newline at end of file diff --git a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java new file mode 100644 index 000000000000..a93bf5280cb6 --- /dev/null +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java @@ -0,0 +1,145 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.saga.orchestration; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * test to test orchestration logic + */ +public class SagaOrchestratorInternallyTest { + + private List records = new ArrayList<>(); + + @Test + public void executeTest() { + SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery()); + Saga.Result result = sagaOrchestrator.execute(1); + Assert.assertEquals(result, Saga.Result.ROLLBACK); + Assert.assertArrayEquals( + records.toArray(new String[]{}), + new String[]{"+1", "+2", "+3", "+4", "-4", "-3", "-2", "-1"}); + } + + private static Saga newSaga() { + return Saga + .create() + .chapter("1") + .chapter("2") + .chapter("3") + .chapter("4"); + } + + private ServiceDiscoveryService serviceDiscovery() { + return + new ServiceDiscoveryService() + .discover(new Service1()) + .discover(new Service2()) + .discover(new Service3()) + .discover(new Service4()); + } + + class Service1 extends Service { + + @Override + public String getName() { + return "1"; + } + + @Override + public ChapterResult process(Integer value) { + records.add("+1"); + return ChapterResult.success(value); + } + + @Override + public ChapterResult rollback(Integer value) { + records.add("-1"); + return ChapterResult.success(value); + } + } + + class Service2 extends Service { + + @Override + public String getName() { + return "2"; + } + + @Override + public ChapterResult process(Integer value) { + records.add("+2"); + return ChapterResult.success(value); + } + + @Override + public ChapterResult rollback(Integer value) { + records.add("-2"); + return ChapterResult.success(value); + } + } + + class Service3 extends Service { + + @Override + public String getName() { + return "3"; + } + + @Override + public ChapterResult process(Integer value) { + records.add("+3"); + return ChapterResult.success(value); + } + + @Override + public ChapterResult rollback(Integer value) { + records.add("-3"); + return ChapterResult.success(value); + } + } + + class Service4 extends Service { + + @Override + public String getName() { + return "4"; + } + + @Override + public ChapterResult process(Integer value) { + records.add("+4"); + return ChapterResult.failure(value); + } + + @Override + public ChapterResult rollback(Integer value) { + records.add("-4"); + return ChapterResult.success(value); + } + } +} \ No newline at end of file diff --git a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java new file mode 100644 index 000000000000..d3522418c346 --- /dev/null +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java @@ -0,0 +1,60 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.saga.orchestration; + +import org.junit.Assert; +import org.junit.Test; + +/** + * test to check general logic + */ +public class SagaOrchestratorTest { + + @Test + public void execute() { + SagaOrchestrator sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery()); + Saga.Result badOrder = sagaOrchestrator.execute("bad_order"); + Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_order"); + + Assert.assertEquals(badOrder, Saga.Result.ROLLBACK); + Assert.assertEquals(crashedOrder, Saga.Result.CRASHED); + } + + private static Saga newSaga() { + return Saga + .create() + .chapter("init an order") + .chapter("booking a Fly") + .chapter("booking a Hotel") + .chapter("withdrawing Money"); + } + + private static ServiceDiscoveryService serviceDiscovery() { + return + new ServiceDiscoveryService() + .discover(new OrderService()) + .discover(new FlyBookingService()) + .discover(new HotelBookingService()) + .discover(new WithdrawMoneyService()); + } +} \ No newline at end of file diff --git a/semaphore/README.md b/semaphore/README.md index 46ccd7b8ea22..be882e04a781 100644 --- a/semaphore/README.md +++ b/semaphore/README.md @@ -5,8 +5,7 @@ folder: semaphore permalink: /patterns/semaphore/ categories: Concurrency tags: - - Java - - Difficulty-Intermediate + - Performance --- ## Also known as @@ -19,13 +18,14 @@ of the semaphore, can access the resources at any given time. A semaphore which only allows one concurrent access to a resource is called a binary semaphore. +## Class diagram ![alt text](./etc/semaphore.png "Semaphore") ## Applicability Use a Semaphore when -* you have a pool of resources to allocate to different threads -* concurrent access to a resource could lead to a race condition +* You have a pool of resources to allocate to different threads +* Concurrent access to a resource could lead to a race condition ## Credits diff --git a/semaphore/etc/semaphore.urm.puml b/semaphore/etc/semaphore.urm.puml new file mode 100644 index 000000000000..168fd17e711a --- /dev/null +++ b/semaphore/etc/semaphore.urm.puml @@ -0,0 +1,56 @@ +@startuml +package com.iluwatar.semaphore { + class App { + + App() + + main(args : String[]) {static} + } + class Fruit { + - type : FruitType + + Fruit(type : FruitType) + + getType() : FruitType + + toString() : String + } + enum FruitType { + + APPLE {static} + + LEMON {static} + + ORANGE {static} + + valueOf(name : String) : FruitType {static} + + values() : FruitType[] {static} + } + class FruitBowl { + - fruit : List + + FruitBowl() + + countFruit() : int + + put(f : Fruit) + + take() : Fruit + + toString() : String + } + class FruitShop { + - available : boolean[] + - bowls : FruitBowl[] + - semaphore : Semaphore + + FruitShop() + + countFruit() : int + + returnBowl(bowl : FruitBowl) + + takeBowl() : FruitBowl + } + interface Lock { + + acquire() {abstract} + + release() {abstract} + } + class Semaphore { + - counter : int + - licenses : int + + Semaphore(licenses : int) + + acquire() + + getAvailableLicenses() : int + + getNumLicenses() : int + + release() + } +} +FruitType ..+ Fruit +Fruit --> "-type" FruitType +FruitShop --> "-semaphore" Semaphore +FruitBowl --> "-fruit" Fruit +Semaphore ..|> Lock +@enduml \ No newline at end of file diff --git a/semaphore/pom.xml b/semaphore/pom.xml index c2c2d19718b4..2684289981fa 100644 --- a/semaphore/pom.xml +++ b/semaphore/pom.xml @@ -2,7 +2,7 @@ "-jenkins" Servant +King ..|> Royalty +Queen ..|> Royalty +@enduml \ No newline at end of file diff --git a/servant/pom.xml b/servant/pom.xml index 11bfeddd4f5a..db5abe580025 100644 --- a/servant/pom.xml +++ b/servant/pom.xml @@ -2,7 +2,7 @@ "-address" Address +Access ..+ JsonProperty +FindPersonApiHandler --|> AbstractDynamoDbHandler +SavePersonApiHandler --|> AbstractDynamoDbHandler +@enduml \ No newline at end of file diff --git a/serverless/pom.xml b/serverless/pom.xml index f3eea887165e..1fb55ec47536 100644 --- a/serverless/pom.xml +++ b/serverless/pom.xml @@ -1,15 +1,19 @@ com.iluwatar java-design-patterns - 1.21.0-SNAPSHOT + 1.23.0-SNAPSHOT @@ -55,11 +60,6 @@ aws-lambda-java-events ${aws-lambda-java-events.version} - - com.amazonaws - aws-lambda-java-log4j - ${aws-lambda-log4j.version} - com.fasterxml.jackson.core jackson-core @@ -75,11 +75,6 @@ jackson-annotations ${jackson.version} - - org.junit.jupiter - junit-jupiter-api - test - org.junit.jupiter junit-jupiter-engine @@ -130,4 +125,4 @@ - \ No newline at end of file + diff --git a/serverless/serverless.yml b/serverless/serverless.yml index c463395df226..bbf9a71c47c2 100644 --- a/serverless/serverless.yml +++ b/serverless/serverless.yml @@ -1,6 +1,6 @@ # # The MIT License -# Copyright (c) 2014 Ilkka Seppälä +# Copyright © 2014-2019 Ilkka Seppälä # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/serverless/src/main/java/com/iluwatar/serverless/baas/api/AbstractDynamoDbHandler.java b/serverless/src/main/java/com/iluwatar/serverless/baas/api/AbstractDynamoDbHandler.java index ac27a9a4a4b7..43465f529dd9 100644 --- a/serverless/src/main/java/com/iluwatar/serverless/baas/api/AbstractDynamoDbHandler.java +++ b/serverless/src/main/java/com/iluwatar/serverless/baas/api/AbstractDynamoDbHandler.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.serverless.baas.api; import com.amazonaws.regions.Regions; @@ -29,13 +30,13 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; - import java.io.Serializable; import java.util.HashMap; import java.util.Map; /** - * abstract dynamodb handler + * abstract dynamodb handler. + * * @param - serializable collection */ public abstract class AbstractDynamoDbHandler { @@ -77,10 +78,10 @@ protected Map headers() { } /** - * API Gateway response + * API Gateway response. * * @param statusCode - status code - * @param body - Object body + * @param body - Object body * @return - api gateway proxy response */ protected APIGatewayProxyResponseEvent apiGatewayProxyResponseEvent(Integer statusCode, T body) { diff --git a/serverless/src/main/java/com/iluwatar/serverless/baas/api/FindPersonApiHandler.java b/serverless/src/main/java/com/iluwatar/serverless/baas/api/FindPersonApiHandler.java index fab448d5c2dd..11066c744521 100644 --- a/serverless/src/main/java/com/iluwatar/serverless/baas/api/FindPersonApiHandler.java +++ b/serverless/src/main/java/com/iluwatar/serverless/baas/api/FindPersonApiHandler.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.serverless.baas.api; import com.amazonaws.services.lambda.runtime.Context; @@ -27,22 +28,26 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import com.iluwatar.serverless.baas.model.Person; -import org.apache.log4j.Logger; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * find person from persons collection - * Created by dheeraj.mummar on 3/5/18. + * find person from persons collection Created by dheeraj.mummar on 3/5/18. */ public class FindPersonApiHandler extends AbstractDynamoDbHandler implements RequestHandler { - private static final Logger LOG = Logger.getLogger(FindPersonApiHandler.class); + private static final Logger LOG = LoggerFactory.getLogger(FindPersonApiHandler.class); private static final Integer SUCCESS_STATUS_CODE = 200; @Override - public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent apiGatewayProxyRequestEvent, - Context context) { - LOG.info(apiGatewayProxyRequestEvent.getPathParameters()); + public APIGatewayProxyResponseEvent handleRequest( + APIGatewayProxyRequestEvent apiGatewayProxyRequestEvent, Context context) { + Map pathParameters = apiGatewayProxyRequestEvent.getPathParameters(); + pathParameters.keySet().stream().map(key -> key + "=" + pathParameters.get(key)) + .forEach(LOG::info); + Person person = this.getDynamoDbMapper().load(Person.class, apiGatewayProxyRequestEvent .getPathParameters().get("id")); diff --git a/serverless/src/main/java/com/iluwatar/serverless/baas/api/SavePersonApiHandler.java b/serverless/src/main/java/com/iluwatar/serverless/baas/api/SavePersonApiHandler.java index b66b997d2ec7..0bc121853373 100644 --- a/serverless/src/main/java/com/iluwatar/serverless/baas/api/SavePersonApiHandler.java +++ b/serverless/src/main/java/com/iluwatar/serverless/baas/api/SavePersonApiHandler.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.serverless.baas.api; import com.amazonaws.services.lambda.runtime.Context; @@ -27,24 +28,23 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import com.iluwatar.serverless.baas.model.Person; -import org.apache.log4j.Logger; - import java.io.IOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * save person into persons collection - * Created by dheeraj.mummar on 3/4/18. + * save person into persons collection Created by dheeraj.mummar on 3/4/18. */ public class SavePersonApiHandler extends AbstractDynamoDbHandler implements RequestHandler { - private static final Logger LOG = Logger.getLogger(SavePersonApiHandler.class); + private static final Logger LOG = LoggerFactory.getLogger(SavePersonApiHandler.class); private static final Integer CREATED_STATUS_CODE = 201; private static final Integer BAD_REQUEST_STATUS_CODE = 400; @Override - public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent - apiGatewayProxyRequestEvent, Context context) { + public APIGatewayProxyResponseEvent handleRequest( + APIGatewayProxyRequestEvent apiGatewayProxyRequestEvent, Context context) { APIGatewayProxyResponseEvent apiGatewayProxyResponseEvent; Person person; try { diff --git a/serverless/src/main/java/com/iluwatar/serverless/baas/model/Address.java b/serverless/src/main/java/com/iluwatar/serverless/baas/model/Address.java index 7331cea2b5c9..710cde531d76 100644 --- a/serverless/src/main/java/com/iluwatar/serverless/baas/model/Address.java +++ b/serverless/src/main/java/com/iluwatar/serverless/baas/model/Address.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,16 +20,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.serverless.baas.model; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBDocument; - import java.io.Serializable; /** - * Address class - * Created by dheeraj.mummarareddy on 3/4/18. + * Address class Created by dheeraj.mummarareddy on 3/4/18. */ @DynamoDBDocument public class Address implements Serializable { diff --git a/serverless/src/main/java/com/iluwatar/serverless/baas/model/Person.java b/serverless/src/main/java/com/iluwatar/serverless/baas/model/Person.java index 75604999ea34..3c97aae11429 100644 --- a/serverless/src/main/java/com/iluwatar/serverless/baas/model/Person.java +++ b/serverless/src/main/java/com/iluwatar/serverless/baas/model/Person.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.serverless.baas.model; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; @@ -27,12 +28,10 @@ import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; import com.fasterxml.jackson.annotation.JsonProperty; - import java.io.Serializable; /** - * Person class - * Created by dheeraj.mummarareddy on 3/4/18. + * Person class Created by dheeraj.mummarareddy on 3/4/18. */ @DynamoDBTable(tableName = "persons") public class Person implements Serializable { @@ -106,7 +105,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = firstName != null ? firstName.hashCode() : 0; + var result = firstName != null ? firstName.hashCode() : 0; result = 31 * result + (lastName != null ? lastName.hashCode() : 0); result = 31 * result + (address != null ? address.hashCode() : 0); return result; diff --git a/serverless/src/main/java/com/iluwatar/serverless/faas/ApiGatewayResponse.java b/serverless/src/main/java/com/iluwatar/serverless/faas/ApiGatewayResponse.java index 98e2de64fe3c..94fe7b4f9a51 100644 --- a/serverless/src/main/java/com/iluwatar/serverless/faas/ApiGatewayResponse.java +++ b/serverless/src/main/java/com/iluwatar/serverless/faas/ApiGatewayResponse.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,16 +20,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.serverless.faas; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; - import java.io.Serializable; import java.util.Map; /** - * Api gateway response + * Api gateway response. * * @param serializable object */ @@ -43,11 +43,11 @@ public class ApiGatewayResponse implements Serializable private final Boolean isBase64Encoded; /** - * api gateway response + * api gateway response. * - * @param statusCode - http status code - * @param body - response body - * @param headers - response headers + * @param statusCode - http status code + * @param body - response body + * @param headers - response headers * @param isBase64Encoded - base64Encoded flag */ public ApiGatewayResponse(Integer statusCode, String body, Map headers, @@ -59,7 +59,7 @@ public ApiGatewayResponse(Integer statusCode, String body, Map h } /** - * http status code + * http status code. * * @return statusCode - http status code */ @@ -68,7 +68,7 @@ public Integer getStatusCode() { } /** - * response body + * response body. * * @return string body */ @@ -77,7 +77,7 @@ public String getBody() { } /** - * response headers + * response headers. * * @return response headers */ @@ -86,7 +86,7 @@ public Map getHeaders() { } /** - * base64Encoded flag, API Gateway expects the property to be called "isBase64Encoded" + * base64Encoded flag, API Gateway expects the property to be called "isBase64Encoded". * * @return base64Encoded flag */ @@ -95,7 +95,8 @@ public Boolean isBase64Encoded() { } /** - * ApiGatewayResponse Builder class + * ApiGatewayResponse Builder class. + * * @param Serializable object */ public static class ApiGatewayResponseBuilder { @@ -106,7 +107,8 @@ public static class ApiGatewayResponseBuilder { private Boolean isBase64Encoded; /** - * http status code + * http status code. + * * @param statusCode - http status code * @return ApiGatewayResponseBuilder */ @@ -116,7 +118,8 @@ public ApiGatewayResponseBuilder statusCode(Integer statusCode) { } /** - * Serializable body + * Serializable body. + * * @param body - Serializable object * @return ApiGatewayResponseBuilder */ @@ -126,7 +129,8 @@ public ApiGatewayResponseBuilder body(T body) { } /** - * response headers + * response headers. + * * @param headers - response headers * @return ApiGatewayResponseBuilder */ @@ -136,7 +140,8 @@ public ApiGatewayResponseBuilder headers(Map headers) { } /** - * base64Encoded glag + * base64Encoded flag. + * * @param isBase64Encoded - base64Encoded flag * @return ApiGatewayResponseBuilder */ @@ -146,7 +151,7 @@ public ApiGatewayResponseBuilder base64Encoded(Boolean isBase64Encoded) { } /** - * build ApiGatewayResponse + * build ApiGatewayResponse. * * @return ApiGatewayResponse */ diff --git a/serverless/src/main/java/com/iluwatar/serverless/faas/LambdaInfo.java b/serverless/src/main/java/com/iluwatar/serverless/faas/LambdaInfo.java index 03932a9f0143..7cb93ebf4a6b 100644 --- a/serverless/src/main/java/com/iluwatar/serverless/faas/LambdaInfo.java +++ b/serverless/src/main/java/com/iluwatar/serverless/faas/LambdaInfo.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +20,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.serverless.faas; import java.io.Serializable; /** - * Lambda context information + * Lambda context information. */ public class LambdaInfo implements Serializable { @@ -109,27 +110,33 @@ public boolean equals(Object o) { LambdaInfo that = (LambdaInfo) o; - if (awsRequestId != null ? !awsRequestId.equals(that.awsRequestId) : that.awsRequestId != null) { + if (awsRequestId != null ? !awsRequestId + .equals(that.awsRequestId) : that.awsRequestId != null) { return false; } - if (logGroupName != null ? !logGroupName.equals(that.logGroupName) : that.logGroupName != null) { + if (logGroupName != null ? !logGroupName + .equals(that.logGroupName) : that.logGroupName != null) { return false; } - if (logStreamName != null ? !logStreamName.equals(that.logStreamName) : that.logStreamName != null) { + if (logStreamName != null ? !logStreamName + .equals(that.logStreamName) : that.logStreamName != null) { return false; } - if (functionName != null ? !functionName.equals(that.functionName) : that.functionName != null) { + if (functionName != null ? !functionName + .equals(that.functionName) : that.functionName != null) { return false; } - if (functionVersion != null ? !functionVersion.equals(that.functionVersion) : that.functionVersion != null) { + if (functionVersion != null ? !functionVersion + .equals(that.functionVersion) : that.functionVersion != null) { return false; } - return memoryLimitInMb != null ? memoryLimitInMb.equals(that.memoryLimitInMb) : that.memoryLimitInMb == null; + return memoryLimitInMb != null ? memoryLimitInMb + .equals(that.memoryLimitInMb) : that.memoryLimitInMb == null; } @Override public int hashCode() { - int result = awsRequestId != null ? awsRequestId.hashCode() : 0; + var result = awsRequestId != null ? awsRequestId.hashCode() : 0; result = 31 * result + (logGroupName != null ? logGroupName.hashCode() : 0); result = 31 * result + (logStreamName != null ? logStreamName.hashCode() : 0); result = 31 * result + (functionName != null ? functionName.hashCode() : 0); diff --git a/serverless/src/main/java/com/iluwatar/serverless/faas/api/LambdaInfoApiHandler.java b/serverless/src/main/java/com/iluwatar/serverless/faas/api/LambdaInfoApiHandler.java index 67e1835f9e13..a16358a54328 100644 --- a/serverless/src/main/java/com/iluwatar/serverless/faas/api/LambdaInfoApiHandler.java +++ b/serverless/src/main/java/com/iluwatar/serverless/faas/api/LambdaInfoApiHandler.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,50 +20,49 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package com.iluwatar.serverless.faas.api; -import com.iluwatar.serverless.faas.ApiGatewayResponse; +package com.iluwatar.serverless.faas.api; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.iluwatar.serverless.faas.ApiGatewayResponse; import com.iluwatar.serverless.faas.LambdaInfo; -import org.apache.log4j.BasicConfigurator; -import org.apache.log4j.Logger; - import java.util.HashMap; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * LambdaInfoApiHandler - simple api to get lambda context - * Created by dheeraj.mummar on 2/5/18. + * LambdaInfoApiHandler - simple api to get lambda context Created by dheeraj.mummar on 2/5/18. */ -public class LambdaInfoApiHandler implements RequestHandler, ApiGatewayResponse> { +public class LambdaInfoApiHandler + implements RequestHandler, ApiGatewayResponse> { - private static final Logger LOG = Logger.getLogger(LambdaInfoApiHandler.class); + private static final Logger LOG = LoggerFactory.getLogger(LambdaInfoApiHandler.class); private static final Integer SUCCESS_STATUS_CODE = 200; @Override public ApiGatewayResponse handleRequest(Map input, Context context) { - BasicConfigurator.configure(); LOG.info("received: " + input); return new ApiGatewayResponse - .ApiGatewayResponseBuilder() - .headers(headers()) - .statusCode(SUCCESS_STATUS_CODE) - .body(lambdaInfo(context)) - .build(); + .ApiGatewayResponseBuilder() + .headers(headers()) + .statusCode(SUCCESS_STATUS_CODE) + .body(lambdaInfo(context)) + .build(); } /** - * lambdaInfo + * lambdaInfo. + * * @param context - Lambda context * @return LambdaInfo */ private LambdaInfo lambdaInfo(Context context) { - LambdaInfo lambdaInfo = new LambdaInfo(); + var lambdaInfo = new LambdaInfo(); lambdaInfo.setAwsRequestId(context.getAwsRequestId()); lambdaInfo.setFunctionName(context.getFunctionName()); lambdaInfo.setFunctionVersion(context.getFunctionVersion()); @@ -75,7 +74,7 @@ private LambdaInfo lambdaInfo(Context context) { } private Map headers() { - Map headers = new HashMap<>(); + var headers = new HashMap(); headers.put("Content-Type", "application/json"); return headers; diff --git a/serverless/src/main/resources/log4j.properties b/serverless/src/main/resources/log4j.properties deleted file mode 100644 index 15a25e61b87a..000000000000 --- a/serverless/src/main/resources/log4j.properties +++ /dev/null @@ -1,29 +0,0 @@ -# -# The MIT License -# Copyright (c) 2014 Ilkka Seppälä -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# - -log = . -log4j.rootLogger = DEBUG, LAMBDA - -log4j.appender.LAMBDA=com.amazonaws.services.lambda.runtime.log4j.LambdaAppender -log4j.appender.LAMBDA.layout=org.apache.log4j.PatternLayout -log4j.appender.LAMBDA.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss} <%X{AWSRequestId}> %-5p %c:%L - %m%n diff --git a/serverless/src/test/java/com/iluwatar/serverless/baas/api/FindPersonApiHandlerTest.java b/serverless/src/test/java/com/iluwatar/serverless/baas/api/FindPersonApiHandlerTest.java index 561281ab3b58..a688bdf9d0ac 100644 --- a/serverless/src/test/java/com/iluwatar/serverless/baas/api/FindPersonApiHandlerTest.java +++ b/serverless/src/test/java/com/iluwatar/serverless/baas/api/FindPersonApiHandlerTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.serverless.baas.api; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; diff --git a/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java b/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java index 83008bdff309..66636c011458 100644 --- a/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java +++ b/serverless/src/test/java/com/iluwatar/serverless/baas/api/SavePersonApiHandlerTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.serverless.baas.api; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; diff --git a/serverless/src/test/java/com/iluwatar/serverless/faas/api/LambdaInfoApiHandlerTest.java b/serverless/src/test/java/com/iluwatar/serverless/faas/api/LambdaInfoApiHandlerTest.java index 876489ed2262..ec6872973449 100644 --- a/serverless/src/test/java/com/iluwatar/serverless/faas/api/LambdaInfoApiHandlerTest.java +++ b/serverless/src/test/java/com/iluwatar/serverless/faas/api/LambdaInfoApiHandlerTest.java @@ -1,17 +1,17 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä - *

    + * Copyright © 2014-2019 Ilkka Seppälä + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - *

    + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - *

    + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.serverless.faas.api; import com.amazonaws.services.lambda.runtime.Context; diff --git a/service-layer/README.md b/service-layer/README.md index 9b685d4e3f4b..b2b61a17a466 100644 --- a/service-layer/README.md +++ b/service-layer/README.md @@ -5,8 +5,7 @@ folder: service-layer permalink: /patterns/service-layer/ categories: Architectural tags: - - Java - - Difficulty-Intermediate + - Data access --- ## Intent @@ -17,13 +16,14 @@ others. Despite their different purposes, these interfaces often need common interactions with the application to access and manipulate its data and invoke its business logic. The Service Layer fulfills this role. +## Class diagram ![alt text](./etc/service-layer.png "Service Layer") ## Applicability Use the Service Layer pattern when -* you want to encapsulate domain logic under API -* you need to implement multiple interfaces with common logic and data +* You want to encapsulate domain logic under API +* You need to implement multiple interfaces with common logic and data ## Credits diff --git a/service-layer/etc/service-layer.urm.puml b/service-layer/etc/service-layer.urm.puml new file mode 100644 index 000000000000..e663e6c4a678 --- /dev/null +++ b/service-layer/etc/service-layer.urm.puml @@ -0,0 +1,159 @@ +@startuml +package com.iluwatar.servicelayer.hibernate { + class HibernateUtil { + - LOGGER : Logger {static} + - sessionFactory : SessionFactory {static} + - HibernateUtil() + + dropSession() {static} + + getSessionFactory() : SessionFactory {static} + } +} +package com.iluwatar.servicelayer.common { + abstract class BaseEntity { + + BaseEntity() + + getId() : Long {abstract} + + getName() : String {abstract} + + setId(Long) {abstract} + + setName(String) {abstract} + } + interface Dao { + + delete(E extends BaseEntity) {abstract} + + find(Long) : E extends BaseEntity {abstract} + + findAll() : List {abstract} + + merge(E extends BaseEntity) : E extends BaseEntity {abstract} + + persist(E extends BaseEntity) {abstract} + } + abstract class DaoBaseImpl { + # persistentClass : Class + + DaoBaseImpl() + + delete(entity : E extends BaseEntity) + + find(id : Long) : E extends BaseEntity + + findAll() : List + # getSessionFactory() : SessionFactory + + merge(entity : E extends BaseEntity) : E extends BaseEntity + + persist(entity : E extends BaseEntity) + } +} +package com.iluwatar.servicelayer.magic { + interface MagicService { + + findAllSpellbooks() : List {abstract} + + findAllSpells() : List {abstract} + + findAllWizards() : List {abstract} + + findWizardsWithSpell(String) : List {abstract} + + findWizardsWithSpellbook(String) : List {abstract} + } + class MagicServiceImpl { + - spellDao : SpellDao + - spellbookDao : SpellbookDao + - wizardDao : WizardDao + + MagicServiceImpl(wizardDao : WizardDao, spellbookDao : SpellbookDao, spellDao : SpellDao) + + findAllSpellbooks() : List + + findAllSpells() : List + + findAllWizards() : List + + findWizardsWithSpell(name : String) : List + + findWizardsWithSpellbook(name : String) : List + } +} +package com.iluwatar.servicelayer.wizard { + class Wizard { + - id : Long + - name : String + - spellbooks : Set + + Wizard() + + Wizard(name : String) + + addSpellbook(spellbook : Spellbook) + + getId() : Long + + getName() : String + + getSpellbooks() : Set + + setId(id : Long) + + setName(name : String) + + setSpellbooks(spellbooks : Set) + + toString() : String + } + interface WizardDao { + + findByName(String) : Wizard {abstract} + } + class WizardDaoImpl { + + WizardDaoImpl() + + findByName(name : String) : Wizard + } +} +package com.iluwatar.servicelayer.spellbook { + class Spellbook { + - id : Long + - name : String + - spells : Set + - wizards : Set + + Spellbook() + + Spellbook(name : String) + + addSpell(spell : Spell) + + getId() : Long + + getName() : String + + getSpells() : Set + + getWizards() : Set + + setId(id : Long) + + setName(name : String) + + setSpells(spells : Set) + + setWizards(wizards : Set) + + toString() : String + } + interface SpellbookDao { + + findByName(String) : Spellbook {abstract} + } + class SpellbookDaoImpl { + + SpellbookDaoImpl() + + findByName(name : String) : Spellbook + } +} +package com.iluwatar.servicelayer.spell { + class Spell { + - id : Long + - name : String + - spellbook : Spellbook + + Spell() + + Spell(name : String) + + getId() : Long + + getName() : String + + getSpellbook() : Spellbook + + setId(id : Long) + + setName(name : String) + + setSpellbook(spellbook : Spellbook) + + toString() : String + } + interface SpellDao { + + findByName(String) : Spell {abstract} + } + class SpellDaoImpl { + + SpellDaoImpl() + + findByName(name : String) : Spell + } +} +package com.iluwatar.servicelayer.app { + class App { + - LOGGER : Logger {static} + + App() + + initData() {static} + + main(args : String[]) {static} + + queryData() {static} + } +} +MagicServiceImpl --> "-wizardDao" WizardDao +MagicServiceImpl --> "-spellbookDao" SpellbookDao +MagicServiceImpl --> "-spellDao" SpellDao +Spellbook --> "-spells" Spell +Spellbook --> "-wizards" Wizard +DaoBaseImpl ..|> Dao +MagicServiceImpl ..|> MagicService +Spell --|> BaseEntity +SpellDao --|> Dao +SpellDaoImpl ..|> SpellDao +SpellDaoImpl --|> DaoBaseImpl +Spellbook --|> BaseEntity +SpellbookDao --|> Dao +SpellbookDaoImpl ..|> SpellbookDao +SpellbookDaoImpl --|> DaoBaseImpl +Wizard --|> BaseEntity +WizardDao --|> Dao +WizardDaoImpl ..|> WizardDao +WizardDaoImpl --|> DaoBaseImpl +@enduml \ No newline at end of file diff --git a/service-layer/pom.xml b/service-layer/pom.xml index 4e22d20d0c34..809454907b10 100644 --- a/service-layer/pom.xml +++ b/service-layer/pom.xml @@ -2,7 +2,7 @@ "-serviceCache" ServiceCache +ServiceImpl ..|> Service +@enduml \ No newline at end of file diff --git a/service-locator/pom.xml b/service-locator/pom.xml index b4033038a394..56a11da10b3f 100644 --- a/service-locator/pom.xml +++ b/service-locator/pom.xml @@ -2,7 +2,7 @@ "-type" DataType +HashShardManager --|> ShardManager +LookupShardManager --|> ShardManager +RangeShardManager --|> ShardManager +@enduml \ No newline at end of file diff --git a/module/src/main/resources/log4j.xml b/sharding/pom.xml similarity index 60% rename from module/src/main/resources/log4j.xml rename to sharding/pom.xml index b591c17e1665..eb24071fa4d5 100644 --- a/module/src/main/resources/log4j.xml +++ b/sharding/pom.xml @@ -1,8 +1,8 @@ - + - - - - - - - - - - - - - - - \ No newline at end of file + + + java-design-patterns + com.iluwatar + 1.23.0-SNAPSHOT + + 4.0.0 + + sharding + + + + junit + junit + + + + \ No newline at end of file diff --git a/sharding/src/main/java/com/iluwatar/sharding/App.java b/sharding/src/main/java/com/iluwatar/sharding/App.java new file mode 100644 index 000000000000..482b056b165f --- /dev/null +++ b/sharding/src/main/java/com/iluwatar/sharding/App.java @@ -0,0 +1,88 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sharding; + +/** + * Sharding pattern means dividing a data store into a set of horizontal partitions + * or shards. This pattern can improve scalability when storing and accessing large + * volumes of data. + */ +public class App { + + /** + * Program main entry point. + * @param args program runtime arguments + */ + public static void main(String[] args) { + + var data1 = new Data(1, "data1", Data.DataType.type1); + var data2 = new Data(2, "data2", Data.DataType.type2); + var data3 = new Data(3, "data3", Data.DataType.type3); + var data4 = new Data(4, "data4", Data.DataType.type1); + + var shard1 = new Shard(1); + var shard2 = new Shard(2); + var shard3 = new Shard(3); + + ShardManager manager = new LookupShardManager(); + manager.addNewShard(shard1); + manager.addNewShard(shard2); + manager.addNewShard(shard3); + manager.storeData(data1); + manager.storeData(data2); + manager.storeData(data3); + manager.storeData(data4); + + shard1.clearData(); + shard2.clearData(); + shard3.clearData(); + + manager = new RangeShardManager(); + manager.addNewShard(shard1); + manager.addNewShard(shard2); + manager.addNewShard(shard3); + manager.storeData(data1); + manager.storeData(data2); + manager.storeData(data3); + manager.storeData(data4); + + shard1.clearData(); + shard2.clearData(); + shard3.clearData(); + + manager = new HashShardManager(); + manager.addNewShard(shard1); + manager.addNewShard(shard2); + manager.addNewShard(shard3); + manager.storeData(data1); + manager.storeData(data2); + manager.storeData(data3); + manager.storeData(data4); + + shard1.clearData(); + shard2.clearData(); + shard3.clearData(); + } + +} diff --git a/sharding/src/main/java/com/iluwatar/sharding/Data.java b/sharding/src/main/java/com/iluwatar/sharding/Data.java new file mode 100644 index 000000000000..92e84e93ae18 --- /dev/null +++ b/sharding/src/main/java/com/iluwatar/sharding/Data.java @@ -0,0 +1,84 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sharding; + +/** + * Basic data structure for each tuple stored in data shards. + */ +public class Data { + + private int key; + + private String value; + + private DataType type; + + /** + * Constructor of Data class. + * @param key data key + * @param value data vlue + * @param type data type + */ + public Data(final int key, final String value, final DataType type) { + this.key = key; + this.value = value; + this.type = type; + } + + public int getKey() { + return key; + } + + public void setKey(final int key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(final String value) { + this.value = value; + } + + public DataType getType() { + return type; + } + + public void setType(DataType type) { + this.type = type; + } + + enum DataType { + type1, type2, type3 + } + + @Override + public String toString() { + return "Data {" + "key=" + + key + ", value='" + value + + '\'' + ", type=" + type + '}'; + } +} + diff --git a/sharding/src/main/java/com/iluwatar/sharding/HashShardManager.java b/sharding/src/main/java/com/iluwatar/sharding/HashShardManager.java new file mode 100644 index 000000000000..11ada60d72ae --- /dev/null +++ b/sharding/src/main/java/com/iluwatar/sharding/HashShardManager.java @@ -0,0 +1,55 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sharding; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ShardManager with hash strategy. The purpose of this strategy is to reduce the + * chance of hot-spots in the data. It aims to distribute the data across the shards + * in a way that achieves a balance between the size of each shard and the average + * load that each shard will encounter. + */ +public class HashShardManager extends ShardManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(HashShardManager.class); + + @Override + public int storeData(Data data) { + var shardId = allocateShard(data); + var shard = shardMap.get(shardId); + shard.storeData(data); + LOGGER.info(data.toString() + " is stored in Shard " + shardId); + return shardId; + } + + @Override + protected int allocateShard(Data data) { + var shardCount = shardMap.size(); + var hash = data.getKey() % shardCount; + return hash == 0 ? hash + shardCount : hash; + } + +} diff --git a/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java b/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java new file mode 100644 index 000000000000..f282afd28bf8 --- /dev/null +++ b/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java @@ -0,0 +1,66 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sharding; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ShardManager with lookup strategy. In this strategy the sharding logic implements + * a map that routes a request for data to the shard that contains that data by using + * the shard key. + */ +public class LookupShardManager extends ShardManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(LookupShardManager.class); + + private Map lookupMap = new HashMap<>(); + + @Override + public int storeData(Data data) { + var shardId = allocateShard(data); + lookupMap.put(data.getKey(), shardId); + var shard = shardMap.get(shardId); + shard.storeData(data); + LOGGER.info(data.toString() + " is stored in Shard " + shardId); + return shardId; + } + + @Override + protected int allocateShard(Data data) { + var key = data.getKey(); + if (lookupMap.containsKey(key)) { + return lookupMap.get(key); + } else { + var shardCount = shardMap.size(); + var allocatedShardId = new Random().nextInt(shardCount - 1) + 1; + return allocatedShardId; + } + } + +} diff --git a/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java b/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java new file mode 100644 index 000000000000..f7a8a90af601 --- /dev/null +++ b/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java @@ -0,0 +1,66 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sharding; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ShardManager with range strategy. This strategy groups related items together + * in the same shard, and orders them by shard key. + */ +public class RangeShardManager extends ShardManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(RangeShardManager.class); + + @Override + public int storeData(Data data) { + var shardId = allocateShard(data); + var shard = shardMap.get(shardId); + shard.storeData(data); + LOGGER.info(data.toString() + " is stored in Shard " + shardId); + return shardId; + } + + @Override + protected int allocateShard(Data data) { + var type = data.getType(); + var shardId = -1; + switch (type) { + case type1: + shardId = 1; + break; + case type2: + shardId = 2; + break; + case type3: + shardId = 3; + break; + default: + break; + } + return shardId; + } + +} diff --git a/sharding/src/main/java/com/iluwatar/sharding/Shard.java b/sharding/src/main/java/com/iluwatar/sharding/Shard.java new file mode 100644 index 000000000000..eb081425887e --- /dev/null +++ b/sharding/src/main/java/com/iluwatar/sharding/Shard.java @@ -0,0 +1,59 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sharding; + +import java.util.HashMap; +import java.util.Map; + +/** + * The Shard class stored data in a HashMap. + */ +public class Shard { + + private final int id; + + private Map dataStore; + + public Shard(final int id) { + this.id = id; + this.dataStore = new HashMap<>(); + } + + public void storeData(Data data) { + dataStore.put(data.getKey(), data); + } + + public void clearData() { + dataStore.clear(); + } + + public Data getDataById(final int id) { + return dataStore.get(id); + } + + public int getId() { + return id; + } + +} diff --git a/sharding/src/main/java/com/iluwatar/sharding/ShardManager.java b/sharding/src/main/java/com/iluwatar/sharding/ShardManager.java new file mode 100644 index 000000000000..f244509d503a --- /dev/null +++ b/sharding/src/main/java/com/iluwatar/sharding/ShardManager.java @@ -0,0 +1,103 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sharding; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract class for ShardManager. + */ +public abstract class ShardManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(ShardManager.class); + + protected Map shardMap; + + public ShardManager() { + shardMap = new HashMap<>(); + } + + /** + * Add a provided shard instance to shardMap. + * + * @param shard new shard instance. + * @return {@code true} if succeed to add the new instance. + * {@code false} if the shardId is already existed. + */ + public boolean addNewShard(final Shard shard) { + var shardId = shard.getId(); + if (!shardMap.containsKey(shardId)) { + shardMap.put(shardId, shard); + return true; + } else { + return false; + } + } + + /** + * Remove a shard instance by provided Id. + * + * @param shardId Id of shard instance to remove. + * @return {@code true} if removed. {@code false} if the shardId is not existed. + */ + public boolean removeShardById(final int shardId) { + if (shardMap.containsKey(shardId)) { + shardMap.remove(shardId); + return true; + } else { + return false; + } + } + + /** + * Get shard instance by provided shardId. + * + * @param shardId id of shard instance to get + * @return required shard instance + */ + public Shard getShardById(final int shardId) { + return shardMap.get(shardId); + } + + /** + * Store data in proper shard instance. + * + * @param data new data + * @return id of shard that the data is stored in + */ + public abstract int storeData(final Data data); + + /** + * Allocate proper shard to provided data. + * + * @param data new data + * @return id of shard that the data should be stored + */ + protected abstract int allocateShard(final Data data); + +} diff --git a/sharding/src/test/java/com/iluwatar/sharding/AppTest.java b/sharding/src/test/java/com/iluwatar/sharding/AppTest.java new file mode 100644 index 000000000000..fce8d89a3ca6 --- /dev/null +++ b/sharding/src/test/java/com/iluwatar/sharding/AppTest.java @@ -0,0 +1,39 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sharding; + +import org.junit.Test; + +/** + * Unit tests for App class. + */ +public class AppTest { + + @Test + public void testMain() { + String[] args = {}; + App.main(args); + } + +} diff --git a/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java new file mode 100644 index 000000000000..6418cf0c4f5b --- /dev/null +++ b/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java @@ -0,0 +1,64 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sharding; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * Unit tests for HashShardManager class. + */ +public class HashShardManagerTest { + + private HashShardManager hashShardManager; + + /** + * Initialize hashShardManager instance. + */ + @Before + public void setup() { + hashShardManager = new HashShardManager(); + var shard1 = new Shard(1); + var shard2 = new Shard(2); + var shard3 = new Shard(3); + hashShardManager.addNewShard(shard1); + hashShardManager.addNewShard(shard2); + hashShardManager.addNewShard(shard3); + } + + @After + public void tearDown() { + + } + + @Test + public void testStoreData() { + var data = new Data(1, "test", Data.DataType.type1); + hashShardManager.storeData(data); + Assert.assertEquals(data, hashShardManager.getShardById(1).getDataById(1)); + } + +} diff --git a/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java new file mode 100644 index 000000000000..7379859b8272 --- /dev/null +++ b/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java @@ -0,0 +1,68 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sharding; + +import java.util.Map; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * Unit tests for LookupShardManager class. + */ +public class LookupShardManagerTest { + + private LookupShardManager lookupShardManager; + + /** + * Initialize lookupShardManager instance. + */ + @Before + public void setup() { + lookupShardManager = new LookupShardManager(); + var shard1 = new Shard(1); + var shard2 = new Shard(2); + var shard3 = new Shard(3); + lookupShardManager.addNewShard(shard1); + lookupShardManager.addNewShard(shard2); + lookupShardManager.addNewShard(shard3); + } + + @Test + public void testStoreData() { + try { + var data = new Data(1, "test", Data.DataType.type1); + lookupShardManager.storeData(data); + var field = LookupShardManager.class.getDeclaredField("lookupMap"); + field.setAccessible(true); + Map lookupMap = (Map) field.get(lookupShardManager); + var shardId = lookupMap.get(1); + var shard = lookupShardManager.getShardById(shardId); + Assert.assertEquals(data, shard.getDataById(1)); + } catch (NoSuchFieldException | IllegalAccessException e) { + Assert.fail("Fail to modify field access."); + } + } +} diff --git a/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java new file mode 100644 index 000000000000..997687dfc9f6 --- /dev/null +++ b/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java @@ -0,0 +1,58 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sharding; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * Unit tests for RangeShardManager class. + */ +public class RangeShardManagerTest { + + private RangeShardManager rangeShardManager; + + /** + * Initialize rangeShardManager instance. + */ + @Before + public void setup() { + rangeShardManager = new RangeShardManager(); + var shard1 = new Shard(1); + var shard2 = new Shard(2); + var shard3 = new Shard(3); + rangeShardManager.addNewShard(shard1); + rangeShardManager.addNewShard(shard2); + rangeShardManager.addNewShard(shard3); + } + + @Test + public void testStoreData() { + var data = new Data(1, "test", Data.DataType.type1); + rangeShardManager.storeData(data); + Assert.assertEquals(data, rangeShardManager.getShardById(1).getDataById(1)); + } + +} diff --git a/sharding/src/test/java/com/iluwatar/sharding/ShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/ShardManagerTest.java new file mode 100644 index 000000000000..ff4544973ef7 --- /dev/null +++ b/sharding/src/test/java/com/iluwatar/sharding/ShardManagerTest.java @@ -0,0 +1,104 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sharding; + +import java.util.Map; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * Unit tests for ShardManager class. + */ +public class ShardManagerTest { + + private ShardManager shardManager; + + /** + * Initialize shardManager instance. + */ + @Before + public void setup() { + shardManager = new TestShardManager(); + } + + @After + public void tearDown() { + + } + + @Test + public void testAddNewShard() { + try { + var shard = new Shard(1); + shardManager.addNewShard(shard); + var field = ShardManager.class.getDeclaredField("shardMap"); + field.setAccessible(true); + Map map = (Map) field.get(shardManager); + Assert.assertEquals(1, map.size()); + Assert.assertEquals(shard, map.get(1)); + } catch (NoSuchFieldException | IllegalAccessException e) { + Assert.fail("Fail to modify field access."); + } + } + + @Test + public void testRemoveShardById() { + try { + var shard = new Shard(1); + shardManager.addNewShard(shard); + boolean flag = shardManager.removeShardById(1); + var field = ShardManager.class.getDeclaredField("shardMap"); + field.setAccessible(true); + Map map = (Map) field.get(shardManager); + Assert.assertEquals(true, flag); + Assert.assertEquals(0, map.size()); + } catch (IllegalAccessException | NoSuchFieldException e) { + Assert.fail("Fail to modify field access."); + } + } + + @Test + public void testGetShardById() { + Shard shard = new Shard(1); + shardManager.addNewShard(shard); + Shard tmpShard = shardManager.getShardById(1); + Assert.assertEquals(shard, tmpShard); + } + + class TestShardManager extends ShardManager { + + @Override + public int storeData(Data data) { + return 0; + } + + @Override + protected int allocateShard(Data data) { + return 0; + } + } +} diff --git a/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java b/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java new file mode 100644 index 000000000000..4c0f74fa2e8d --- /dev/null +++ b/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java @@ -0,0 +1,83 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sharding; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + + +/** + * Unit tests for Shard class. + */ +public class ShardTest { + + private Data data; + + private Shard shard; + + @Before + public void setup() { + data = new Data(1, "test", Data.DataType.type1); + shard = new Shard(1); + } + + @After + public void tearDown() {} + + @Test + public void testStoreData() { + try { + shard.storeData(data); + var field = Shard.class.getDeclaredField("dataStore"); + field.setAccessible(true); + Map dataMap = (Map) field.get(shard); + Assert.assertEquals(1, dataMap.size()); + Assert.assertEquals(data, dataMap.get(1)); + } catch (NoSuchFieldException | IllegalAccessException e) { + Assert.fail("Fail to modify field access."); + } + + } + + @Test + public void testClearData() { + try { + Map dataMap = new HashMap<>(); + dataMap.put(1, data); + var field = Shard.class.getDeclaredField("dataStore"); + field.setAccessible(true); + field.set(shard, dataMap); + shard.clearData(); + dataMap = (Map) field.get(shard); + Assert.assertEquals(0, dataMap.size()); + } catch (NoSuchFieldException | IllegalAccessException e) { + Assert.fail("Fail to modify field access."); + } + } +} diff --git a/singleton/README.md b/singleton/README.md index 52aca8e162e6..8c145f1df8be 100644 --- a/singleton/README.md +++ b/singleton/README.md @@ -5,9 +5,7 @@ folder: singleton permalink: /patterns/singleton/ categories: Creational tags: - - Java - - Gang Of Four - - Difficulty-Beginner + - Gang of Four --- ## Intent @@ -48,17 +46,20 @@ EnumIvoryTower enumIvoryTower2 = EnumIvoryTower.INSTANCE; assertEquals(enumIvoryTower1, enumIvoryTower2); // true ``` +## Class diagram +![alt text](./etc/singleton.urm.png "Singleton pattern class diagram") + ## Applicability Use the Singleton pattern when -* there must be exactly one instance of a class, and it must be accessible to clients from a well-known access point -* when the sole instance should be extensible by subclassing, and clients should be able to use an extended instance without modifying their code +* There must be exactly one instance of a class, and it must be accessible to clients from a well-known access point +* When the sole instance should be extensible by subclassing, and clients should be able to use an extended instance without modifying their code ## Typical Use Case -* the logging class -* managing a connection to a database -* file manager +* The logging class +* Managing a connection to a database +* File manager ## Real world examples diff --git a/singleton/etc/singleton.urm.png b/singleton/etc/singleton.urm.png new file mode 100644 index 000000000000..46584af40039 Binary files /dev/null and b/singleton/etc/singleton.urm.png differ diff --git a/singleton/etc/singleton.urm.puml b/singleton/etc/singleton.urm.puml new file mode 100644 index 000000000000..371c9e2393d1 --- /dev/null +++ b/singleton/etc/singleton.urm.puml @@ -0,0 +1,44 @@ +@startuml +package com.iluwatar.singleton { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + enum EnumIvoryTower { + + INSTANCE {static} + + toString() : String + + valueOf(name : String) : EnumIvoryTower {static} + + values() : EnumIvoryTower[] {static} + } + class InitializingOnDemandHolderIdiom { + - InitializingOnDemandHolderIdiom() + + getInstance() : InitializingOnDemandHolderIdiom {static} + } + -class HelperHolder { + - INSTANCE : InitializingOnDemandHolderIdiom {static} + - HelperHolder() + } + class IvoryTower { + - INSTANCE : IvoryTower {static} + - IvoryTower() + + getInstance() : IvoryTower {static} + } + class ThreadSafeDoubleCheckLocking { + - flag : boolean {static} + - instance : ThreadSafeDoubleCheckLocking {static} + - ThreadSafeDoubleCheckLocking() + + getInstance() : ThreadSafeDoubleCheckLocking {static} + } + class ThreadSafeLazyLoadedIvoryTower { + - instance : ThreadSafeLazyLoadedIvoryTower {static} + - ThreadSafeLazyLoadedIvoryTower() + + getInstance() : ThreadSafeLazyLoadedIvoryTower {static} + } +} +IvoryTower --> "-INSTANCE" IvoryTower +ThreadSafeDoubleCheckLocking --> "-instance" ThreadSafeDoubleCheckLocking +ThreadSafeLazyLoadedIvoryTower --> "-instance" ThreadSafeLazyLoadedIvoryTower +HelperHolder ..+ InitializingOnDemandHolderIdiom +HelperHolder --> "-INSTANCE" InitializingOnDemandHolderIdiom +@enduml \ No newline at end of file diff --git a/singleton/pom.xml b/singleton/pom.xml index ac900d0a6d63..7862cd2a090b 100644 --- a/singleton/pom.xml +++ b/singleton/pom.xml @@ -2,7 +2,7 @@ "-quadTree" QuadTree +SpatialPartitionGeneric --> "-quadTree" QuadTree +QuadTree --> "-boundary" Rect +QuadTree --> "-northwest" QuadTree +QuadTree --> "-southwest" QuadTree +Bubble --|> Point +SpatialPartitionBubbles --|> SpatialPartitionGeneric +@enduml \ No newline at end of file diff --git a/spatial-partition/pom.xml b/spatial-partition/pom.xml index 40fe0f735634..4b048714de72 100644 --- a/spatial-partition/pom.xml +++ b/spatial-partition/pom.xml @@ -1,7 +1,7 @@ "-size" Size +AbstractCreature --> "-mass" Mass +MassEqualSelector --> "-mass" Mass +AbstractCreature --> "-color" Color +MassGreaterThanSelector --> "-mass" Mass +MovementSelector --> "-movement" Movement +NegationSelector --> "-component" AbstractSelector +AbstractCreature --> "-movement" Movement +MassSmallerThanOrEqSelector --> "-mass" Mass +AbstractCreature --> "-size" Size +ColorSelector --> "-color" Color +AbstractCreature ..|> Creature +Dragon --|> AbstractCreature +Goblin --|> AbstractCreature +KillerBee --|> AbstractCreature +Octopus --|> AbstractCreature +Shark --|> AbstractCreature +Troll --|> AbstractCreature +ColorSelector --|> AbstractSelector +ConjunctionSelector --|> AbstractSelector +DisjunctionSelector --|> AbstractSelector +MassEqualSelector --|> AbstractSelector +MassGreaterThanSelector --|> AbstractSelector +MassSmallerThanOrEqSelector --|> AbstractSelector +MovementSelector --|> AbstractSelector +NegationSelector --|> AbstractSelector +SizeSelector --|> AbstractSelector +@enduml \ No newline at end of file diff --git a/specification/pom.xml b/specification/pom.xml index 2e250d8ce9a5..79d81fd5c3a8 100644 --- a/specification/pom.xml +++ b/specification/pom.xml @@ -2,7 +2,7 @@ "-mammoth" Mammoth +AngryState --> "-mammoth" Mammoth +Mammoth --> "-state" State +AngryState ..|> State +PeacefulState ..|> State +@enduml \ No newline at end of file diff --git a/state/pom.xml b/state/pom.xml index f0433d9b5fa0..cb90a53f856a 100644 --- a/state/pom.xml +++ b/state/pom.xml @@ -2,7 +2,7 @@ "-strategy" DragonSlayingStrategy +MeleeStrategy ..|> DragonSlayingStrategy +ProjectileStrategy ..|> DragonSlayingStrategy +SpellStrategy ..|> DragonSlayingStrategy +@enduml \ No newline at end of file diff --git a/strategy/pom.xml b/strategy/pom.xml index eec6ad9a24ea..a0f09297ecf2 100644 --- a/strategy/pom.xml +++ b/strategy/pom.xml @@ -2,7 +2,7 @@ + + + java-design-patterns + com.iluwatar + 1.23.0-SNAPSHOT + + 4.0.0 + + subclass-sandbox + + + + junit + junit + + + com.github.stefanbirkner + system-rules + + + + \ No newline at end of file diff --git a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/App.java b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/App.java new file mode 100644 index 000000000000..9d3881b52010 --- /dev/null +++ b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/App.java @@ -0,0 +1,53 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.subclasssandbox; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The subclass sandbox pattern describes a basic idea, while not having a lot + * of detailed mechanics. You will need the pattern when you have several similar + * subclasses. If you have to make a tiny change, then change the base class, + * while all subclasses shouldn't have to be touched. So the base class has to be + * able to provide all of the operations a derived class needs to perform. + */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Entry point of the main program. + * @param args Program runtime arguments. + */ + public static void main(String[] args) { + LOGGER.info("Use superpower: sky launch"); + var skyLaunch = new SkyLaunch(); + skyLaunch.activate(); + LOGGER.info("Use superpower: ground dive"); + var groundDive = new GroundDive(); + groundDive.activate(); + } + +} diff --git a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/GroundDive.java b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/GroundDive.java new file mode 100644 index 000000000000..57779e823c57 --- /dev/null +++ b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/GroundDive.java @@ -0,0 +1,44 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.subclasssandbox; + +import org.slf4j.LoggerFactory; + +/** + * GroundDive superpower. + */ +public class GroundDive extends Superpower { + + public GroundDive() { + super(); + logger = LoggerFactory.getLogger(GroundDive.class); + } + + @Override + protected void activate() { + move(0, 0, -20); + playSound("GROUNDDIVE_SOUND", 5); + spawnParticles("GROUNDDIVE_PARTICLE", 20); + } +} diff --git a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/SkyLaunch.java b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/SkyLaunch.java new file mode 100644 index 000000000000..803fd419ae56 --- /dev/null +++ b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/SkyLaunch.java @@ -0,0 +1,44 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.subclasssandbox; + +import org.slf4j.LoggerFactory; + +/** + * SkyLaunch superpower. + */ +public class SkyLaunch extends Superpower { + + public SkyLaunch() { + super(); + logger = LoggerFactory.getLogger(SkyLaunch.class); + } + + @Override + protected void activate() { + move(0, 0, 20); + playSound("SKYLAUNCH_SOUND", 1); + spawnParticles("SKYLAUNCH_PARTICLE", 100); + } +} diff --git a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/Superpower.java b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/Superpower.java new file mode 100644 index 000000000000..aa10f9bee0bb --- /dev/null +++ b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/Superpower.java @@ -0,0 +1,69 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.subclasssandbox; + +import org.slf4j.Logger; + +/** + * Superpower abstract class. In this class the basic operations of all types of + * superpowers are provided as protected methods. + */ +public abstract class Superpower { + + protected Logger logger; + + /** + * Subclass of superpower should implement this sandbox method by calling the + * methods provided in this super class. + */ + protected abstract void activate(); + + /** + * Move to (x, y, z). + * @param x X coordinate. + * @param y Y coordinate. + * @param z Z coordinate. + */ + protected void move(double x, double y, double z) { + logger.info("Move to ( " + x + ", " + y + ", " + z + " )"); + } + + /** + * Play sound effect for the superpower. + * @param soundName Sound name. + * @param volumn Value of volumn. + */ + protected void playSound(String soundName, int volumn) { + logger.info("Play " + soundName + " with volumn " + volumn); + } + + /** + * Spawn particles for the superpower. + * @param particleType Particle type. + * @param count Count of particles to be spawned. + */ + protected void spawnParticles(String particleType, int count) { + logger.info("Spawn " + count + " particle with type " + particleType); + } +} diff --git a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java new file mode 100644 index 000000000000..51ed0d455052 --- /dev/null +++ b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java @@ -0,0 +1,38 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.subclasssandbox; + +import org.junit.Test; + +/** + * App unit tests. + */ +public class AppTest { + + @Test + public void testMain() { + String[] args = {}; + App.main(args); + } +} diff --git a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java new file mode 100644 index 000000000000..ff052a81eb30 --- /dev/null +++ b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java @@ -0,0 +1,92 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.subclasssandbox; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.SystemOutRule; + +/** + * GroundDive unit tests. + */ +public class GroundDiveTest { + + @Rule + public SystemOutRule log = new SystemOutRule().enableLog(); + + @Test + public void testMove() { + log.clearLog(); + var groundDive = new GroundDive(); + groundDive.move(1.0, 1.0, 1.0); + var outputLog = getLogContent(log.getLog()); + var expectedLog = "Move to ( 1.0, 1.0, 1.0 )"; + Assert.assertEquals(outputLog, expectedLog); + } + + @Test + public void testPlaySound() { + log.clearLog(); + var groundDive = new GroundDive(); + groundDive.playSound("SOUND_NAME", 1); + var outputLog = getLogContent(log.getLog()); + var expectedLog = "Play SOUND_NAME with volumn 1"; + Assert.assertEquals(outputLog, expectedLog); + } + + @Test + public void testSpawnParticles() { + log.clearLog(); + var groundDive = new GroundDive(); + groundDive.spawnParticles("PARTICLE_TYPE", 100); + final var outputLog = getLogContent(log.getLog()); + final var expectedLog = "Spawn 100 particle with type PARTICLE_TYPE"; + Assert.assertEquals(outputLog, expectedLog); + } + + @Test + public void testActivate() { + log.clearLog(); + var groundDive = new GroundDive(); + groundDive.activate();; + String[] logs = log.getLog().split("\n"); + final var expectedSize = 3; + final var log1 = logs[0].split("-")[1].trim() + " -" + logs[0].split("-")[2].trim(); + final var expectedLog1 = "Move to ( 0.0, 0.0, -20.0 )"; + final var log2 = getLogContent(logs[1]); + final var expectedLog2 = "Play GROUNDDIVE_SOUND with volumn 5"; + final var log3 = getLogContent(logs[2]); + final var expectedLog3 = "Spawn 20 particle with type GROUNDDIVE_PARTICLE"; + Assert.assertEquals(logs.length, expectedSize); + Assert.assertEquals(log1, expectedLog1); + Assert.assertEquals(log2, expectedLog2); + Assert.assertEquals(log3, expectedLog3); + } + + private String getLogContent(String log) { + return log.split("-")[1].trim(); + } + +} diff --git a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java new file mode 100644 index 000000000000..58a349bb5dfd --- /dev/null +++ b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java @@ -0,0 +1,91 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.subclasssandbox; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.SystemOutRule; + +/** + * SkyLaunch unit tests. + */ +public class SkyLaunchTest { + + @Rule + public SystemOutRule log = new SystemOutRule().enableLog(); + + @Test + public void testMove() { + log.clearLog(); + var skyLaunch = new SkyLaunch(); + skyLaunch.move(1.0, 1.0, 1.0); + var outputLog = getLogContent(log.getLog()); + var expectedLog = "Move to ( 1.0, 1.0, 1.0 )"; + Assert.assertEquals(outputLog, expectedLog); + } + + @Test + public void testPlaySound() { + log.clearLog(); + var skyLaunch = new SkyLaunch(); + skyLaunch.playSound("SOUND_NAME", 1); + var outputLog = getLogContent(log.getLog()); + var expectedLog = "Play SOUND_NAME with volumn 1"; + Assert.assertEquals(outputLog, expectedLog); + } + + @Test + public void testSpawnParticles() { + log.clearLog(); + var skyLaunch = new SkyLaunch(); + skyLaunch.spawnParticles("PARTICLE_TYPE", 100); + var outputLog = getLogContent(log.getLog()); + var expectedLog = "Spawn 100 particle with type PARTICLE_TYPE"; + Assert.assertEquals(outputLog, expectedLog); + } + + @Test + public void testActivate() { + log.clearLog(); + var skyLaunch = new SkyLaunch(); + skyLaunch.activate();; + String[] logs = log.getLog().split("\n"); + final var expectedSize = 3; + final var log1 = getLogContent(logs[0]); + final var expectedLog1 = "Move to ( 0.0, 0.0, 20.0 )"; + final var log2 = getLogContent(logs[1]); + final var expectedLog2 = "Play SKYLAUNCH_SOUND with volumn 1"; + final var log3 = getLogContent(logs[2]); + final var expectedLog3 = "Spawn 100 particle with type SKYLAUNCH_PARTICLE"; + Assert.assertEquals(logs.length, expectedSize); + Assert.assertEquals(log1, expectedLog1); + Assert.assertEquals(log2, expectedLog2); + Assert.assertEquals(log3, expectedLog3); + } + + private String getLogContent(String log) { + return log.split("-")[1].trim(); + } +} diff --git a/template-method/README.md b/template-method/README.md index 5a1402259f4b..668d6e28c9d5 100644 --- a/template-method/README.md +++ b/template-method/README.md @@ -5,9 +5,7 @@ folder: template-method permalink: /patterns/template-method/ categories: Behavioral tags: - - Java - - Difficulty-Beginner - - Gang Of Four + - Gang of Four --- ## Intent @@ -15,18 +13,28 @@ Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure. +To make sure that subclasses don’t override the template method, the template method should be declared `final`. + +## Class diagram ![alt text](./etc/template-method_1.png "Template Method") ## Applicability The Template Method pattern should be used -* to implement the invariant parts of an algorithm once and leave it up to subclasses to implement the behavior that can vary -* when common behavior among subclasses should be factored and localized in a common class to avoid code duplication. This is good example of "refactoring to generalize" as described by Opdyke and Johnson. You first identify the differences in the existing code and then separate the differences into new operations. Finally, you replace the differing code with a template method that calls one of these new operations -* to control subclasses extensions. You can define a template method that calls "hook" operations at specific points, thereby permitting extensions only at those points +* To implement the invariant parts of an algorithm once and leave it up to subclasses to implement the behavior that can vary +* When common behavior among subclasses should be factored and localized in a common class to avoid code duplication. This is good example of "refactoring to generalize" as described by Opdyke and Johnson. You first identify the differences in the existing code and then separate the differences into new operations. Finally, you replace the differing code with a template method that calls one of these new operations +* To control subclasses extensions. You can define a template method that calls "hook" operations at specific points, thereby permitting extensions only at those points ## Tutorial + * [Template-method Pattern Tutorial](https://www.journaldev.com/1763/template-method-design-pattern-in-java) +## Real world examples + +* [javax.servlet.GenericServlet.init](https://jakarta.ee/specifications/servlet/4.0/apidocs/javax/servlet/GenericServlet.html#init--): +Method `GenericServlet.init(ServletConfig config)` calls the parameterless method `GenericServlet.init()` which is intended to be overridden in subclasses. +Method `GenericServlet.init(ServletConfig config)` is the template method in this example. + ## Credits * [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) diff --git a/template-method/etc/template-method.urm.puml b/template-method/etc/template-method.urm.puml new file mode 100644 index 000000000000..a6e2dc3d20fd --- /dev/null +++ b/template-method/etc/template-method.urm.puml @@ -0,0 +1,39 @@ +@startuml +package com.iluwatar.templatemethod { + class App { + + App() + + main(args : String[]) {static} + } + class HalflingThief { + - method : StealingMethod + + HalflingThief(method : StealingMethod) + + changeMethod(method : StealingMethod) + + steal() + } + class HitAndRunMethod { + - LOGGER : Logger {static} + + HitAndRunMethod() + # confuseTarget(target : String) + # pickTarget() : String + # stealTheItem(target : String) + } + abstract class StealingMethod { + - LOGGER : Logger {static} + + StealingMethod() + # confuseTarget(String) {abstract} + # pickTarget() : String {abstract} + + steal() + # stealTheItem(String) {abstract} + } + class SubtleMethod { + - LOGGER : Logger {static} + + SubtleMethod() + # confuseTarget(target : String) + # pickTarget() : String + # stealTheItem(target : String) + } +} +HalflingThief --> "-method" StealingMethod +HitAndRunMethod --|> StealingMethod +SubtleMethod --|> StealingMethod +@enduml \ No newline at end of file diff --git a/template-method/pom.xml b/template-method/pom.xml index bd8255a8be45..e236c28c8340 100644 --- a/template-method/pom.xml +++ b/template-method/pom.xml @@ -2,7 +2,7 @@ "-task" Task +CoffeeMakingTask --|> Task +PotatoPeelingTask --|> Task +@enduml \ No newline at end of file diff --git a/thread-pool/pom.xml b/thread-pool/pom.xml index c4b2cdd282bb..14946b393710 100644 --- a/thread-pool/pom.xml +++ b/thread-pool/pom.xml @@ -2,7 +2,7 @@ "-callsCount" CallsCount +ThrottleTimerImpl --> "-callsCount" CallsCount +ThrottleTimerImpl ..|> Throttler +@enduml \ No newline at end of file diff --git a/throttling/pom.xml b/throttling/pom.xml index 3f8fac4b6034..5192ba28abb7 100644 --- a/throttling/pom.xml +++ b/throttling/pom.xml @@ -2,7 +2,7 @@ - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.21.0-SNAPSHOT - - tls - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + tls + + + org.junit.jupiter + junit-jupiter-engine + test + + + diff --git a/tls/src/main/java/com/iluwatar/tls/App.java b/tls/src/main/java/com/iluwatar/tls/App.java index 634d36d26b90..9dd611512ef8 100644 --- a/tls/src/main/java/com/iluwatar/tls/App.java +++ b/tls/src/main/java/com/iluwatar/tls/App.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2016 Thomas Bauer + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,68 +24,64 @@ package com.iluwatar.tls; import java.util.Calendar; -import java.util.Date; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * ThreadLocal pattern - *

    - * This App shows how to create an isolated space per each thread. In this - * example the usage of SimpleDateFormat is made to be thread-safe. This is an - * example of the ThreadLocal pattern. - *

    - * By applying the ThreadLocal pattern you can keep track of application - * instances or locale settings throughout the handling of a request. The - * ThreadLocal class works like a static variable, with the exception that it is - * only bound to the current thread! This allows us to use static variables in a - * thread-safe way. - *

    - * In Java, thread-local variables are implemented by the ThreadLocal class - * object. ThreadLocal holds a variable of type T, which is accessible via get/set - * methods. - *

    - * SimpleDateFormat is one of the basic Java classes and is not thread-safe. If - * you do not isolate the instance of SimpleDateFormat per each thread then - * problems arise. - *

    - * App converts the String date value 15/12/2015 to the Date format using the - * Java class SimpleDateFormat. It does this 20 times using 4 threads, each doing - * it 5 times. With the usage of as ThreadLocal in DateFormatCallable everything - * runs well. But if you comment out the ThreadLocal variant (marked with "//TLTL") - * and comment in the non ThreadLocal variant (marked with "//NTLNTL") you can - * see what will happen without the ThreadLocal. Most likely you will get incorrect - * date values and / or exceptions. - *

    - * This example clearly show what will happen when using non thread-safe classes - * in a thread. In real life this may happen one in of 1.000 or 10.000 conversions - * and those are really hard to find errors. - * - * @author Thomas Bauer, 2017 + * + *

    This App shows how to create an isolated space per each thread. In this example the usage of + * SimpleDateFormat is made to be thread-safe. This is an example of the ThreadLocal pattern. + * + *

    By applying the ThreadLocal pattern you can keep track of application instances or locale + * settings throughout the handling of a request. The ThreadLocal class works like a static + * variable, with the exception that it is only bound to the current thread! This allows us to use + * static variables in a thread-safe way. + * + *

    In Java, thread-local variables are implemented by the ThreadLocal class object. ThreadLocal + * holds a variable of type T, which is accessible via get/set methods. + * + *

    SimpleDateFormat is one of the basic Java classes and is not thread-safe. If you do not + * isolate the instance of SimpleDateFormat per each thread then problems arise. + * + *

    App converts the String date value 15/12/2015 to the Date format using the Java class + * SimpleDateFormat. It does this 20 times using 4 threads, each doing it 5 times. With the usage of + * as ThreadLocal in DateFormatCallable everything runs well. But if you comment out the ThreadLocal + * variant (marked with "//TLTL") and comment in the non ThreadLocal variant (marked with + * "//NTLNTL") you can see what will happen without the ThreadLocal. Most likely you will get + * incorrect date values and / or exceptions. + * + *

    This example clearly show what will happen when using non thread-safe classes in a thread. In + * real life this may happen one in of 1.000 or 10.000 conversions and those are really hard to find + * errors. + * + * @author Thomas Bauer, 2017 */ public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + /** - * Program entry point - * - * @param args - * command line args + * Program entry point. + * + * @param args command line args */ public static void main(String[] args) { - int counterDateValues = 0; - int counterExceptions = 0; + var counterDateValues = 0; + var counterExceptions = 0; // Create a callable - DateFormatCallable callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); + var callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); // start 4 threads, each using the same Callable instance - ExecutorService executor = Executors.newCachedThreadPool(); + var executor = Executors.newCachedThreadPool(); - Future futureResult1 = executor.submit(callableDf); - Future futureResult2 = executor.submit(callableDf); - Future futureResult3 = executor.submit(callableDf); - Future futureResult4 = executor.submit(callableDf); + var futureResult1 = executor.submit(callableDf); + var futureResult2 = executor.submit(callableDf); + var futureResult3 = executor.submit(callableDf); + var futureResult4 = executor.submit(callableDf); try { - Result[] result = new Result[4]; + var result = new Result[4]; result[0] = futureResult1.get(); result[1] = futureResult2.get(); result[2] = futureResult3.get(); @@ -93,53 +89,55 @@ public static void main(String[] args) { // Print results of thread executions (converted dates and raised exceptions) // and count them - for (int i = 0; i < result.length; i++) { - counterDateValues = counterDateValues + printAndCountDates(result[i]); - counterExceptions = counterExceptions + printAndCountExceptions(result[i]); + for (var value : result) { + counterDateValues = counterDateValues + printAndCountDates(value); + counterExceptions = counterExceptions + printAndCountExceptions(value); } // a correct run should deliver 20 times 15.12.2015 // and a correct run shouldn't deliver any exception - System.out.println("The List dateList contains " + counterDateValues + " date values"); - System.out.println("The List exceptionList contains " + counterExceptions + " exceptions"); + LOGGER.info("The List dateList contains " + counterDateValues + " date values"); + LOGGER.info("The List exceptionList contains " + counterExceptions + " exceptions"); } catch (Exception e) { - System.out.println("Abnormal end of program. Program throws exception: " + e); + LOGGER.info("Abnormal end of program. Program throws exception: " + e); } executor.shutdown(); } /** - * Print result (date values) of a thread execution and count dates - * - * @param res contains results of a thread execution + * Print result (date values) of a thread execution and count dates. + * + * @param res contains results of a thread execution */ private static int printAndCountDates(Result res) { // a correct run should deliver 5 times 15.12.2015 per each thread - int counter = 0; - for (Date dt : res.getDateList()) { + var counter = 0; + for (var dt : res.getDateList()) { counter++; - Calendar cal = Calendar.getInstance(); + var cal = Calendar.getInstance(); cal.setTime(dt); // Formatted output of the date value: DD.MM.YYYY - System.out.println( - cal.get(Calendar.DAY_OF_MONTH) + "." + cal.get(Calendar.MONTH) + "." + +cal.get(Calendar.YEAR)); + LOGGER.info(cal.get(Calendar.DAY_OF_MONTH) + "." + + cal.get(Calendar.MONTH) + "." + + cal.get(Calendar.YEAR) + ); } return counter; } /** - * Print result (exceptions) of a thread execution and count exceptions - * - * @param res contains results of a thread execution + * Print result (exceptions) of a thread execution and count exceptions. + * + * @param res contains results of a thread execution * @return number of dates */ private static int printAndCountExceptions(Result res) { // a correct run shouldn't deliver any exception - int counter = 0; - for (String ex : res.getExceptionList()) { + var counter = 0; + for (var ex : res.getExceptionList()) { counter++; - System.out.println(ex); + LOGGER.info(ex); } return counter; } diff --git a/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java b/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java index a28e24d4e762..c4e885896cc3 100644 --- a/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java +++ b/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2016 Thomas Bauer + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,72 +26,67 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.concurrent.Callable; +import java.util.stream.IntStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * DateFormatCallable converts string dates to a date format using - * SimpleDateFormat. The date format and the date value will be passed to the - * Callable by the constructor. The constructor creates a instance of - * SimpleDateFormat and stores it in a ThreadLocal class variable. For the - * complete description of the example see {@link App} - * - * You can comment out the code marked with //TLTL and comment in the - * code marked //NTLNTL. Then you can see what will happen if you do not - * use the ThreadLocal. For details see the description of {@link App} + * DateFormatCallable converts string dates to a date format using SimpleDateFormat. The date format + * and the date value will be passed to the Callable by the constructor. The constructor creates a + * instance of SimpleDateFormat and stores it in a ThreadLocal class variable. For the complete + * description of the example see {@link App}. + * + *

    You can comment out the code marked with //TLTL and comment in the code marked //NTLNTL. Then + * you can see what will happen if you do not use the ThreadLocal. For details see the description + * of {@link App} * - * @author Thomas Bauer, 2017 + * @author Thomas Bauer, 2017 */ public class DateFormatCallable implements Callable { + + private static final Logger LOGGER = LoggerFactory.getLogger(DateFormatCallable.class); // class variables (members) private ThreadLocal df; //TLTL // private DateFormat df; //NTLNTL private String dateValue; // for dateValue Thread Local not needed - + /** - * The date format and the date value are passed to the constructor - * - * @param inDateFormat - * string date format string, e.g. "dd/MM/yyyy" - * @param inDateValue - * string date value, e.g. "21/06/2016" + * The date format and the date value are passed to the constructor. + * + * @param inDateFormat string date format string, e.g. "dd/MM/yyyy" + * @param inDateValue string date value, e.g. "21/06/2016" */ public DateFormatCallable(String inDateFormat, String inDateValue) { - final String idf = inDateFormat; //TLTL - this.df = new ThreadLocal() { //TLTL - @Override //TLTL - protected DateFormat initialValue() { //TLTL - return new SimpleDateFormat(idf); //TLTL - } //TLTL - }; //TLTL + final var idf = inDateFormat; //TLTL + this.df = ThreadLocal.withInitial(() -> { //TLTL + return new SimpleDateFormat(idf); //TLTL + }); //TLTL // this.df = new SimpleDateFormat(inDateFormat); //NTLNTL this.dateValue = inDateValue; } - /** - * @see java.util.concurrent.Callable#call() - */ @Override public Result call() { - System.out.println(Thread.currentThread() + " started executing..."); - Result result = new Result(); + LOGGER.info(Thread.currentThread() + " started executing..."); + var result = new Result(); // Convert date value to date 5 times - for (int i = 1; i <= 5; i++) { + IntStream.rangeClosed(1, 5).forEach(i -> { try { // this is the statement where it is important to have the // instance of SimpleDateFormat locally // Create the date value and store it in dateList result.getDateList().add(this.df.get().parse(this.dateValue)); //TLTL -// result.getDateList().add(this.df.parse(this.dateValue)); //NTLNTL + // result.getDateList().add(this.df.parse(this.dateValue)); //NTLNTL } catch (Exception e) { // write the Exception to a list and continue work result.getExceptionList().add(e.getClass() + ": " + e.getMessage()); } + }); - } - - System.out.println(Thread.currentThread() + " finished processing part of the thread"); + LOGGER.info(Thread.currentThread() + " finished processing part of the thread"); return result; } diff --git a/tls/src/main/java/com/iluwatar/tls/Result.java b/tls/src/main/java/com/iluwatar/tls/Result.java index 69ec5c5020f6..c98a07b913c0 100644 --- a/tls/src/main/java/com/iluwatar/tls/Result.java +++ b/tls/src/main/java/com/iluwatar/tls/Result.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2014 Ilkka Seppälä + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +20,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + /* * Fiducia IT AG, All rights reserved. Use is subject to license terms. */ @@ -31,21 +32,22 @@ import java.util.List; /** - * Result object that will be returned by the Callable {@link DateFormatCallable} - * used in {@link App} + * Result object that will be returned by the Callable {@link DateFormatCallable} used in {@link + * App}. * - * @author Thomas Bauer, 2017 + * @author Thomas Bauer, 2017 */ public class Result { // A list to collect the date values created in one thread - private List dateList = new ArrayList(); + private List dateList = new ArrayList<>(); // A list to collect Exceptions thrown in one threads (should be none in // this example) - private List exceptionList = new ArrayList(); - + private List exceptionList = new ArrayList<>(); + /** - * + * Get list of date values collected within a thread execution. + * * @return List of date values collected within an thread execution */ public List getDateList() { @@ -53,7 +55,8 @@ public List getDateList() { } /** - * + * Get list of exceptions thrown within a thread execution. + * * @return List of exceptions thrown within an thread execution */ public List getExceptionList() { diff --git a/tls/src/test/java/com/iluwatar/tls/AppTest.java b/tls/src/test/java/com/iluwatar/tls/AppTest.java index 12bb1ea3cba9..bae673b9735f 100644 --- a/tls/src/test/java/com/iluwatar/tls/AppTest.java +++ b/tls/src/test/java/com/iluwatar/tls/AppTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2016 Thomas Bauer + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,14 +27,12 @@ /** * Tests that thread local storage example runs without errors. - * - * @author Thomas Bauer, January 2017 * + * @author Thomas Bauer, January 2017 */ public class AppTest { @Test - public void test() throws Exception { - String[] args = {}; - App.main(args); + public void test() { + App.main(new String[]{}); } } diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java index bb2dfafb7231..48e5854a3465 100644 --- a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java +++ b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2016 Thomas Bauer + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,66 +23,62 @@ package com.iluwatar.tls; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + import java.util.ArrayList; -import java.util.Arrays; import java.util.Calendar; -import java.util.Date; import java.util.List; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; - import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - /** - * * Test of the Callable - * - * In this test {@link DateFormatCallable} is tested with only one thread (i.e. without concurrency situation) *

    - * After a successful run 5 date values should be in the result object. All dates should have - * the same value (15.11.2015). To avoid problems with time zone not the date instances themselves - * are compared by the test. For the test the dates are converted into string format DD.MM.YYY + * In this test {@link DateFormatCallable} is tested with only one thread (i.e. without concurrency + * situation) *

    - * Additionally the number of list entries are tested for both the list with the date values - * and the list with the exceptions - * - * @author Thomas Bauer, January 2017 + * After a successful run 5 date values should be in the result object. All dates should have the + * same value (15.11.2015). To avoid problems with time zone not the date instances themselves are + * compared by the test. For the test the dates are converted into string format DD.MM.YYY + *

    + * Additionally the number of list entries are tested for both the list with the date values and the + * list with the exceptions * + * @author Thomas Bauer, January 2017 */ public class DateFormatCallableTest { // Class variables used in setup() have to be static because setup() has to be static /** - * Result object given back by DateFormatCallable - * -- Array with converted date values - * -- Array with thrown exceptions + * Result object given back by DateFormatCallable -- Array with converted date values -- Array + * with thrown exceptions */ - static Result result; + private static Result result; /** - * The date values created by the run of of DateFormatRunnalbe. List will be filled in the setup() method + * The date values created by the run of of DateFormatRunnalbe. List will be filled in the setup() + * method */ - static List createdDateValues = new ArrayList(); + private static List createdDateValues = new ArrayList<>(); /** * Expected number of date values in the date value list created by the run of DateFormatRunnalbe */ - int expectedCounterDateValues = 5; + private int expectedCounterDateValues = 5; /** - * Expected number of exceptions in the exception list created by the run of DateFormatRunnalbe. + * Expected number of exceptions in the exception list created by the run of DateFormatRunnalbe. */ - int expectedCounterExceptions = 0; + private int expectedCounterExceptions = 0; /** - * Expected content of the list containing the date values created by the run of DateFormatRunnalbe + * Expected content of the list containing the date values created by the run of + * DateFormatRunnalbe */ - List expectedDateValues = Arrays.asList("15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015"); + private List expectedDateValues = + List.of("15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015"); /** * Run Callable and prepare results for usage in the test methods @@ -90,10 +86,10 @@ public class DateFormatCallableTest { @BeforeAll public static void setup() { // Create a callable - DateFormatCallable callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); + var callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); // start thread using the Callable instance - ExecutorService executor = Executors.newCachedThreadPool(); - Future futureResult = executor.submit(callableDf); + var executor = Executors.newCachedThreadPool(); + var futureResult = executor.submit(callableDf); try { result = futureResult.get(); createdDateValues = convertDatesToString(result); @@ -108,18 +104,22 @@ private static List convertDatesToString(Result res) { if (res == null || res.getDateList() == null || res.getDateList().size() == 0) { return null; } - List returnList = new ArrayList(); + var returnList = new ArrayList(); - for (Date dt : res.getDateList()) { - Calendar cal = Calendar.getInstance(); + for (var dt : res.getDateList()) { + var cal = Calendar.getInstance(); cal.setTime(dt); - returnList.add(cal.get(Calendar.DAY_OF_MONTH) + "." + cal.get(Calendar.MONTH) + "." + cal.get(Calendar.YEAR)); + returnList.add(cal.get(Calendar.DAY_OF_MONTH) + "." + + cal.get(Calendar.MONTH) + "." + + cal.get(Calendar.YEAR) + ); } return returnList; } /** - * Test date values after the run of DateFormatRunnalbe. A correct run should deliver 5 times 15.12.2015 + * Test date values after the run of DateFormatRunnalbe. A correct run should deliver 5 times + * 15.12.2015 */ @Test public void testDateValues() { @@ -127,7 +127,8 @@ public void testDateValues() { } /** - * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should deliver 5 date values + * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should + * deliver 5 date values */ @Test public void testCounterDateValues() { @@ -135,8 +136,8 @@ public void testCounterDateValues() { } /** - * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should deliver - * no exceptions + * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should + * deliver no exceptions */ @Test public void testCounterExceptions() { diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java index 1b21e00d7848..8b02faf0b683 100644 --- a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java +++ b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2016 Thomas Bauer + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,64 +23,55 @@ package com.iluwatar.tls; -import java.util.ArrayList; -import java.util.Arrays; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + import java.util.List; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; - import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - /** - * * Test of the Callable - * - * In this test {@link DateFormatCallable} is tested with only one thread (i.e. without concurrency situation) *

    - * An incorrect formatted date is passed to the Callable - * After a successful run 0 date values and 5 exceptions should be in the result object. - * - * @author Thomas Bauer, January 2017 + * In this test {@link DateFormatCallable} is tested with only one thread (i.e. without concurrency + * situation) + *

    + * An incorrect formatted date is passed to the Callable After a successful run 0 date values and 5 + * exceptions should be in the result object. * + * @author Thomas Bauer, January 2017 */ public class DateFormatCallableTestIncorrectDateFormat { // Class variables used in setup() have to be static because setup() has to be static /** - * Result object given back by DateFormatCallable - * -- Array with converted date values - * -- Array with thrown exceptions + * Result object given back by DateFormatCallable -- Array with converted date values -- Array + * with thrown exceptions */ - static Result result; - - /** - * The date values created by the run of DateFormatRunnalbe. List will be filled in the setup() method - */ - static List createdExceptions = new ArrayList(); + private static Result result; /** * Expected number of date values in the date value list created by the run of DateFormatRunnalbe */ - int expectedCounterDateValues = 0; + private int expectedCounterDateValues = 0; /** - * Expected number of exceptions in the exception list created by the run of DateFormatRunnalbe. + * Expected number of exceptions in the exception list created by the run of DateFormatRunnalbe. */ - int expectedCounterExceptions = 5; + private int expectedCounterExceptions = 5; /** - * Expected content of the list containing the exceptions created by the run of DateFormatRunnalbe + * Expected content of the list containing the exceptions created by the run of + * DateFormatRunnalbe */ - List expectedExceptions = Arrays.asList("class java.text.ParseException: Unparseable date: \"15.12.2015\"", + private List expectedExceptions = List.of( + "class java.text.ParseException: Unparseable date: \"15.12.2015\"", "class java.text.ParseException: Unparseable date: \"15.12.2015\"", "class java.text.ParseException: Unparseable date: \"15.12.2015\"", "class java.text.ParseException: Unparseable date: \"15.12.2015\"", - "class java.text.ParseException: Unparseable date: \"15.12.2015\""); + "class java.text.ParseException: Unparseable date: \"15.12.2015\"" + ); /** * Run Callable and prepare results for usage in the test methods @@ -88,10 +79,10 @@ public class DateFormatCallableTestIncorrectDateFormat { @BeforeAll public static void setup() { // Create a callable. Pass a string date value not matching the format string - DateFormatCallable callableDf = new DateFormatCallable("dd/MM/yyyy", "15.12.2015"); + var callableDf = new DateFormatCallable("dd/MM/yyyy", "15.12.2015"); // start thread using the Callable instance - ExecutorService executor = Executors.newCachedThreadPool(); - Future futureResult = executor.submit(callableDf); + var executor = Executors.newCachedThreadPool(); + var futureResult = executor.submit(callableDf); try { result = futureResult.get(); } catch (Exception e) { @@ -110,7 +101,8 @@ public void testExceptions() { } /** - * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should deliver no date values + * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should + * deliver no date values */ @Test public void testCounterDateValues() { @@ -118,7 +110,7 @@ public void testCounterDateValues() { } /** - * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should + * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should * deliver 5 exceptions */ @Test diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java index 845ab366dac3..c0e8e1844738 100644 --- a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java +++ b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java @@ -1,6 +1,6 @@ -/** +/* * The MIT License - * Copyright (c) 2016 Thomas Bauer + * Copyright © 2014-2019 Ilkka Seppälä * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,70 +23,66 @@ package com.iluwatar.tls; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + import java.util.ArrayList; -import java.util.Arrays; import java.util.Calendar; -import java.util.Date; import java.util.List; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; - import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - /** - * * Test of the Callable - * + *

    * In this test {@link DateFormatCallable} is used by 4 threads in parallel *

    - * After a successful run 5 date values should be in the result object of each thread. All dates + * After a successful run 5 date values should be in the result object of each thread. All dates * should have the same value (15.11.2015). To avoid problems with time zone not the date instances - * themselves are compared by the test. For the test the dates are converted into string format DD.MM.YYY + * themselves are compared by the test. For the test the dates are converted into string format + * DD.MM.YYY *

    - * Additionally the number of list entries are tested for both the list with the date values - * and the list with the exceptions - * - * @author Thomas Bauer, January 2017 + * Additionally the number of list entries are tested for both the list with the date values and the + * list with the exceptions * + * @author Thomas Bauer, January 2017 */ public class DateFormatCallableTestMultiThread { // Class variables used in setup() have to be static because setup() has to be static /** - * Result object given back by DateFormatCallable, one for each thread - * -- Array with converted date values - * -- Array with thrown exceptions + * Result object given back by DateFormatCallable, one for each thread -- Array with converted + * date values -- Array with thrown exceptions */ - static Result[] result = new Result[4]; + private static Result[] result = new Result[4]; /** - * The date values created by the run of of DateFormatRunnalbe. List will be filled in the setup() method + * The date values created by the run of of DateFormatRunnalbe. List will be filled in the setup() + * method */ @SuppressWarnings("serial") - static class StringArrayList extends ArrayList { + private static class StringArrayList extends ArrayList { /* nothing needed here */ } - static List[] createdDateValues = new StringArrayList[4]; + + private static List[] createdDateValues = new StringArrayList[4]; /** * Expected number of date values in the date value list created by each thread */ - int expectedCounterDateValues = 5; + private int expectedCounterDateValues = 5; /** - * Expected number of exceptions in the exception list created by each thread + * Expected number of exceptions in the exception list created by each thread */ - int expectedCounterExceptions = 0; + private int expectedCounterExceptions = 0; /** - * Expected content of the list containing the date values created by each thread + * Expected content of the list containing the date values created by each thread */ - List expectedDateValues = Arrays.asList("15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015"); + private List expectedDateValues = + List.of("15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015"); /** * Run Callable and prepare results for usage in the test methods @@ -94,19 +90,19 @@ static class StringArrayList extends ArrayList { @BeforeAll public static void setup() { // Create a callable - DateFormatCallable callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); + var callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); // start thread using the Callable instance - ExecutorService executor = Executors.newCachedThreadPool(); - Future futureResult1 = executor.submit(callableDf); - Future futureResult2 = executor.submit(callableDf); - Future futureResult3 = executor.submit(callableDf); - Future futureResult4 = executor.submit(callableDf); + var executor = Executors.newCachedThreadPool(); + var futureResult1 = executor.submit(callableDf); + var futureResult2 = executor.submit(callableDf); + var futureResult3 = executor.submit(callableDf); + var futureResult4 = executor.submit(callableDf); try { result[0] = futureResult1.get(); result[1] = futureResult2.get(); result[2] = futureResult3.get(); result[3] = futureResult4.get(); - for (int i = 0; i < result.length; i++) { + for (var i = 0; i < result.length; i++) { createdDateValues[i] = convertDatesToString(result[i]); } } catch (Exception e) { @@ -120,46 +116,49 @@ private static List convertDatesToString(Result res) { if (res == null || res.getDateList() == null || res.getDateList().size() == 0) { return null; } - List returnList = new StringArrayList(); + var returnList = new StringArrayList(); - for (Date dt : res.getDateList()) { - Calendar cal = Calendar.getInstance(); + for (var dt : res.getDateList()) { + var cal = Calendar.getInstance(); cal.setTime(dt); - returnList.add(cal.get(Calendar.DAY_OF_MONTH) + "." + cal.get(Calendar.MONTH) + "." + cal.get(Calendar.YEAR)); + returnList.add(cal.get(Calendar.DAY_OF_MONTH) + "." + + cal.get(Calendar.MONTH) + "." + + cal.get(Calendar.YEAR) + ); } return returnList; } /** - * Test date values after the run of DateFormatRunnalbe. A correct run should deliver 5 times 15.12.2015 - * by each thread + * Test date values after the run of DateFormatRunnalbe. A correct run should deliver 5 times + * 15.12.2015 by each thread */ @Test public void testDateValues() { - for (int i = 0; i < createdDateValues.length; i++) { - assertEquals(expectedDateValues, createdDateValues[i]); + for (var createdDateValue : createdDateValues) { + assertEquals(expectedDateValues, createdDateValue); } } /** - * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should + * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should * deliver 5 date values by each thread */ @Test public void testCounterDateValues() { - for (int i = 0; i < result.length; i++) { - assertEquals(expectedCounterDateValues, result[i].getDateList().size()); + for (var value : result) { + assertEquals(expectedCounterDateValues, value.getDateList().size()); } } /** - * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should + * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should * deliver no exceptions */ @Test public void testCounterExceptions() { - for (int i = 0; i < result.length; i++) { - assertEquals(expectedCounterExceptions, result[i].getExceptionList().size()); + for (var value : result) { + assertEquals(expectedCounterExceptions, value.getExceptionList().size()); } } } diff --git a/tolerant-reader/README.md b/tolerant-reader/README.md index be0085f2c571..c60e757073df 100644 --- a/tolerant-reader/README.md +++ b/tolerant-reader/README.md @@ -5,8 +5,7 @@ folder: tolerant-reader permalink: /patterns/tolerant-reader/ categories: Integration tags: - - Java - - Difficulty-Beginner + - Decoupling --- ## Intent @@ -15,12 +14,13 @@ robust communication systems. The idea is to be as tolerant as possible when reading data from another service. This way, when the communication schema changes, the readers must not break. +## Class diagram ![alt text](./etc/tolerant-reader.png "Tolerant Reader") ## Applicability Use the Tolerant Reader pattern when -* the communication schema can evolve and change and yet the receiving side should not break +* The communication schema can evolve and change and yet the receiving side should not break ## Credits diff --git a/tolerant-reader/etc/tolerant-reader.urm.puml b/tolerant-reader/etc/tolerant-reader.urm.puml new file mode 100644 index 000000000000..a73394a4ed7d --- /dev/null +++ b/tolerant-reader/etc/tolerant-reader.urm.puml @@ -0,0 +1,39 @@ +@startuml +package com.iluwatar.tolerantreader { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + class RainbowFish { + - age : int + - lengthMeters : int + - name : String + - serialVersionUID : long {static} + - weightTons : int + + RainbowFish(name : String, age : int, lengthMeters : int, weightTons : int) + + getAge() : int + + getLengthMeters() : int + + getName() : String + + getWeightTons() : int + } + class RainbowFishSerializer { + - RainbowFishSerializer() + + readV1(filename : String) : RainbowFish {static} + + writeV1(rainbowFish : RainbowFish, filename : String) {static} + + writeV2(rainbowFish : RainbowFishV2, filename : String) {static} + } + class RainbowFishV2 { + - angry : boolean + - hungry : boolean + - serialVersionUID : long {static} + - sleeping : boolean + + RainbowFishV2(name : String, age : int, lengthMeters : int, weightTons : int) + + RainbowFishV2(name : String, age : int, lengthMeters : int, weightTons : int, sleeping : boolean, hungry : boolean, angry : boolean) + + getAngry() : boolean + + getHungry() : boolean + + getSleeping() : boolean + } +} +RainbowFishV2 --|> RainbowFish +@enduml \ No newline at end of file diff --git a/tolerant-reader/pom.xml b/tolerant-reader/pom.xml index 8b4b1bbc53c6..c0ebaeaec7e6 100644 --- a/tolerant-reader/pom.xml +++ b/tolerant-reader/pom.xml @@ -2,7 +2,7 @@ "-candy" Candy +Type ..+ Candy +Candy --> "-type" Type +Candy --> "-parent" Candy +CandyGame --> "-pool" CellPool +CellPool --> "-pool" Cell +@enduml \ No newline at end of file diff --git a/typeobjectpattern/pom.xml b/typeobjectpattern/pom.xml new file mode 100644 index 000000000000..0d56a8376fdb --- /dev/null +++ b/typeobjectpattern/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.23.0-SNAPSHOT + + typeobjectpattern + + + com.googlecode.json-simple + json-simple + 1.1.1 + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/App.java b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/App.java new file mode 100644 index 000000000000..e70acbf9e0d6 --- /dev/null +++ b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/App.java @@ -0,0 +1,91 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.typeobject; + +import java.io.FileNotFoundException; +import java.io.IOException; +import org.json.simple.parser.ParseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

    Type object pattern is the pattern we use when the OOP concept of creating a base class and + * inheriting from it just doesn't work for the case in hand. This happens when we either don't know + * what types we will need upfront, or want to be able to modify or add new types conveniently w/o + * recompiling repeatedly. The pattern provides a solution by allowing flexible creation of required + * objects by creating one class, which has a field which represents the 'type' of the object.

    + *

    In this example, we have a mini candy-crush game in action. There are many different candies + * in the game, which may change over time, as we may want to upgrade the game. To make the object + * creation convenient, we have a class {@link Candy} which has a field name, parent, points and + * Type. We have a json file {@link candy} which contains the details about the candies, and this is + * parsed to get all the different candies in {@link JsonParser}. The {@link Cell} class is what the + * game matrix is made of, which has the candies that are to be crushed, and contains information on + * how crushing can be done, how the matrix is to be reconfigured and how points are to be gained. + * The {@link CellPool} class is a pool which reuses the candy cells that have been crushed instead + * of making new ones repeatedly. The {@link CandyGame} class has the rules for the continuation of + * the game and the {@link App} class has the game itself.

    + */ + +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) throws FileNotFoundException, IOException, ParseException { + var givenTime = 50; //50ms + var toWin = 500; //points + var pointsWon = 0; + var numOfRows = 3; + var start = System.currentTimeMillis(); + var end = System.currentTimeMillis(); + var round = 0; + while (pointsWon < toWin && end - start < givenTime) { + round++; + var pool = new CellPool(numOfRows * numOfRows + 5); + var cg = new CandyGame(numOfRows, pool); + if (round > 1) { + LOGGER.info("Refreshing.."); + } else { + LOGGER.info("Starting game.."); + } + cg.printGameStatus(); + end = System.currentTimeMillis(); + cg.round((int) (end - start), givenTime); + pointsWon += cg.totalPoints; + end = System.currentTimeMillis(); + } + LOGGER.info("Game Over"); + if (pointsWon >= toWin) { + LOGGER.info("" + pointsWon); + LOGGER.info("You win!!"); + } else { + LOGGER.info("" + pointsWon); + LOGGER.info("Sorry, you lose!"); + } + } +} diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Candy.java b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Candy.java new file mode 100644 index 000000000000..ec41dc6cdb16 --- /dev/null +++ b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Candy.java @@ -0,0 +1,62 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.typeobject; + +/** + * The Candy class has a field type, which represents the 'type' of candy. The objects are created + * by parsing the candy.json file. + */ +public class Candy { + + enum Type { + crushableCandy, + rewardFruit + } + + String name; + Candy parent; + String parentName; + private int points; + private Type type; + + Candy(String name, String parentName, Type type, int points) { + this.name = name; + this.parent = null; + this.type = type; + this.points = points; + this.parentName = parentName; + } + + int getPoints() { + return this.points; + } + + void setPoints(int a) { + this.points = a; + } + + Type getType() { + return this.type; + } +} diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CandyGame.java b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CandyGame.java new file mode 100644 index 000000000000..04e281a993c3 --- /dev/null +++ b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CandyGame.java @@ -0,0 +1,173 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.typeobject; + +import com.iluwatar.typeobject.Candy.Type; +import java.util.ArrayList; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The CandyGame class contains the rules for the continuation of the game and has the game matrix + * (field 'cells') and totalPoints gained during the game. + */ + +public class CandyGame { + + private static final Logger LOGGER = LoggerFactory.getLogger(CandyGame.class); + + Cell[][] cells; + CellPool pool; + int totalPoints; + + CandyGame(int num, CellPool pool) { + this.cells = new Cell[num][num]; + this.pool = pool; + this.totalPoints = 0; + for (var i = 0; i < num; i++) { + for (var j = 0; j < num; j++) { + this.cells[i][j] = this.pool.getNewCell(); + this.cells[i][j].positionX = j; + this.cells[i][j].positionY = i; + } + } + } + + static String numOfSpaces(int num) { + return " ".repeat(Math.max(0, num)); + } + + void printGameStatus() { + LOGGER.info(""); + for (Cell[] cell : cells) { + for (var j = 0; j < cells.length; j++) { + var candyName = cell[j].candy.name; + if (candyName.length() < 20) { + var totalSpaces = 20 - candyName.length(); + LOGGER.info(numOfSpaces(totalSpaces / 2) + cell[j].candy.name + + numOfSpaces(totalSpaces - totalSpaces / 2) + "|"); + } else { + LOGGER.info(candyName + "|"); + } + } + LOGGER.info(""); + } + LOGGER.info(""); + } + + List adjacentCells(int y, int x) { + var adjacent = new ArrayList(); + if (y == 0) { + adjacent.add(this.cells[1][x]); + } + if (x == 0) { + adjacent.add(this.cells[y][1]); + } + if (y == cells.length - 1) { + adjacent.add(this.cells[cells.length - 2][x]); + } + if (x == cells.length - 1) { + adjacent.add(this.cells[y][cells.length - 2]); + } + if (y > 0 && y < cells.length - 1) { + adjacent.add(this.cells[y - 1][x]); + adjacent.add(this.cells[y + 1][x]); + } + if (x > 0 && x < cells.length - 1) { + adjacent.add(this.cells[y][x - 1]); + adjacent.add(this.cells[y][x + 1]); + } + return adjacent; + } + + boolean continueRound() { + for (var i = 0; i < this.cells.length; i++) { + if (this.cells[cells.length - 1][i].candy.getType().equals(Type.rewardFruit)) { + return true; + } + } + for (var i = 0; i < this.cells.length; i++) { + for (var j = 0; j < this.cells.length; j++) { + if (!this.cells[i][j].candy.getType().equals(Type.rewardFruit)) { + var adj = adjacentCells(i, j); + for (Cell cell : adj) { + if (this.cells[i][j].candy.name.equals(cell.candy.name)) { + return true; + } + } + } + } + } + return false; + } + + void handleChange(int points) { + LOGGER.info("+" + points + " points!"); + this.totalPoints += points; + printGameStatus(); + } + + void round(int timeSoFar, int totalTime) { + var start = System.currentTimeMillis(); + var end = System.currentTimeMillis(); + while (end - start + timeSoFar < totalTime && continueRound()) { + for (var i = 0; i < this.cells.length; i++) { + var points = 0; + var j = this.cells.length - 1; + while (this.cells[j][i].candy.getType().equals(Type.rewardFruit)) { + points = this.cells[j][i].candy.getPoints(); + this.cells[j][i].crush(pool, this.cells); + handleChange(points); + } + } + for (var i = 0; i < this.cells.length; i++) { + var j = cells.length - 1; + var points = 0; + while (j > 0) { + points = this.cells[j][i].interact(this.cells[j - 1][i], this.pool, this.cells); + if (points != 0) { + handleChange(points); + } else { + j = j - 1; + } + } + } + for (Cell[] cell : this.cells) { + var j = 0; + var points = 0; + while (j < cells.length - 1) { + points = cell[j].interact(cell[j + 1], this.pool, this.cells); + if (points != 0) { + handleChange(points); + } else { + j = j + 1; + } + } + } + end = System.currentTimeMillis(); + } + } + +} diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Cell.java b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Cell.java new file mode 100644 index 000000000000..e4d9d497fb52 --- /dev/null +++ b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/Cell.java @@ -0,0 +1,90 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.typeobject; + +import com.iluwatar.typeobject.Candy.Type; + +/** + * The Cell object is what the game matrix is made of and contains the candy which is to be crushed + * or collected as reward. + */ +public class Cell { + Candy candy; + int positionX; + int positionY; + + Cell(Candy candy, int positionX, int positionY) { + this.candy = candy; + this.positionX = positionX; + this.positionY = positionY; + } + + Cell() { + this.candy = null; + this.positionX = 0; + this.positionY = 0; + } + + void crush(CellPool pool, Cell[][] cellMatrix) { + //take out from this position and put back in pool + pool.addNewCell(this); + this.fillThisSpace(pool, cellMatrix); + } + + void fillThisSpace(CellPool pool, Cell[][] cellMatrix) { + for (var y = this.positionY; y > 0; y--) { + cellMatrix[y][this.positionX] = cellMatrix[y - 1][this.positionX]; + cellMatrix[y][this.positionX].positionY = y; + } + var newC = pool.getNewCell(); + cellMatrix[0][this.positionX] = newC; + cellMatrix[0][this.positionX].positionX = this.positionX; + cellMatrix[0][this.positionX].positionY = 0; + } + + void handleCrush(Cell c, CellPool pool, Cell[][] cellMatrix) { + if (this.positionY >= c.positionY) { + this.crush(pool, cellMatrix); + c.crush(pool, cellMatrix); + } else { + c.crush(pool, cellMatrix); + this.crush(pool, cellMatrix); + } + } + + int interact(Cell c, CellPool pool, Cell[][] cellMatrix) { + if (this.candy.getType().equals(Type.rewardFruit) || c.candy.getType() + .equals(Type.rewardFruit)) { + return 0; + } else { + if (this.candy.name.equals(c.candy.name)) { + var pointsWon = this.candy.getPoints() + c.candy.getPoints(); + handleCrush(c, pool, cellMatrix); + return pointsWon; + } else { + return 0; + } + } + } +} diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CellPool.java b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CellPool.java new file mode 100644 index 000000000000..553458a6d0d7 --- /dev/null +++ b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/CellPool.java @@ -0,0 +1,95 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.typeobject; + +import com.iluwatar.typeobject.Candy.Type; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import org.json.simple.parser.ParseException; + +/** + * The CellPool class allows the reuse of crushed cells instead of creation of new cells each time. + * The reused cell is given a new candy to hold using the randomCode field which holds all the + * candies available. + */ + +public class CellPool { + private static final Random RANDOM = new Random(); + List pool; + int pointer; + Candy[] randomCode; + + CellPool(int num) { + this.pool = new ArrayList<>(num); + try { + this.randomCode = assignRandomCandytypes(); + } catch (Exception e) { + e.printStackTrace(); + //manually initialising this.randomCode + this.randomCode = new Candy[5]; + randomCode[0] = new Candy("cherry", "fruit", Type.rewardFruit, 20); + randomCode[1] = new Candy("mango", "fruit", Type.rewardFruit, 20); + randomCode[2] = new Candy("purple popsicle", "candy", Type.crushableCandy, 10); + randomCode[3] = new Candy("green jellybean", "candy", Type.crushableCandy, 10); + randomCode[4] = new Candy("orange gum", "candy", Type.crushableCandy, 10); + } + for (int i = 0; i < num; i++) { + var c = new Cell(); + c.candy = randomCode[RANDOM.nextInt(randomCode.length)]; + this.pool.add(c); + } + this.pointer = num - 1; + } + + Cell getNewCell() { + var newCell = this.pool.remove(pointer); + pointer--; + return newCell; + } + + void addNewCell(Cell c) { + c.candy = randomCode[RANDOM.nextInt(randomCode.length)]; //changing candytype to new + this.pool.add(c); + pointer++; + } + + Candy[] assignRandomCandytypes() throws FileNotFoundException, IOException, ParseException { + var jp = new JsonParser(); + jp.parse(); + var randomCode = new Candy[jp.candies.size() - 2]; //exclude generic types 'fruit' and 'candy' + var i = 0; + for (var e = jp.candies.keys(); e.hasMoreElements(); ) { + var s = e.nextElement(); + if (!s.equals("fruit") && !s.equals("candy")) { + //not generic + randomCode[i] = jp.candies.get(s); + i++; + } + } + return randomCode; + } +} diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/JsonParser.java b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/JsonParser.java new file mode 100644 index 000000000000..01e709c8f24f --- /dev/null +++ b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/JsonParser.java @@ -0,0 +1,86 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.typeobject; + +import com.iluwatar.typeobject.Candy.Type; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Hashtable; +import java.util.List; +import java.util.stream.Collectors; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +/** + * The JsonParser class helps parse the json file candy.json to get all the different candies. + */ + +public class JsonParser { + Hashtable candies; + + JsonParser() { + this.candies = new Hashtable<>(); + } + + void parse() throws IOException, ParseException { + var parser = new JSONParser(); + var workingDirectory = new File("").getAbsolutePath(); + var filePath = List.of("src", "main", "java", "com", "iluwatar", "typeobject", "candy.json"); + var absolutePath = workingDirectory + File.separator + String.join(File.separator, filePath); + var jo = (JSONObject) parser.parse(new FileReader(absolutePath)); + var a = (JSONArray) jo.get("candies"); + for (var o : a) { + var candy = (JSONObject) o; + var name = (String) candy.get("name"); + var parentName = (String) candy.get("parent"); + var t = (String) candy.get("type"); + var type = Type.crushableCandy; + if (t.equals("rewardFruit")) { + type = Type.rewardFruit; + } + var points = Integer.parseInt((String) candy.get("points")); + var c = new Candy(name, parentName, type, points); + this.candies.put(name, c); + } + setParentAndPoints(); + } + + void setParentAndPoints() { + for (var e = this.candies.keys(); e.hasMoreElements(); ) { + var c = this.candies.get(e.nextElement()); + if (c.parentName == null) { + c.parent = null; + } else { + c.parent = this.candies.get(c.parentName); + } + if (c.getPoints() == 0 && c.parent != null) { + c.setPoints(c.parent.getPoints()); + } + } + } + +} diff --git a/typeobjectpattern/src/main/java/com/iluwatar/typeobject/candy.json b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/candy.json new file mode 100644 index 000000000000..74430c318d72 --- /dev/null +++ b/typeobjectpattern/src/main/java/com/iluwatar/typeobject/candy.json @@ -0,0 +1,45 @@ +{"candies" : [ + { + "name" : "fruit", + "parent" : "null", + "type" : "rewardFruit", + "points" : "20" + }, + { + "name" : "candy", + "parent" : "null", + "type" : "crushableCandy", + "points" : "10" + }, + { + "name" : "cherry", + "parent" : "fruit", + "type" : "rewardFruit", + "points" : "0" + }, + { + "name" : "mango", + "parent" : "fruit", + "type" : "rewardFruit", + "points" : "0" + }, + { + "name" : "purple popsicle", + "parent" : "candy", + "type" : "crushableCandy", + "points" : "0" + }, + { + "name" : "green jellybean", + "parent" : "candy", + "type" : "crushableCandy", + "points" : "0" + }, + { + "name" : "orange gum", + "parent" : "candy", + "type" : "crushableCandy", + "points" : "0" + } + ] +} diff --git a/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CandyGameTest.java b/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CandyGameTest.java new file mode 100644 index 000000000000..8175d1dd0324 --- /dev/null +++ b/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CandyGameTest.java @@ -0,0 +1,69 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.typeobject; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.iluwatar.typeobject.Candy.Type; +import org.junit.jupiter.api.Test; + +/** + * The CandyGameTest class tests the methods in the {@link CandyGame} class. + */ + +class CandyGameTest { + + @Test + void adjacentCellsTest() { + var cg = new CandyGame(3, new CellPool(9)); + var arr1 = cg.adjacentCells(0, 0); + var arr2 = cg.adjacentCells(1, 2); + var arr3 = cg.adjacentCells(1, 1); + assertTrue(arr1.size() == 2 && arr2.size() == 3 && arr3.size() == 4); + } + + @Test + void continueRoundTest() { + var matrix = new Cell[2][2]; + var c1 = new Candy("green jelly", "jelly", Type.crushableCandy, 5); + var c2 = new Candy("purple jelly", "jelly", Type.crushableCandy, 5); + var c3 = new Candy("green apple", "apple", Type.rewardFruit, 10); + matrix[0][0] = new Cell(c1, 0, 0); + matrix[0][1] = new Cell(c2, 1, 0); + matrix[1][0] = new Cell(c3, 0, 1); + matrix[1][1] = new Cell(c2, 1, 1); + var p = new CellPool(4); + var cg = new CandyGame(2, p); + cg.cells = matrix; + var fruitInLastRow = cg.continueRound(); + matrix[1][0].crush(p, matrix); + matrix[0][0] = new Cell(c3, 0, 0); + var matchingCandy = cg.continueRound(); + matrix[0][1].crush(p, matrix); + matrix[0][1] = new Cell(c3, 1, 0); + var noneLeft = cg.continueRound(); + assertTrue(fruitInLastRow && matchingCandy && !noneLeft); + } + +} diff --git a/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellPoolTest.java b/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellPoolTest.java new file mode 100644 index 000000000000..777dae2dddb5 --- /dev/null +++ b/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellPoolTest.java @@ -0,0 +1,51 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.typeobject; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Hashtable; +import org.junit.jupiter.api.Test; + +/** + * The CellPoolTest class tests the methods in the {@link CellPool} class. + */ + +class CellPoolTest { + + @Test + void assignRandomCandyTypesTest() { + var cp = new CellPool(10); + var ht = new Hashtable(); + var parentTypes = 0; + for (var i = 0; i < cp.randomCode.length; i++) { + ht.putIfAbsent(cp.randomCode[i].name, true); + if (cp.randomCode[i].name.equals("fruit") || cp.randomCode[i].name.equals("candy")) { + parentTypes++; + } + } + assertTrue(ht.size() == 5 && parentTypes == 0); + } + +} diff --git a/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellTest.java b/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellTest.java new file mode 100644 index 000000000000..18bca62bb50e --- /dev/null +++ b/typeobjectpattern/src/test/java/com/iluwatar/typeobject/CellTest.java @@ -0,0 +1,62 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.typeobject; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.iluwatar.typeobject.Candy.Type; +import org.junit.jupiter.api.Test; + +/** + * The CellTest class tests the methods in the {@link Cell} class. + */ +class CellTest { + + @Test + void interactTest() { + var c1 = new Candy("green jelly", "jelly", Type.crushableCandy, 5); + var c2 = new Candy("green apple", "apple", Type.rewardFruit, 10); + var matrix = new Cell[4][4]; + matrix[0][0] = new Cell(c1, 0, 0); + matrix[0][1] = new Cell(c1, 1, 0); + matrix[0][2] = new Cell(c2, 2, 0); + matrix[0][3] = new Cell(c1, 3, 0); + var cp = new CellPool(5); + var points1 = matrix[0][0].interact(matrix[0][1], cp, matrix); + var points2 = matrix[0][2].interact(matrix[0][3], cp, matrix); + assertTrue(points1 > 0 && points2 == 0); + } + + @Test + void crushTest() { + var c1 = new Candy("green jelly", "jelly", Type.crushableCandy, 5); + var c2 = new Candy("purple candy", "candy", Type.crushableCandy, 5); + var matrix = new Cell[4][4]; + matrix[0][0] = new Cell(c1, 0, 0); + matrix[1][0] = new Cell(c2, 0, 1); + matrix[1][0].crush(new CellPool(5), matrix); + assertEquals("green jelly", matrix[1][0].candy.name); + } +} diff --git a/unit-of-work/README.md b/unit-of-work/README.md index b2fbde8ff3a6..21f653b7df34 100644 --- a/unit-of-work/README.md +++ b/unit-of-work/README.md @@ -6,16 +6,14 @@ permalink: /patterns/unit-of-work/ categories: Architectural tags: - - Java - - KISS - - YAGNI - - Difficulty-Beginner + - Data access --- ## Intent When a business transaction is completed, all the these updates are sent as one big unit of work to be persisted in a database in one go so as to minimize database trips. +## Class diagram ![alt text](etc/unit-of-work.urm.png "unit-of-work") ## Applicability diff --git a/unit-of-work/pom.xml b/unit-of-work/pom.xml index b53075f21e8a..a92cc4110100 100644 --- a/unit-of-work/pom.xml +++ b/unit-of-work/pom.xml @@ -2,7 +2,7 @@ "-entities" Entity +Skeleton --|> Entity +Statue --|> Entity +@enduml \ No newline at end of file diff --git a/update-method/pom.xml b/update-method/pom.xml new file mode 100644 index 000000000000..ede79f8f6c06 --- /dev/null +++ b/update-method/pom.xml @@ -0,0 +1,44 @@ + + + + + + java-design-patterns + com.iluwatar + 1.23.0-SNAPSHOT + + 4.0.0 + + update-method + + + junit + junit + + + + + \ No newline at end of file diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/App.java b/update-method/src/main/java/com/iluwatar/updatemethod/App.java new file mode 100644 index 000000000000..45069a3dee21 --- /dev/null +++ b/update-method/src/main/java/com/iluwatar/updatemethod/App.java @@ -0,0 +1,61 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.updatemethod; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This pattern simulate a collection of independent objects by telling each to + * process one frame of behavior at a time. The game world maintains a collection + * of objects. Each object implements an update method that simulates one frame of + * the object’s behavior. Each frame, the game updates every object in the collection. + */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + private static final int GAME_RUNNING_TIME = 2000; + + /** + * Program entry point. + * @param args runtime arguments + */ + public static void main(String[] args) { + try { + var world = new World(); + var skeleton1 = new Skeleton(1, 10); + var skeleton2 = new Skeleton(2, 70); + var statue = new Statue(3, 20); + world.addEntity(skeleton1); + world.addEntity(skeleton2); + world.addEntity(statue); + world.run(); + Thread.sleep(GAME_RUNNING_TIME); + world.stop(); + } catch (InterruptedException e) { + LOGGER.error(e.getMessage()); + } + } +} diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/Entity.java b/update-method/src/main/java/com/iluwatar/updatemethod/Entity.java new file mode 100644 index 000000000000..0f10d77758cd --- /dev/null +++ b/update-method/src/main/java/com/iluwatar/updatemethod/Entity.java @@ -0,0 +1,54 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.updatemethod; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract class for all the entity types. + */ +public abstract class Entity { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + protected int id; + + protected int position; + + public Entity(int id) { + this.id = id; + this.position = 0; + } + + public abstract void update(); + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } +} diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/Skeleton.java b/update-method/src/main/java/com/iluwatar/updatemethod/Skeleton.java new file mode 100644 index 000000000000..e365d6aecfcb --- /dev/null +++ b/update-method/src/main/java/com/iluwatar/updatemethod/Skeleton.java @@ -0,0 +1,78 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.updatemethod; + +/** + * Skeletons are always patrolling on the game map. Initially all the skeletons + * patrolling to the right, and after them reach the bounding, it will start + * patrolling to the left. For each frame, one skeleton will move 1 position + * step. + */ +public class Skeleton extends Entity { + + private static final int PATROLLING_LEFT_BOUNDING = 0; + + private static final int PATROLLING_RIGHT_BOUNDING = 100; + + protected boolean patrollingLeft; + + /** + * Constructor of Skeleton. + * + * @param id id of skeleton + */ + public Skeleton(int id) { + super(id); + patrollingLeft = false; + } + + /** + * Constructor of Skeleton. + * + * @param id id of skeleton + * @param postition position of skeleton + */ + public Skeleton(int id, int postition) { + super(id); + this.position = position; + patrollingLeft = false; + } + + @Override + public void update() { + if (patrollingLeft) { + position -= 1; + if (position == PATROLLING_LEFT_BOUNDING) { + patrollingLeft = false; + } + } else { + position += 1; + if (position == PATROLLING_RIGHT_BOUNDING) { + patrollingLeft = true; + } + } + logger.info("Skeleton " + id + " is on position " + position + "."); + } +} + diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/Statue.java b/update-method/src/main/java/com/iluwatar/updatemethod/Statue.java new file mode 100644 index 000000000000..f1f3e2a87a6f --- /dev/null +++ b/update-method/src/main/java/com/iluwatar/updatemethod/Statue.java @@ -0,0 +1,69 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.updatemethod; + +/** + * Statues shoot lightning at regular intervals. + */ +public class Statue extends Entity { + + protected int frames; + + protected int delay; + + /** + * Constructor of Statue. + * + * @param id id of statue + */ + public Statue(int id) { + super(id); + this.frames = 0; + this.delay = 0; + } + + /** + * Constructor of Statue. + * + * @param id id of statue + * @param delay the number of frames between two lightning + */ + public Statue(int id, int delay) { + super(id); + this.frames = 0; + this.delay = delay; + } + + @Override + public void update() { + if (++ frames == delay) { + shootLightning(); + frames = 0; + } + } + + private void shootLightning() { + logger.info("Statue " + id + " shoots lightning!"); + } +} diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/World.java b/update-method/src/main/java/com/iluwatar/updatemethod/World.java new file mode 100644 index 000000000000..8cabead56f00 --- /dev/null +++ b/update-method/src/main/java/com/iluwatar/updatemethod/World.java @@ -0,0 +1,114 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.updatemethod; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The game world class. Maintain all the objects existed in the game frames. + */ +public class World { + + private static final Logger LOGGER = LoggerFactory.getLogger(World.class); + + protected List entities; + + protected volatile boolean isRunning; + + public World() { + entities = new ArrayList<>(); + isRunning = false; + } + + /** + * Main game loop. This loop will always run until the game is over. For + * each loop it will process user input, update internal status, and render + * the next frames. For more detail please refer to the game-loop pattern. + */ + private void gameLoop() { + while (isRunning) { + processInput(); + update(); + render(); + } + } + + /** + * Handle any user input that has happened since the last call. In order to + * simulate the situation in real-life game, here we add a random time lag. + * The time lag ranges from 50 ms to 250 ms. + */ + private void processInput() { + try { + int lag = new Random().nextInt(200) + 50; + Thread.sleep(lag); + } catch (InterruptedException e) { + LOGGER.error(e.getMessage()); + } + } + + /** + * Update internal status. The update method pattern invoke udpate method for + * each entity in the game. + */ + private void update() { + for (var entity : entities) { + entity.update(); + } + } + + /** + * Render the next frame. Here we do nothing since it is not related to the + * pattern. + */ + private void render() {} + + /** + * Run game loop. + */ + public void run() { + LOGGER.info("Start game."); + isRunning = true; + var thread = new Thread(this::gameLoop); + thread.start(); + } + + /** + * Stop game loop. + */ + public void stop() { + LOGGER.info("Stop game."); + isRunning = false; + } + + public void addEntity(Entity entity) { + entities.add(entity); + } + +} diff --git a/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java b/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java new file mode 100644 index 000000000000..75c30470d1b7 --- /dev/null +++ b/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java @@ -0,0 +1,35 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.updatemethod; + +import org.junit.Test; + +public class AppTest { + + @Test + public void testMain() { + String[] args = {}; + App.main(args); + } +} diff --git a/update-method/src/test/java/com/iluwatar/updatemethod/SkeletonTest.java b/update-method/src/test/java/com/iluwatar/updatemethod/SkeletonTest.java new file mode 100644 index 000000000000..73ab9eb1dc13 --- /dev/null +++ b/update-method/src/test/java/com/iluwatar/updatemethod/SkeletonTest.java @@ -0,0 +1,78 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.updatemethod; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class SkeletonTest { + + private Skeleton skeleton; + + @Before + public void setup() { + skeleton = new Skeleton(1); + } + + @After + public void tearDown() { + skeleton = null; + } + + @Test + public void testUpdateForPatrollingLeft() { + skeleton.patrollingLeft = true; + skeleton.setPosition(50); + skeleton.update(); + Assert.assertEquals(49, skeleton.getPosition()); + } + + @Test + public void testUpdateForPatrollingRight() { + skeleton.patrollingLeft = false; + skeleton.setPosition(50); + skeleton.update(); + Assert.assertEquals(51, skeleton.getPosition()); + } + + @Test + public void testUpdateForReverseDirectionFromLeftToRight() { + skeleton.patrollingLeft = true; + skeleton.setPosition(1); + skeleton.update(); + Assert.assertEquals(0, skeleton.getPosition()); + Assert.assertEquals(false, skeleton.patrollingLeft); + } + + @Test + public void testUpdateForReverseDirectionFromRightToLeft() { + skeleton.patrollingLeft = false; + skeleton.setPosition(99); + skeleton.update(); + Assert.assertEquals(100, skeleton.getPosition()); + Assert.assertEquals(true, skeleton.patrollingLeft); + } +} diff --git a/update-method/src/test/java/com/iluwatar/updatemethod/StatueTest.java b/update-method/src/test/java/com/iluwatar/updatemethod/StatueTest.java new file mode 100644 index 000000000000..2d523237cfc3 --- /dev/null +++ b/update-method/src/test/java/com/iluwatar/updatemethod/StatueTest.java @@ -0,0 +1,58 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.updatemethod; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class StatueTest { + + private Statue statue; + + @Before + public void setup() { + statue = new Statue(1, 20); + } + + @After + public void tearDown() { + statue = null; + } + + @Test + public void testUpdateForPendingShoot() { + statue.frames = 10; + statue.update(); + Assert.assertEquals(11, statue.frames); + } + + @Test + public void testUpdateForShooting() { + statue.frames = 19; + statue.update(); + Assert.assertEquals(0, statue.frames); + } +} diff --git a/update-method/src/test/java/com/iluwatar/updatemethod/WorldTest.java b/update-method/src/test/java/com/iluwatar/updatemethod/WorldTest.java new file mode 100644 index 000000000000..2a9dad316ba5 --- /dev/null +++ b/update-method/src/test/java/com/iluwatar/updatemethod/WorldTest.java @@ -0,0 +1,63 @@ +/* + * The MIT License + * Copyright © 2014-2019 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.updatemethod; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class WorldTest { + + private World world; + + @Before + public void setup() { + world = new World(); + } + + @After + public void tearDown() { + world = null; + } + + @Test + public void testRun() { + world.run(); + Assert.assertEquals(true, world.isRunning); + } + + @Test + public void testStop() { + world.stop(); + Assert.assertEquals(false, world.isRunning); + } + + @Test + public void testAddEntity() { + var entity = new Skeleton(1); + world.addEntity(entity); + Assert.assertEquals(entity, world.entities.get(0)); + } +} diff --git a/update-ghpages.sh b/update-website.sh similarity index 72% rename from update-ghpages.sh rename to update-website.sh index d240453b0755..66192f15f82a 100644 --- a/update-ghpages.sh +++ b/update-website.sh @@ -1,7 +1,7 @@ #!/bin/bash # # The MIT License -# Copyright (c) 2014-2016 Ilkka Seppälä +# Copyright © 2014-2019 Ilkka Seppälä # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -24,22 +24,23 @@ # Clone gh-pages -git clone -b gh-pages "https://${GH_REF}" ghpagesclone -cd ghpagesclone +git clone https://github.com/iluwatar/java-design-patterns-web.git +cd java-design-patterns-web # Init and update submodule to latest git submodule update --init --recursive -git submodule update --remote +cd 30-seconds-of-java +git pull origin master +cd ../patterns +git pull origin master +cd ../programming-principles +git pull origin gh-pages +cd .. # Setup Git git config user.name "Travis-CI" git config user.email "travis@no.reply" -# If there is a new version of the master branch -if git status | grep patterns > /dev/null 2>&1 -then - # it should be committed - git add . - git commit -m ":sparkles: :up: Automagic Update via Travis-CI" - git push --quiet "https://${GH_TOKEN}:x-oauth-basic@${GH_REF}" gh-pages > /dev/null 2>&1 -fi +git add . +git commit -m ":sparkles: :up: Automagic Update via Travis-CI" +git push --quiet "https://${GH_TOKEN}:x-oauth-basic@github.com/iluwatar/java-design-patterns-web.git" master > /dev/null 2>&1 diff --git a/value-object/README.md b/value-object/README.md index 83223d8a2377..ee6d7143bfe0 100644 --- a/value-object/README.md +++ b/value-object/README.md @@ -5,8 +5,7 @@ folder: value-object permalink: /patterns/value-object/ categories: Creational tags: - - Java - - Difficulty-Beginner + - Instantiation --- ## Intent @@ -14,12 +13,13 @@ Provide objects which follow value semantics rather than reference semantics. This means value objects' equality are not based on identity. Two value objects are equal when they have the same value, not necessarily being the same object. +## Class diagram ![alt text](./etc/value-object.png "Value Object") ## Applicability Use the Value Object when -* you need to measure the objects' equality based on the objects' value +* You need to measure the objects' equality based on the objects' value ## Real world examples diff --git a/value-object/etc/value-object.urm.puml b/value-object/etc/value-object.urm.puml new file mode 100644 index 000000000000..6149ead9ba36 --- /dev/null +++ b/value-object/etc/value-object.urm.puml @@ -0,0 +1,22 @@ +@startuml +package com.iluwatar.value.object { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + class HeroStat { + - intelligence : int + - luck : int + - strength : int + - HeroStat(strength : int, intelligence : int, luck : int) + + equals(obj : Object) : boolean + + getIntelligence() : int + + getLuck() : int + + getStrength() : int + + hashCode() : int + + toString() : String + + valueOf(strength : int, intelligence : int, luck : int) : HeroStat {static} + } +} +@enduml \ No newline at end of file diff --git a/value-object/pom.xml b/value-object/pom.xml index 5a942ff3067a..bf8e4a1e2696 100644 --- a/value-object/pom.xml +++ b/value-object/pom.xml @@ -2,7 +2,7 @@