entry : documentProperties.entrySet()) {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+
+ // Append key-value pair
+ builder.append("[").append(key).append(" : ").append(value).append("]");
+
+ // Add comma if not last property
+ if (++currentPropertyIndex < numProperties) {
+ builder.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
new file mode 100644
index 000000000000..607b4a7f7913
--- /dev/null
+++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java
@@ -0,0 +1,88 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+import com.iluwatar.abstractdocument.domain.Car;
+import com.iluwatar.abstractdocument.domain.enums.Property;
+import java.util.List;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 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.
+ */
+@Slf4j
+public class App {
+
+ /**
+ * Program entry point.
+ *
+ * @param args command line args
+ */
+ public static void main(String[] args) {
+ LOGGER.info("Constructing parts and car");
+
+ var wheelProperties =
+ Map.of(
+ Property.TYPE.toString(), "wheel",
+ Property.MODEL.toString(), "15C",
+ Property.PRICE.toString(), 100L);
+
+ var doorProperties =
+ Map.of(
+ Property.TYPE.toString(), "door",
+ Property.MODEL.toString(), "Lambo",
+ Property.PRICE.toString(), 300L);
+
+ var carProperties =
+ Map.of(
+ Property.MODEL.toString(),
+ "300SL",
+ Property.PRICE.toString(),
+ 10000L,
+ Property.PARTS.toString(),
+ List.of(wheelProperties, doorProperties));
+
+ var car = new Car(carProperties);
+
+ LOGGER.info("Here is our car:");
+ LOGGER.info("-> model: {}", car.getModel().orElseThrow());
+ LOGGER.info("-> price: {}", car.getPrice().orElseThrow());
+ LOGGER.info("-> parts: ");
+ car.getParts()
+ .forEach(
+ p ->
+ LOGGER.info(
+ "\t{}/{}/{}",
+ p.getType().orElse(null),
+ p.getModel().orElse(null),
+ p.getPrice().orElse(null)));
+ }
+}
diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java
new file mode 100644
index 000000000000..79a51b610337
--- /dev/null
+++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java
@@ -0,0 +1,59 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+/** Document interface. */
+public interface Document {
+
+ /**
+ * Puts the value related to the key.
+ *
+ * @param key element key
+ * @param value element value
+ * @return Void
+ */
+ Void put(String key, Object value);
+
+ /**
+ * Gets the value for the key.
+ *
+ * @param key element key
+ * @return value or null
+ */
+ Object get(String key);
+
+ /**
+ * Gets the stream of child documents.
+ *
+ * @param key element key
+ * @param constructor constructor of child class
+ * @return child documents
+ */
+ Stream children(String key, Function, T> constructor);
+}
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
new file mode 100644
index 000000000000..93fbbb9c1eae
--- /dev/null
+++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java
@@ -0,0 +1,36 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+import com.iluwatar.abstractdocument.AbstractDocument;
+import java.util.Map;
+
+/** Car entity. */
+public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts {
+
+ public Car(Map properties) {
+ super(properties);
+ }
+}
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
new file mode 100644
index 000000000000..6f517588e5a0
--- /dev/null
+++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java
@@ -0,0 +1,37 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+import com.iluwatar.abstractdocument.Document;
+import com.iluwatar.abstractdocument.domain.enums.Property;
+import java.util.Optional;
+
+/** HasModel trait for static access to 'model' property. */
+public interface HasModel extends Document {
+
+ default Optional getModel() {
+ 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
new file mode 100644
index 000000000000..8bffa753e6ef
--- /dev/null
+++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java
@@ -0,0 +1,37 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+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. */
+public interface HasParts extends Document {
+
+ default Stream getParts() {
+ 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
new file mode 100644
index 000000000000..ce876e5faf54
--- /dev/null
+++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java
@@ -0,0 +1,37 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+import com.iluwatar.abstractdocument.Document;
+import com.iluwatar.abstractdocument.domain.enums.Property;
+import java.util.Optional;
+
+/** HasPrice trait for static access to 'price' property. */
+public interface HasPrice extends Document {
+
+ default Optional getPrice() {
+ 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
new file mode 100644
index 000000000000..5e0f49df7b7b
--- /dev/null
+++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java
@@ -0,0 +1,37 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+import com.iluwatar.abstractdocument.Document;
+import com.iluwatar.abstractdocument.domain.enums.Property;
+import java.util.Optional;
+
+/** HasType trait for static access to 'type' property. */
+public interface HasType extends Document {
+
+ default Optional getType() {
+ 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
new file mode 100644
index 000000000000..6eec08b0d2a4
--- /dev/null
+++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java
@@ -0,0 +1,36 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+import com.iluwatar.abstractdocument.AbstractDocument;
+import java.util.Map;
+
+/** Part entity. */
+public class Part extends AbstractDocument implements HasType, HasModel, HasPrice {
+
+ public Part(Map properties) {
+ super(properties);
+ }
+}
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..3e0d6d10ab1f
--- /dev/null
+++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/enums/Property.java
@@ -0,0 +1,33 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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
new file mode 100644
index 000000000000..a098517c3a69
--- /dev/null
+++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java
@@ -0,0 +1,130 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+/** AbstractDocument test class */
+class AbstractDocumentTest {
+
+ private static final String KEY = "key";
+ private static final String VALUE = "value";
+
+ private static class DocumentImplementation extends AbstractDocument {
+
+ DocumentImplementation(Map properties) {
+ super(properties);
+ }
+ }
+
+ private final DocumentImplementation document = new DocumentImplementation(new HashMap<>());
+
+ @Test
+ void shouldPutAndGetValue() {
+ document.put(KEY, VALUE);
+ assertEquals(VALUE, document.get(KEY));
+ }
+
+ @Test
+ void shouldRetrieveChildren() {
+ var children = List.of(Map.of(), Map.of());
+
+ document.put(KEY, children);
+
+ var childrenStream = document.children(KEY, DocumentImplementation::new);
+ assertNotNull(children);
+ assertEquals(2, childrenStream.count());
+ }
+
+ @Test
+ void shouldRetrieveEmptyStreamForNonExistingChildren() {
+ var children = document.children(KEY, DocumentImplementation::new);
+ assertNotNull(children);
+ assertEquals(0, children.count());
+ }
+
+ @Test
+ void shouldIncludePropsInToString() {
+ var props = Map.of(KEY, (Object) VALUE);
+ var document = new DocumentImplementation(props);
+ assertTrue(document.toString().contains(KEY));
+ assertTrue(document.toString().contains(VALUE));
+ }
+
+ @Test
+ void shouldHandleExceptionDuringConstruction() {
+ Map invalidProperties =
+ null; // Invalid properties, causing NullPointerException
+
+ // Throw null pointer exception
+ assertThrows(
+ NullPointerException.class,
+ () -> {
+ // Attempt to construct a document with invalid properties
+ new DocumentImplementation(invalidProperties);
+ });
+ }
+
+ @Test
+ void shouldPutAndGetNestedDocument() {
+ // Creating a nested document
+ DocumentImplementation nestedDocument = new DocumentImplementation(new HashMap<>());
+ nestedDocument.put("nestedKey", "nestedValue");
+
+ document.put("nested", nestedDocument);
+
+ // Retrieving the nested document
+ DocumentImplementation retrievedNestedDocument =
+ (DocumentImplementation) document.get("nested");
+
+ assertNotNull(retrievedNestedDocument);
+ assertEquals("nestedValue", retrievedNestedDocument.get("nestedKey"));
+ }
+
+ @Test
+ void shouldUpdateExistingValue() {
+ // Arrange
+ final String key = "key";
+ final String originalValue = "originalValue";
+ final String updatedValue = "updatedValue";
+
+ // Initializing the value
+ document.put(key, originalValue);
+
+ // Verifying that the initial value is retrieved correctly
+ assertEquals(originalValue, document.get(key));
+
+ // Updating the value
+ document.put(key, updatedValue);
+
+ // Verifying that the updated value is retrieved correctly
+ assertEquals(updatedValue, document.get(key));
+ }
+}
diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java
new file mode 100644
index 000000000000..16dcba0db37f
--- /dev/null
+++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java
@@ -0,0 +1,44 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import org.junit.jupiter.api.Test;
+
+/** Simple App test */
+class AppTest {
+
+ /**
+ * Issue: Add at least one assertion to this test case.
+ *
+ * Solution: Inserted assertion to check whether the execution of the main method in {@link
+ * App} throws an exception.
+ */
+ @Test
+ void shouldExecuteAppWithoutException() {
+ assertDoesNotThrow(() -> App.main(null));
+ }
+}
diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java
new file mode 100644
index 000000000000..fc29dea45c43
--- /dev/null
+++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java
@@ -0,0 +1,71 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import com.iluwatar.abstractdocument.domain.Car;
+import com.iluwatar.abstractdocument.domain.Part;
+import com.iluwatar.abstractdocument.domain.enums.Property;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+/** Test for Part and Car */
+class DomainTest {
+
+ private static final String TEST_PART_TYPE = "test-part-type";
+ private static final String TEST_PART_MODEL = "test-part-model";
+ private static final long TEST_PART_PRICE = 0L;
+
+ private static final String TEST_CAR_MODEL = "test-car-model";
+ private static final long TEST_CAR_PRICE = 1L;
+
+ @Test
+ void shouldConstructPart() {
+ 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
+ void shouldConstructCar() {
+ 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
new file mode 100644
index 000000000000..ee9823a39a5f
--- /dev/null
+++ b/abstract-factory/README.md
@@ -0,0 +1,227 @@
+---
+title: "Abstract Factory Pattern in Java: Mastering Object Creation Elegantly"
+shortTitle: Abstract Factory
+description: "Learn the Abstract Factory pattern in Java with real-world examples, class diagrams, and tutorials. Understand its intent, applicability, benefits, and known uses to enhance your design pattern knowledge."
+category: Creational
+language: en
+tag:
+ - Abstraction
+ - Decoupling
+ - Gang of Four
+ - Instantiation
+ - Polymorphism
+---
+
+## Also known as
+
+* Kit
+
+## Intent of Abstract Factory Design Pattern
+
+The Abstract Factory pattern in Java provides an interface for creating families of related or dependent objects without specifying their concrete classes, enhancing modularity and flexibility in software design.
+
+## Detailed Explanation of Abstract Factory Pattern with Real-World Examples
+
+Real-world example
+
+> Imagine a furniture company that uses the Abstract Factory pattern in Java to produce various styles of furniture: modern, Victorian, and rustic. Each style includes products like chairs, tables, and sofas. To ensure consistency within each style, the company uses an Abstract Factory pattern.
+>
+> In this scenario, the Abstract Factory is an interface for creating families of related furniture objects (chairs, tables, sofas). Each concrete factory (ModernFurnitureFactory, VictorianFurnitureFactory, RusticFurnitureFactory) implements the Abstract Factory interface and creates a set of products that match the specific style. This way, clients can create a whole set of modern or Victorian furniture without worrying about the details of their instantiation. This maintains a consistent style and allows easy swapping of one style of furniture for another.
+
+In plain words
+
+> A factory of factories; a factory that groups the individual but related/dependent factories together without specifying their concrete classes.
+
+Wikipedia says
+
+> The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes
+
+Class diagram
+
+
+
+## Programmatic Example of Abstract Factory in Java
+
+To create a kingdom using the Abstract Factory pattern in Java, we need objects with a common theme. The elven kingdom needs an elven king, elven castle, and elven army whereas the orcish kingdom needs an orcish king, orcish castle, and orcish army. There is a dependency between the objects in the kingdom.
+
+Translating the kingdom example above. First of all, we have some interfaces and implementation for the objects in the kingdom.
+
+```java
+public interface Castle {
+ String getDescription();
+}
+
+public interface King {
+ String getDescription();
+}
+
+public interface Army {
+ String getDescription();
+}
+
+// Elven implementations ->
+public class ElfCastle implements Castle {
+ static final String DESCRIPTION = "This is the elven castle!";
+
+ @Override
+ public String getDescription() {
+ return DESCRIPTION;
+ }
+}
+
+public class ElfKing implements King {
+ static final String DESCRIPTION = "This is the elven king!";
+
+ @Override
+ public String getDescription() {
+ return DESCRIPTION;
+ }
+}
+
+public class ElfArmy implements Army {
+ static final String DESCRIPTION = "This is the elven Army!";
+
+ @Override
+ public String getDescription() {
+ return DESCRIPTION;
+ }
+}
+
+// Orcish implementations similarly -> ...
+```
+
+Then we have the abstraction and implementations for the kingdom factory.
+
+```java
+public interface KingdomFactory {
+ Castle createCastle();
+
+ King createKing();
+
+ Army createArmy();
+}
+
+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();
+ }
+}
+
+// Orcish implementations similarly -> ...
+```
+
+Now, we can design a factory for our different kingdom factories. In this example, we created `FactoryMaker`, responsible for returning an instance of either `ElfKingdomFactory` or `OrcKingdomFactory`. The client can use `FactoryMaker` to create the desired concrete factory which, in turn, will produce different concrete objects (derived from `Army`, `King`, `Castle`). In this example, we also used an enum to parameterize which type of kingdom factory the client will ask for.
+
+```java
+public static class FactoryMaker {
+
+ public enum KingdomType {
+ ELF, ORC
+ }
+
+ public static KingdomFactory makeFactory(KingdomType type) {
+ return switch (type) {
+ case ELF -> new ElfKingdomFactory();
+ case ORC -> new OrcKingdomFactory();
+ };
+ }
+}
+```
+
+Here is the main function of our example application:
+
+```java
+LOGGER.info("elf kingdom");
+createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
+LOGGER.info(kingdom.getArmy().getDescription());
+LOGGER.info(kingdom.getCastle().getDescription());
+LOGGER.info(kingdom.getKing().getDescription());
+
+LOGGER.info("orc kingdom");
+createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
+LOGGER.info(kingdom.getArmy().getDescription());
+LOGGER.info(kingdom.getCastle().getDescription());
+LOGGER.info(kingdom.getKing().getDescription());
+```
+
+The program output:
+
+```
+07:35:46.340 [main] INFO com.iluwatar.abstractfactory.App -- elf kingdom
+07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven army!
+07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven castle!
+07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven king!
+07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- orc kingdom
+07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc army!
+07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc castle!
+07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc king!
+```
+
+## When to Use the Abstract Factory Pattern in Java
+
+Use the Abstract Factory pattern in Java when:
+
+* The system should be independent of how its products are created, composed, and represented.
+* You need to configure the system with one of multiple families of products.
+* A family of related product objects must be used together, enforcing consistency.
+* You want to provide a class library of products, exposing only their interfaces, not their implementations.
+* The lifetime of dependencies is shorter than the consumer's lifetime.
+* Dependencies need to be constructed using runtime values or parameters.
+* You need to choose which product to use from a family at runtime.
+* Adding new products or families should not require changes to existing code.
+
+## Abstract Factory Pattern Java Tutorials
+
+* [Abstract Factory Design Pattern in Java (DigitalOcean)](https://www.digitalocean.com/community/tutorials/abstract-factory-design-pattern-in-java)
+* [Abstract Factory(Refactoring Guru)](https://refactoring.guru/design-patterns/abstract-factory)
+
+## Benefits and Trade-offs of Abstract Factory Pattern
+
+Benefits:
+
+* Flexibility: Easily switch between product families without code modifications.
+
+* Decoupling: Client code only interacts with abstract interfaces, promoting portability and maintainability.
+
+* Reusability: Abstract factories and products facilitate component reuse across projects.
+
+* Maintainability: Changes to individual product families are localized, simplifying updates.
+
+Trade-offs:
+
+* Complexity: Defining abstract interfaces and concrete factories adds initial overhead.
+
+* Indirectness: Client code interacts with products indirectly through factories, potentially reducing transparency.
+
+## Real-World Applications of Abstract Factory Pattern in Java
+
+* Java Swing's `LookAndFeel` classes for providing different look-and-feel options.
+* Various implementations in the Java Abstract Window Toolkit (AWT) for creating different GUI components.
+* [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html)
+* [javax.xml.transform.TransformerFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newInstance--)
+* [javax.xml.xpath.XPathFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/xpath/XPathFactory.html#newInstance--)
+
+## Related Java Design Patterns
+
+* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): Abstract Factory uses Factory Methods to create products.
+* [Singleton](https://java-design-patterns.com/patterns/singleton/): Abstract Factory classes are often implemented as Singletons.
+* [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/): Similar to Abstract Factory but focuses on configuring and managing a set of related objects in a flexible way.
+
+## References and Credits
+
+* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
+* [Design Patterns in Java](https://amzn.to/3Syw0vC)
+* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq)
+* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3HWNf4U)
diff --git a/abstract-factory/etc/abstract-factory.png b/abstract-factory/etc/abstract-factory.png
deleted file mode 100644
index c709276b662b..000000000000
Binary files a/abstract-factory/etc/abstract-factory.png and /dev/null differ
diff --git a/abstract-factory/etc/abstract-factory.ucls b/abstract-factory/etc/abstract-factory.ucls
deleted file mode 100644
index bdf1e1510773..000000000000
--- a/abstract-factory/etc/abstract-factory.ucls
+++ /dev/null
@@ -1,159 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
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/abstract-factory_1.png b/abstract-factory/etc/abstract-factory_1.png
deleted file mode 100644
index 5990edd83126..000000000000
Binary files a/abstract-factory/etc/abstract-factory_1.png and /dev/null differ
diff --git a/abstract-factory/index.md b/abstract-factory/index.md
deleted file mode 100644
index f824f7e0eba4..000000000000
--- a/abstract-factory/index.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-layout: pattern
-title: Abstract Factory
-folder: abstract-factory
-permalink: /patterns/abstract-factory/
-categories: Creational
-tags:
- - Java
- - Gang Of Four
----
-
-**Also known as:** Kit
-
-**Intent:** Provide an interface for creating families of related or dependent
-objects without specifying their concrete classes.
-
-
-
-**Applicability:** Use the Abstract Factory pattern when
-
-* a system should be independent of how its products are created, composed and represented
-* a system should be configured with one of multiple families of products
-* a family of related product objects is designed to be used together, and you need to enforce this constraint
-* you want to provide a class library of products, and you want to reveal just their interfaces, not their implementations
-
-**Real world examples:**
-
-* [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html)
-
-**Credits**
-
-* [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)
diff --git a/abstract-factory/pom.xml b/abstract-factory/pom.xml
index f8c7fa106447..60fbf72eff54 100644
--- a/abstract-factory/pom.xml
+++ b/abstract-factory/pom.xml
@@ -1,18 +1,72 @@
-
-
+
+
+
4.0.0
com.iluwatar
java-design-patterns
- 1.8.0-SNAPSHOT
+ 1.26.0-SNAPSHOT
abstract-factory
- junit
- junit
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ 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 9a32a091a899..798cbe4fd118 100644
--- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java
+++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java
@@ -1,8 +1,33 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
/**
- *
* 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
@@ -11,100 +36,51 @@
* since it uses only the generic interfaces of their products. This pattern separates the details
* of implementation of a set of objects from their general usage and relies on object composition,
* as object creation is implemented in methods exposed in the factory interface.
- *
- * The essence of the Abstract Factory pattern is a factory interface ({@link KingdomFactory}) and
- * its implementations ({@link ElfKingdomFactory}, {@link OrcKingdomFactory}). The example uses both
- * concrete implementations to create a king, a castle and an army.
- *
+ *
+ *
The essence of the Abstract Factory pattern is a factory interface ({@link KingdomFactory})
+ * and its implementations ( {@link ElfKingdomFactory}, {@link OrcKingdomFactory}). The example uses
+ * both concrete implementations to create a king, a castle, and an army.
*/
-public class App {
-
- private King king;
- private Castle castle;
- private Army army;
+@Slf4j
+@Getter
+public class App implements Runnable {
+ private final Kingdom kingdom = new Kingdom();
/**
- * Creates kingdom
- *
- * @param factory
+ * Program entry point.
+ *
+ * @param args command line args
*/
- public void createKingdom(final KingdomFactory factory) {
- setKing(factory.createKing());
- setCastle(factory.createCastle());
- setArmy(factory.createArmy());
- }
-
- ElfKingdomFactory getElfKingdomFactory() {
- return new ElfKingdomFactory();
- }
-
- OrcKingdomFactory getOrcKingdomFactory() {
- return new OrcKingdomFactory();
- }
-
- King getKing(final KingdomFactory factory) {
- return factory.createKing();
- }
-
- Castle getCastle(final KingdomFactory factory) {
- return factory.createCastle();
- }
-
- Army getArmy(final KingdomFactory factory) {
- return factory.createArmy();
- }
-
- public King getKing() {
- return king;
- }
-
- private void setKing(final King king) {
- this.king = king;
- }
-
- public Castle getCastle() {
- return castle;
- }
-
- private void setCastle(final Castle castle) {
- this.castle = castle;
- }
-
- public Army getArmy() {
- return army;
+ public static void main(String[] args) {
+ var app = new App();
+ app.run();
}
- private void setArmy(final Army army) {
- this.army = army;
+ @Override
+ public void run() {
+ LOGGER.info("elf kingdom");
+ createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
+ LOGGER.info(kingdom.getArmy().getDescription());
+ LOGGER.info(kingdom.getCastle().getDescription());
+ LOGGER.info(kingdom.getKing().getDescription());
+
+ LOGGER.info("orc kingdom");
+ createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
+ LOGGER.info(kingdom.getArmy().getDescription());
+ LOGGER.info(kingdom.getCastle().getDescription());
+ LOGGER.info(kingdom.getKing().getDescription());
}
-
/**
- * Program entry point
- *
- * @param args command line args
+ * Creates kingdom.
+ *
+ * @param kingdomType type of Kingdom
*/
- public static void main(String[] args) {
-
- App app = new App();
-
- System.out.println("Elf Kingdom");
- KingdomFactory elfKingdomFactory;
- elfKingdomFactory = app.getElfKingdomFactory();
- app.createKingdom(elfKingdomFactory);
- System.out.println(app.getArmy().getDescription());
- System.out.println(app.getCastle().getDescription());
- System.out.println(app.getKing().getDescription());
-
- System.out.println("\nOrc Kingdom");
- KingdomFactory orcKingdomFactory;
- orcKingdomFactory = app.getOrcKingdomFactory();
- app.createKingdom(orcKingdomFactory);
- System.out.println(app.getArmy().getDescription());
- System.out.println(app.getCastle().getDescription());
- System.out.println(app.getKing().getDescription());
-
+ public void createKingdom(final Kingdom.FactoryMaker.KingdomType kingdomType) {
+ final KingdomFactory kingdomFactory = Kingdom.FactoryMaker.makeFactory(kingdomType);
+ kingdom.setKing(kingdomFactory.createKing());
+ kingdom.setCastle(kingdomFactory.createCastle());
+ kingdom.setArmy(kingdomFactory.createArmy());
}
-
}
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 8e97d3f4e3c2..78c75323f1c0 100644
--- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Army.java
+++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Army.java
@@ -1,10 +1,30 @@
-package com.iluwatar.abstractfactory;
-
-/**
- *
- * Army interface
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
+
+/** Army interface. */
public interface Army {
String getDescription();
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 3a36513bff6f..ee1e16f3cd3f 100644
--- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Castle.java
+++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Castle.java
@@ -1,10 +1,30 @@
-package com.iluwatar.abstractfactory;
-
-/**
- *
- * Castle interface
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
+
+/** Castle interface. */
public interface Castle {
String getDescription();
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 d7ae734d241c..d7e46c1456f0 100644
--- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfArmy.java
+++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfArmy.java
@@ -1,13 +1,33 @@
-package com.iluwatar.abstractfactory;
-
-/**
- *
- * ElfArmy
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
+
+/** ElfArmy. */
public class ElfArmy implements Army {
- static final String DESCRIPTION = "This is the Elven Army!";
+ static final String DESCRIPTION = "This is the elven army!";
@Override
public String getDescription() {
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 e53b198d78e2..136afb11fd23 100644
--- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfCastle.java
+++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfCastle.java
@@ -1,13 +1,33 @@
-package com.iluwatar.abstractfactory;
-
-/**
- *
- * ElfCastle
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
+
+/** ElfCastle. */
public class ElfCastle implements Castle {
- static final String DESCRIPTION = "This is the Elven castle!";
+ static final String DESCRIPTION = "This is the elven castle!";
@Override
public String getDescription() {
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 36669f42f912..9b0d3a6f1a77 100644
--- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKing.java
+++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKing.java
@@ -1,13 +1,33 @@
-package com.iluwatar.abstractfactory;
-
-/**
- *
- * ElfKing
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
+
+/** ElfKing. */
public class ElfKing implements King {
- static final String DESCRIPTION = "This is the Elven king!";
+ static final String DESCRIPTION = "This is the elven king!";
@Override
public String getDescription() {
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 7c8435056d5f..b09a2f47c252 100644
--- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKingdomFactory.java
+++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKingdomFactory.java
@@ -1,22 +1,44 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
-/**
- *
- * ElfKingdomFactory concrete factory.
- *
- */
+/** 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 38e6b9f4dd36..9f65ed434577 100644
--- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/King.java
+++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/King.java
@@ -1,10 +1,30 @@
-package com.iluwatar.abstractfactory;
-
-/**
- *
- * King interface
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
+
+/** King interface. */
public interface King {
String getDescription();
diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Kingdom.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Kingdom.java
new file mode 100644
index 000000000000..d1f85a6a4812
--- /dev/null
+++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Kingdom.java
@@ -0,0 +1,56 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/** Helper class to manufacture {@link KingdomFactory} beans. */
+@Getter
+@Setter
+public class Kingdom {
+
+ private King king;
+ private Castle castle;
+ private Army army;
+
+ /** The factory of kingdom factories. */
+ public static class FactoryMaker {
+
+ /** Enumeration for the different types of Kingdoms. */
+ public enum KingdomType {
+ ELF,
+ ORC
+ }
+
+ /** The factory method to create KingdomFactory concrete objects. */
+ public static KingdomFactory makeFactory(KingdomType type) {
+ return switch (type) {
+ case ELF -> new ElfKingdomFactory();
+ case ORC -> new OrcKingdomFactory();
+ };
+ }
+ }
+}
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 e303b32f437b..199c6697dcaa 100644
--- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/KingdomFactory.java
+++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/KingdomFactory.java
@@ -1,10 +1,30 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
-/**
- *
- * KingdomFactory factory interface.
- *
- */
+/** KingdomFactory factory interface. */
public interface KingdomFactory {
Castle createCastle();
@@ -12,5 +32,4 @@ public interface KingdomFactory {
King createKing();
Army createArmy();
-
}
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 de16e5007029..31ed6896d921 100644
--- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcArmy.java
+++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcArmy.java
@@ -1,13 +1,33 @@
-package com.iluwatar.abstractfactory;
-
-/**
- *
- * OrcArmy
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
+
+/** OrcArmy. */
public class OrcArmy implements Army {
- static final String DESCRIPTION = "This is the Orc Army!";
+ static final String DESCRIPTION = "This is the orc army!";
@Override
public String getDescription() {
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 b355744cea03..bdae5709a97c 100644
--- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcCastle.java
+++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcCastle.java
@@ -1,13 +1,33 @@
-package com.iluwatar.abstractfactory;
-
-/**
- *
- * OrcCastle
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
+
+/** OrcCastle. */
public class OrcCastle implements Castle {
- static final String DESCRIPTION = "This is the Orc castle!";
+ static final String DESCRIPTION = "This is the orc castle!";
@Override
public String getDescription() {
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 eac96913fa0c..7f106d45a01d 100644
--- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKing.java
+++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKing.java
@@ -1,13 +1,33 @@
-package com.iluwatar.abstractfactory;
-
-/**
- *
- * OrcKing
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
+
+/** OrcKing. */
public class OrcKing implements King {
- static final String DESCRIPTION = "This is the Orc king!";
+ static final String DESCRIPTION = "This is the orc king!";
@Override
public String getDescription() {
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 18301c955d03..82d258570460 100644
--- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKingdomFactory.java
+++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKingdomFactory.java
@@ -1,20 +1,43 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
-/**
- *
- * OrcKingdomFactory concrete factory.
- *
- */
+/** 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
new file mode 100644
index 000000000000..b5dde940c464
--- /dev/null
+++ b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java
@@ -0,0 +1,113 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/** Tests for abstract factory. */
+class AbstractFactoryTest {
+
+ private final App app = new App();
+
+ @Test
+ void verifyKingCreation() {
+ app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
+ final var kingdom = app.getKingdom();
+
+ final var elfKing = kingdom.getKing();
+ assertTrue(elfKing instanceof ElfKing);
+ assertEquals(ElfKing.DESCRIPTION, elfKing.getDescription());
+
+ app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
+ final var orcKing = kingdom.getKing();
+ assertTrue(orcKing instanceof OrcKing);
+ assertEquals(OrcKing.DESCRIPTION, orcKing.getDescription());
+ }
+
+ @Test
+ void verifyCastleCreation() {
+ app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
+ final var kingdom = app.getKingdom();
+
+ final var elfCastle = kingdom.getCastle();
+ assertTrue(elfCastle instanceof ElfCastle);
+ assertEquals(ElfCastle.DESCRIPTION, elfCastle.getDescription());
+
+ app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
+ final var orcCastle = kingdom.getCastle();
+ assertTrue(orcCastle instanceof OrcCastle);
+ assertEquals(OrcCastle.DESCRIPTION, orcCastle.getDescription());
+ }
+
+ @Test
+ void verifyArmyCreation() {
+ app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
+ final var kingdom = app.getKingdom();
+
+ final var elfArmy = kingdom.getArmy();
+ assertTrue(elfArmy instanceof ElfArmy);
+ assertEquals(ElfArmy.DESCRIPTION, elfArmy.getDescription());
+
+ app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
+ final var orcArmy = kingdom.getArmy();
+ assertTrue(orcArmy instanceof OrcArmy);
+ assertEquals(OrcArmy.DESCRIPTION, orcArmy.getDescription());
+ }
+
+ @Test
+ void verifyElfKingdomCreation() {
+ app.createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
+ final var kingdom = app.getKingdom();
+
+ final var king = kingdom.getKing();
+ final var castle = kingdom.getCastle();
+ final var army = kingdom.getArmy();
+ assertTrue(king instanceof ElfKing);
+ assertEquals(ElfKing.DESCRIPTION, king.getDescription());
+ assertTrue(castle instanceof ElfCastle);
+ assertEquals(ElfCastle.DESCRIPTION, castle.getDescription());
+ assertTrue(army instanceof ElfArmy);
+ assertEquals(ElfArmy.DESCRIPTION, army.getDescription());
+ }
+
+ @Test
+ void verifyOrcKingdomCreation() {
+ app.createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
+ final var kingdom = app.getKingdom();
+
+ final var king = kingdom.getKing();
+ final var castle = kingdom.getCastle();
+ final var army = kingdom.getArmy();
+ assertTrue(king instanceof OrcKing);
+ assertEquals(OrcKing.DESCRIPTION, king.getDescription());
+ assertTrue(castle instanceof OrcCastle);
+ assertEquals(OrcCastle.DESCRIPTION, castle.getDescription());
+ assertTrue(army instanceof OrcArmy);
+ assertEquals(OrcArmy.DESCRIPTION, army.getDescription());
+ }
+}
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 5e3869fc8082..9f53691a594d 100644
--- a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java
+++ b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java
@@ -1,78 +1,39 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.abstractfactory;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
-public class AppTest {
-
- private App app = new App();
- private KingdomFactory elfFactory;
- private KingdomFactory orcFactory;
-
- @Before
- public void setUp() {
- elfFactory = app.getElfKingdomFactory();
- orcFactory = app.getOrcKingdomFactory();
- }
-
- @Test
- public void king() {
- final King elfKing = app.getKing(elfFactory);
- assertTrue(elfKing instanceof ElfKing);
- assertEquals(ElfKing.DESCRIPTION, elfKing.getDescription());
- final King orcKing = app.getKing(orcFactory);
- assertTrue(orcKing instanceof OrcKing);
- assertEquals(OrcKing.DESCRIPTION, orcKing.getDescription());
- }
-
- @Test
- public void castle() {
- final Castle elfCastle = app.getCastle(elfFactory);
- assertTrue(elfCastle instanceof ElfCastle);
- assertEquals(ElfCastle.DESCRIPTION, elfCastle.getDescription());
- final Castle orcCastle = app.getCastle(orcFactory);
- assertTrue(orcCastle instanceof OrcCastle);
- assertEquals(OrcCastle.DESCRIPTION, orcCastle.getDescription());
- }
+/** Check whether the execution of the main method in {@link App} throws an exception. */
+class AppTest {
@Test
- public void army() {
- final Army elfArmy = app.getArmy(elfFactory);
- assertTrue(elfArmy instanceof ElfArmy);
- assertEquals(ElfArmy.DESCRIPTION, elfArmy.getDescription());
- final Army orcArmy = app.getArmy(orcFactory);
- assertTrue(orcArmy instanceof OrcArmy);
- assertEquals(OrcArmy.DESCRIPTION, orcArmy.getDescription());
- }
+ void shouldExecuteApplicationWithoutException() {
- @Test
- public void createElfKingdom() {
- app.createKingdom(elfFactory);
- final King king = app.getKing();
- final Castle castle = app.getCastle();
- final Army army = app.getArmy();
- assertTrue(king instanceof ElfKing);
- assertEquals(ElfKing.DESCRIPTION, king.getDescription());
- assertTrue(castle instanceof ElfCastle);
- assertEquals(ElfCastle.DESCRIPTION, castle.getDescription());
- assertTrue(army instanceof ElfArmy);
- assertEquals(ElfArmy.DESCRIPTION, army.getDescription());
- }
-
- @Test
- public void createOrcKingdom() {
- app.createKingdom(orcFactory);
- final King king = app.getKing();
- final Castle castle = app.getCastle();
- final Army army = app.getArmy();
- assertTrue(king instanceof OrcKing);
- assertEquals(OrcKing.DESCRIPTION, king.getDescription());
- assertTrue(castle instanceof OrcCastle);
- assertEquals(OrcCastle.DESCRIPTION, castle.getDescription());
- assertTrue(army instanceof OrcArmy);
- assertEquals(OrcArmy.DESCRIPTION, army.getDescription());
+ assertDoesNotThrow(() -> App.main(new String[] {}));
}
}
diff --git a/active-object/README.md b/active-object/README.md
new file mode 100644
index 000000000000..a804ce9e632b
--- /dev/null
+++ b/active-object/README.md
@@ -0,0 +1,222 @@
+---
+title: "Active Object Pattern in Java: Achieving Efficient Asynchronous Processing"
+shortTitle: Active Object
+description: "Learn about the Active Object design pattern in Java. This guide covers asynchronous behavior, concurrency, and practical examples to enhance your Java applications' performance."
+category: Concurrency
+language: en
+tag:
+ - Asynchronous
+ - Decoupling
+ - Messaging
+ - Synchronization
+ - Thread management
+---
+
+## Intent of Active Object Design Pattern
+
+The Active Object pattern provides a reliable method for asynchronous processing in Java, ensuring responsive applications and efficient thread management. It achieves this by encapsulating tasks within objects that have their own thread and message queue. This separation keeps the main thread responsive and avoids issues like direct thread manipulation or shared state access.
+
+## Detailed Explanation of Active Object Pattern with Real-World Examples
+
+Real-world example
+
+> Imagine a busy restaurant where customers place orders with waiters. Instead of the waiters going to the kitchen to prepare the food themselves, they write the orders on slips and hand them to a dispatcher. The dispatcher manages a pool of chefs who prepare the meals asynchronously. Once a chef is free, they pick up the next order from the queue, prepare the dish, and notify the waiter when it's ready for serving.
+>
+> In this analogy, the waiters represent the client threads, the dispatcher represents the scheduler, and the chefs represent the method execution in separate threads. This setup allows the waiters to continue taking orders without being blocked by the food preparation process, much like the Active Object pattern decouples method invocation from execution to enhance concurrency.
+
+In plain words
+
+> The Active Object pattern decouples method execution from method invocation to improve concurrency and responsiveness in multithreaded applications.
+
+Wikipedia says
+
+> The active object design pattern decouples method execution from method invocation for objects that each reside in their own thread of control.[1] The goal is to introduce concurrency, by using asynchronous method invocation and a scheduler for handling requests.
+>
+> The pattern consists of six elements:
+>
+> * A proxy, which provides an interface towards clients with publicly accessible methods.
+> * An interface which defines the method request on an active object.
+> * A list of pending requests from clients.
+> * A scheduler, which decides which request to execute next.
+> * The implementation of the active object method.
+> * A callback or variable for the client to receive the result.
+
+Sequence diagram
+
+
+
+
+## Programmatic Example of Active Object in Java
+
+This section explains how the Active Object design pattern works in Java, highlighting its use in asynchronous task management and concurrency control.
+
+The Orcs are known for their wildness and untameable soul. It seems like they have their own thread of control based on previous behavior. To implement a creature that has its own thread of control mechanism and expose its API only and not the execution itself, we can use the Active Object pattern.
+
+```java
+public abstract class ActiveCreature {
+ private final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName());
+
+ private BlockingQueue requests;
+
+ private String name;
+
+ private Thread thread;
+
+ public ActiveCreature(String name) {
+ this.name = name;
+ this.requests = new LinkedBlockingQueue();
+ thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ requests.take().run();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage());
+ }
+ }
+ }
+ }
+ );
+ thread.start();
+ }
+
+ public void eat() throws InterruptedException {
+ requests.put(new Runnable() {
+ @Override
+ public void run() {
+ logger.info("{} is eating!", name());
+ logger.info("{} has finished eating!", name());
+ }
+ }
+ );
+ }
+
+ public void roam() throws InterruptedException {
+ requests.put(new Runnable() {
+ @Override
+ public void run() {
+ logger.info("{} has started to roam the wastelands.", name());
+ }
+ }
+ );
+ }
+
+ public String name() {
+ return this.name;
+ }
+}
+```
+
+We can see that any class that will extend the `ActiveCreature` class will have its own thread of control to invoke and execute methods.
+
+For example, the `Orc` class:
+
+```java
+public class Orc extends ActiveCreature {
+
+ public Orc(String name) {
+ super(name);
+ }
+}
+```
+
+Now, we can create multiple creatures such as orcs, tell them to eat and roam, and they will execute it on their own thread of control:
+
+```java
+public class App implements Runnable {
+
+ private static final Logger logger = LoggerFactory.getLogger(App.class.getName());
+
+ private static final int NUM_CREATURES = 3;
+
+ public static void main(String[] args) {
+ var app = new App();
+ app.run();
+ }
+
+ @Override
+ public void run() {
+ List creatures = new ArrayList<>();
+ try {
+ for (int i = 0; i < NUM_CREATURES; i++) {
+ creatures.add(new Orc(Orc.class.getSimpleName() + i));
+ creatures.get(i).eat();
+ creatures.get(i).roam();
+ }
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage());
+ Thread.currentThread().interrupt();
+ } finally {
+ for (int i = 0; i < NUM_CREATURES; i++) {
+ creatures.get(i).kill(0);
+ }
+ }
+ }
+}
+```
+
+Program output:
+
+```
+09:00:02.501 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 is eating!
+09:00:02.501 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 is eating!
+09:00:02.501 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 is eating!
+09:00:02.504 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 has finished eating!
+09:00:02.504 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 has finished eating!
+09:00:02.504 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 has started to roam in the wastelands.
+09:00:02.504 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 has finished eating!
+09:00:02.504 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 has started to roam in the wastelands.
+09:00:02.504 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 has started to roam in the wastelands.
+```
+
+## When to Use the Active Object Pattern in Java
+
+Use the Active Object pattern in Java when:
+
+* when you need to handle asynchronous tasks without blocking the main thread, ensuring better performance and responsiveness.
+* When you need to interact with external resources asynchronously.
+* When you want to improve the responsiveness of your application.
+* When you need to manage concurrent tasks in a modular and maintainable way.
+
+## Active Object Pattern Java Tutorials
+
+* [Android and Java Concurrency: The Active Object Pattern(Douglas Schmidt)](https://www.youtube.com/watch?v=Cd8t2u5Qmvc)
+
+## Real-World Applications of Active Object Pattern in Java
+
+* Real-time trading systems where transaction requests are handled asynchronously.
+* GUIs where long-running tasks are executed in the background without freezing the user interface.
+* Game programming to handle concurrent updates to game state or AI computations.
+
+## Benefits and Trade-offs of Active Object Pattern
+
+Discover the benefits and trade-offs of using the Active Object pattern in Java, including improved thread safety and potential overhead concerns.
+
+Benefits:
+
+* Improves responsiveness of the main thread.
+* Encapsulates concurrency concerns within objects.
+* Promotes better code organization and maintainability.
+* Provides thread safety and avoids shared state access problems.
+
+Trade-offs:
+
+* Introduces additional overhead due to message passing and thread management.
+* May not be suitable for all types of concurrency problems.
+
+## Related Java Design Patterns
+
+* [Command](https://java-design-patterns.com/patterns/command/): Encapsulates a request as an object, similarly to how the Active Object pattern encapsulates method calls.
+* [Promise](https://java-design-patterns.com/patterns/promise/): Provides a means to retrieve the result of an asynchronous method call, often used in conjunction with Active Object.
+* [Proxy](https://java-design-patterns.com/patterns/proxy/): The Active Object pattern can use a proxy to handle method invocations asynchronously.
+
+## References and Credits
+
+* [Design Patterns: Elements of Reusable Object Software](https://amzn.to/3HYqrBE)
+* [Concurrent Programming in Java: Design Principles and Patterns](https://amzn.to/498SRVq)
+* [Java Concurrency in Practice](https://amzn.to/4aRMruW)
+* [Learning Concurrent Programming in Scala](https://amzn.to/3UE07nV)
+* [Pattern Languages of Program Design 3](https://amzn.to/3OI1j61)
+* [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/3UgC24V)
diff --git a/active-object/etc/active-object-sequence-diagram.png b/active-object/etc/active-object-sequence-diagram.png
new file mode 100644
index 000000000000..b725d9b07b6d
Binary files /dev/null and b/active-object/etc/active-object-sequence-diagram.png differ
diff --git a/active-object/etc/active-object.urm.png b/active-object/etc/active-object.urm.png
new file mode 100644
index 000000000000..c14f66144ee2
Binary files /dev/null and b/active-object/etc/active-object.urm.png differ
diff --git a/active-object/etc/active-object.urm.puml b/active-object/etc/active-object.urm.puml
new file mode 100644
index 000000000000..3fc3c8e1e921
--- /dev/null
+++ b/active-object/etc/active-object.urm.puml
@@ -0,0 +1,25 @@
+@startuml
+package com.iluwatar.activeobject {
+ abstract class ActiveCreature {
+ - logger : Logger
+ - name : String
+ - requests : BlockingQueue
+ - thread : Thread
+ + ActiveCreature(name : String)
+ + eat()
+ + name() : String
+ + roam()
+ }
+ class App {
+ - creatures : Integer
+ - logger : Logger
+ + App()
+ + main(args : String[]) {static}
+ + run()
+ }
+ class Orc {
+ + Orc(name : String)
+ }
+}
+Orc --|> ActiveCreature
+@enduml
\ No newline at end of file
diff --git a/active-object/pom.xml b/active-object/pom.xml
new file mode 100644
index 000000000000..aa26dbbe2e48
--- /dev/null
+++ b/active-object/pom.xml
@@ -0,0 +1,72 @@
+
+
+
+ 4.0.0
+
+ com.iluwatar
+ java-design-patterns
+ 1.26.0-SNAPSHOT
+
+ active-object
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+
+
+ com.iluwatar.activeobject.App
+
+
+
+
+
+
+
+
+
diff --git a/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java b/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java
new file mode 100644
index 000000000000..5a440020c0ac
--- /dev/null
+++ b/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java
@@ -0,0 +1,118 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.activeobject;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** ActiveCreature class is the base of the active object example. */
+public abstract class ActiveCreature {
+
+ private static final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName());
+
+ private BlockingQueue requests;
+
+ private String name;
+
+ private Thread thread; // Thread of execution.
+
+ private int status; // status of the thread of execution.
+
+ /** Constructor and initialization. */
+ protected ActiveCreature(String name) {
+ this.name = name;
+ this.status = 0;
+ this.requests = new LinkedBlockingQueue<>();
+ thread =
+ new Thread(
+ () -> {
+ boolean infinite = true;
+ while (infinite) {
+ try {
+ requests.take().run();
+ } catch (InterruptedException e) {
+ if (this.status != 0) {
+ logger.error("Thread was interrupted. --> {}", e.getMessage());
+ }
+ infinite = false;
+ Thread.currentThread().interrupt();
+ }
+ }
+ });
+ thread.start();
+ }
+
+ /**
+ * Eats the porridge.
+ *
+ * @throws InterruptedException due to firing a new Runnable.
+ */
+ public void eat() throws InterruptedException {
+ requests.put(
+ () -> {
+ logger.info("{} is eating!", name());
+ logger.info("{} has finished eating!", name());
+ });
+ }
+
+ /**
+ * Roam the wastelands.
+ *
+ * @throws InterruptedException due to firing a new Runnable.
+ */
+ public void roam() throws InterruptedException {
+ requests.put(() -> logger.info("{} has started to roam in the wastelands.", name()));
+ }
+
+ /**
+ * Returns the name of the creature.
+ *
+ * @return the name of the creature.
+ */
+ public String name() {
+ return this.name;
+ }
+
+ /**
+ * Kills the thread of execution.
+ *
+ * @param status of the thread of execution. 0 == OK, the rest is logging an error.
+ */
+ public void kill(int status) {
+ this.status = status;
+ this.thread.interrupt();
+ }
+
+ /**
+ * Returns the status of the thread of execution.
+ *
+ * @return the status of the thread of execution.
+ */
+ public int getStatus() {
+ return this.status;
+ }
+}
diff --git a/active-object/src/main/java/com/iluwatar/activeobject/App.java b/active-object/src/main/java/com/iluwatar/activeobject/App.java
new file mode 100644
index 000000000000..ca3a5526ebb8
--- /dev/null
+++ b/active-object/src/main/java/com/iluwatar/activeobject/App.java
@@ -0,0 +1,75 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.activeobject;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The Active Object pattern helps to solve synchronization difficulties without using
+ * 'synchronized' methods. The active object will contain a thread-safe data structure (such as
+ * BlockingQueue) and use to synchronize method calls by moving the logic of the method into an
+ * invocator(usually a Runnable) and store it in the DSA.
+ *
+ * In this example, we fire 20 threads to modify a value in the target class.
+ */
+public class App implements Runnable {
+
+ private static final Logger logger = LoggerFactory.getLogger(App.class.getName());
+
+ private static final int NUM_CREATURES = 3;
+
+ /**
+ * Program entry point.
+ *
+ * @param args command line arguments.
+ */
+ public static void main(String[] args) {
+ var app = new App();
+ app.run();
+ }
+
+ @Override
+ public void run() {
+ List creatures = new ArrayList<>();
+ try {
+ for (int i = 0; i < NUM_CREATURES; i++) {
+ creatures.add(new Orc(Orc.class.getSimpleName() + i));
+ creatures.get(i).eat();
+ creatures.get(i).roam();
+ }
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage());
+ Thread.currentThread().interrupt();
+ } finally {
+ for (int i = 0; i < NUM_CREATURES; i++) {
+ creatures.get(i).kill(0);
+ }
+ }
+ }
+}
diff --git a/active-object/src/main/java/com/iluwatar/activeobject/Orc.java b/active-object/src/main/java/com/iluwatar/activeobject/Orc.java
new file mode 100644
index 000000000000..30adde034de5
--- /dev/null
+++ b/active-object/src/main/java/com/iluwatar/activeobject/Orc.java
@@ -0,0 +1,33 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.activeobject;
+
+/** An implementation of the ActiveCreature class. */
+public class Orc extends ActiveCreature {
+
+ public Orc(String name) {
+ super(name);
+ }
+}
diff --git a/active-object/src/test/java/com/iluwatar/activeobject/ActiveCreatureTest.java b/active-object/src/test/java/com/iluwatar/activeobject/ActiveCreatureTest.java
new file mode 100644
index 000000000000..be79e2fb5527
--- /dev/null
+++ b/active-object/src/test/java/com/iluwatar/activeobject/ActiveCreatureTest.java
@@ -0,0 +1,42 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.activeobject;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class ActiveCreatureTest {
+
+ @Test
+ void executionTest() throws InterruptedException {
+ ActiveCreature orc = new Orc("orc1");
+ assertEquals("orc1", orc.name());
+ assertEquals(0, orc.getStatus());
+ orc.eat();
+ orc.roam();
+ orc.kill(0);
+ }
+}
diff --git a/active-object/src/test/java/com/iluwatar/activeobject/AppTest.java b/active-object/src/test/java/com/iluwatar/activeobject/AppTest.java
new file mode 100644
index 000000000000..559e2a1f58f1
--- /dev/null
+++ b/active-object/src/test/java/com/iluwatar/activeobject/AppTest.java
@@ -0,0 +1,37 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.activeobject;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import org.junit.jupiter.api.Test;
+
+class AppTest {
+
+ @Test
+ void shouldExecuteApplicationWithoutException() {
+ assertDoesNotThrow(() -> App.main(new String[] {}));
+ }
+}
diff --git a/actor-model/README.md b/actor-model/README.md
new file mode 100644
index 000000000000..be8065ffefef
--- /dev/null
+++ b/actor-model/README.md
@@ -0,0 +1,201 @@
+---
+title: "Actor Model Pattern in Java: Building Concurrent Systems with Elegance"
+shortTitle: Actor Model
+description: "Explore the Actor Model pattern in Java with real-world examples and practical implementation. Learn how to build scalable, message-driven systems using actors, messages, and asynchronous communication."
+category: Concurrency
+language: en
+tag:
+ - Concurrency
+ - Messaging
+ - Isolation
+ - Asynchronous
+ - Distributed Systems
+ - Actor Model
+---
+
+## Also Known As
+
+- Message-passing concurrency
+- Actor-based concurrency
+
+---
+
+## Intent of Actor Model Pattern
+
+The Actor Model pattern enables the construction of highly concurrent, distributed, and fault-tolerant systems by using isolated components (actors) that interact exclusively through asynchronous message passing.
+
+---
+
+## Detailed Explanation of Actor Model Pattern with Real-World Examples
+
+### 📦 Real-world Example
+
+Imagine a customer service system:
+- Each **customer support agent** is an **actor**.
+- Customers **send questions (messages)** to agents.
+- Each agent handles one request at a time and can **respond asynchronously** without interfering with other agents.
+
+---
+
+### 🧠 In Plain Words
+
+> "Actors are like independent workers that never share memory and only communicate through messages."
+
+---
+
+### 📖 Wikipedia Says
+
+> [Actor model](https://en.wikipedia.org/wiki/Actor_model) is a mathematical model of concurrent computation that treats "actors" as the universal primitives of concurrent computation.
+
+---
+
+### 🧹 Architecture Diagram
+
+
+
+---
+
+## Programmatic Example of Actor Model Pattern in Java
+
+### Actor.java
+
+```java
+public abstract class Actor implements Runnable {
+
+ @Setter @Getter private String actorId;
+ private final BlockingQueue mailbox = new LinkedBlockingQueue<>();
+ private volatile boolean active = true;
+
+
+ public void send(Message message) {
+ mailbox.add(message);
+ }
+
+ public void stop() {
+ active = false;
+ }
+
+ @Override
+ public void run() {
+
+ }
+
+ protected abstract void onReceive(Message message);
+}
+
+```
+
+### Message.java
+
+```java
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class Message {
+ private final String content;
+ private final String senderId;
+}
+```
+
+### ActorSystem.java
+
+```java
+public class ActorSystem {
+ public void startActor(Actor actor) {
+ String actorId = "actor-" + idCounter.incrementAndGet(); // Generate a new and unique ID
+ actor.setActorId(actorId); // assign the actor it's ID
+ actorRegister.put(actorId, actor); // Register and save the actor with it's ID
+ executor.submit(actor); // Run the actor in a thread
+ }
+ public Actor getActorById(String actorId) {
+ return actorRegister.get(actorId); // Find by Id
+ }
+
+ public void shutdown() {
+ executor.shutdownNow(); // Stop all threads
+ }
+}
+```
+
+### App.java
+
+```java
+public class App {
+ public static void main(String[] args) {
+ ActorSystem system = new ActorSystem();
+ Actor srijan = new ExampleActor(system);
+ Actor ansh = new ExampleActor2(system);
+
+ system.startActor(srijan);
+ system.startActor(ansh);
+ ansh.send(new Message("Hello ansh", srijan.getActorId()));
+ srijan.send(new Message("Hello srijan!", ansh.getActorId()));
+
+ Thread.sleep(1000); // Give time for messages to process
+
+ srijan.stop(); // Stop the actor gracefully
+ ansh.stop();
+ system.shutdown(); // Stop the actor system
+ }
+}
+```
+
+---
+
+## When to Use the Actor Model Pattern in Java
+
+- When building **concurrent or distributed systems**
+- When you want **no shared mutable state**
+- When you need **asynchronous, message-driven communication**
+- When components should be **isolated and loosely coupled**
+
+---
+
+## Actor Model Pattern Java Tutorials
+
+- [Baeldung – Akka with Java](https://www.baeldung.com/java-akka)
+- [Vaughn Vernon – Reactive Messaging Patterns](https://vaughnvernon.co/?p=1143)
+
+---
+
+## Real-World Applications of Actor Model Pattern in Java
+
+- [Akka Framework](https://akka.io/)
+- [Erlang and Elixir concurrency](https://www.erlang.org/)
+- [Microsoft Orleans](https://learn.microsoft.com/en-us/dotnet/orleans/)
+- JVM-based game engines and simulators
+
+---
+
+## Benefits and Trade-offs of Actor Model Pattern
+
+### ✅ Benefits
+- High concurrency support
+- Easy scaling across threads or machines
+- Fault isolation and recovery
+- Message ordering within actors
+
+### ⚠️ Trade-offs
+- Harder to debug due to asynchronous behavior
+- Slight performance overhead due to message queues
+- More complex to design than simple method calls
+
+---
+
+## Related Java Design Patterns
+
+- [Command Pattern](../command)
+- [Mediator Pattern](../mediator)
+- [Event-Driven Architecture](../event-driven-architecture)
+- [Observer Pattern](../observer)
+
+---
+
+## References and Credits
+
+- *Programming Erlang*, Joe Armstrong
+- *Reactive Design Patterns*, Roland Kuhn
+- *The Actor Model in 10 Minutes*, [InfoQ Article](https://www.infoq.com/articles/actor-model/)
+- [Akka Documentation](https://doc.akka.io/docs/akka/current/index.html)
+
diff --git a/actor-model/etc/Actor_Model_UML_Class_Diagram.png b/actor-model/etc/Actor_Model_UML_Class_Diagram.png
new file mode 100644
index 000000000000..a4c34d7a75fd
Binary files /dev/null and b/actor-model/etc/Actor_Model_UML_Class_Diagram.png differ
diff --git a/actor-model/etc/actor-model.urm.puml b/actor-model/etc/actor-model.urm.puml
new file mode 100644
index 000000000000..020c1fc735a4
--- /dev/null
+++ b/actor-model/etc/actor-model.urm.puml
@@ -0,0 +1,35 @@
+@startuml actor-model
+
+title Actor Model - UML Class Diagram
+
+class ActorSystem {
+ +actorOf(actor: Actor): Actor
+ +shutdown(): void
+}
+
+class Actor {
+ -mailbox: BlockingQueue
+ -active: boolean
+ +send(message: Message): void
+ +stop(): void
+ +run(): void
+ #onReceive(message: Message): void
+}
+
+class ExampleActor {
+ +onReceive(message: Message): void
+}
+
+class Message {
+ -content: String
+ -sender: Actor
+ +getContent(): String
+ +getSender(): Actor
+}
+
+ActorSystem --> Actor : creates
+Actor <|-- ExampleActor : extends
+Actor --> Message : processes
+ExampleActor --> Message : uses
+
+@enduml
diff --git a/actor-model/pom.xml b/actor-model/pom.xml
new file mode 100644
index 000000000000..76c288829b8d
--- /dev/null
+++ b/actor-model/pom.xml
@@ -0,0 +1,114 @@
+
+
+
+
+ 4.0.0
+
+
+ com.iluwatar
+ java-design-patterns
+ 1.26.0-SNAPSHOT
+
+
+ actor-model
+ Actor Model
+
+
+
+
+
+ org.junit
+ junit-bom
+ 5.11.0
+ pom
+ import
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.junit.platform
+ junit-platform-launcher
+ test
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.3.0
+
+
+ jar-with-dependencies
+
+
+
+ com.iluwatar.actormodel.App
+
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
+
+
diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java b/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java
new file mode 100644
index 000000000000..6e2aaccd1937
--- /dev/null
+++ b/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java
@@ -0,0 +1,63 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.actormodel;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import lombok.Getter;
+import lombok.Setter;
+
+public abstract class Actor implements Runnable {
+
+ @Setter @Getter private String actorId;
+ private final BlockingQueue mailbox = new LinkedBlockingQueue<>();
+ private volatile boolean active =
+ true; // always read from main memory and written back to main memory,
+
+ // rather than being cached in a thread's local memory. To make it consistent to all Actors
+
+ public void send(Message message) {
+ mailbox.add(message); // Add message to queue
+ }
+
+ public void stop() {
+ active = false; // Stop the actor loop
+ }
+
+ @Override
+ public void run() {
+ while (active) {
+ try {
+ Message message = mailbox.take(); // Wait for a message
+ onReceive(message); // Process it
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ // Child classes must define what to do with a message
+ protected abstract void onReceive(Message message);
+}
diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java b/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java
new file mode 100644
index 000000000000..db7c21cb6088
--- /dev/null
+++ b/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java
@@ -0,0 +1,51 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.actormodel;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ActorSystem {
+ private final ExecutorService executor = Executors.newCachedThreadPool();
+ private final ConcurrentHashMap actorRegister = new ConcurrentHashMap<>();
+ private final AtomicInteger idCounter = new AtomicInteger(0);
+
+ public void startActor(Actor actor) {
+ String actorId = "actor-" + idCounter.incrementAndGet(); // Generate a new and unique ID
+ actor.setActorId(actorId); // assign the actor it's ID
+ actorRegister.put(actorId, actor); // Register and save the actor with it's ID
+ executor.submit(actor); // Run the actor in a thread
+ }
+
+ public Actor getActorById(String actorId) {
+ return actorRegister.get(actorId); // Find by Id
+ }
+
+ public void shutdown() {
+ executor.shutdownNow(); // Stop all threads
+ }
+}
diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/App.java b/actor-model/src/main/java/com/iluwatar/actormodel/App.java
new file mode 100644
index 000000000000..79fe79e48a6f
--- /dev/null
+++ b/actor-model/src/main/java/com/iluwatar/actormodel/App.java
@@ -0,0 +1,64 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.
+ */
+
+/**
+ * The Actor Model is a design pattern used to handle concurrency in a safe, scalable, and
+ * message-driven way.
+ *
+ * In the Actor Model: - An **Actor** is an independent unit that has its own state and behavior.
+ * - Actors **communicate only through messages** — they do not share memory. - An **ActorSystem**
+ * is responsible for creating, starting, and managing the lifecycle of actors. - Messages are
+ * delivered asynchronously, and each actor processes them one at a time.
+ *
+ *
💡 Key benefits: - No shared memory = no need for complex thread-safety - Easy to scale with
+ * many actors - Suitable for highly concurrent or distributed systems
+ *
+ *
🔍 This example demonstrates the Actor Model: - `ActorSystem` starts two actors: `srijan` and
+ * `ansh`. - `ExampleActor` and `ExampleActor2` extend the `Actor` class and override the
+ * `onReceive()` method to handle messages. - Actors communicate using `send()` to pass `Message`
+ * objects that include the message content and sender's ID. - The actors process messages
+ * **asynchronously in separate threads**, and we allow a short delay (`Thread.sleep`) to let them
+ * run. - The system is shut down gracefully at the end.
+ */
+package com.iluwatar.actormodel;
+
+public class App {
+ public static void main(String[] args) throws InterruptedException {
+ ActorSystem system = new ActorSystem();
+ Actor srijan = new ExampleActor(system);
+ Actor ansh = new ExampleActor2(system);
+
+ system.startActor(srijan);
+ system.startActor(ansh);
+ ansh.send(new Message("Hello ansh", srijan.getActorId()));
+ srijan.send(new Message("Hello srijan!", ansh.getActorId()));
+
+ Thread.sleep(1000); // Give time for messages to process
+
+ srijan.stop(); // Stop the actor gracefully
+ ansh.stop();
+ system.shutdown(); // Stop the actor system
+ }
+}
diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java
new file mode 100644
index 000000000000..fd49325f44bd
--- /dev/null
+++ b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java
@@ -0,0 +1,53 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.actormodel;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ExampleActor extends Actor {
+ private final ActorSystem actorSystem;
+ @Getter private final List receivedMessages = new ArrayList<>();
+
+ public ExampleActor(ActorSystem actorSystem) {
+ this.actorSystem = actorSystem;
+ }
+
+ // Logger log = Logger.getLogger(getClass().getName());
+
+ @Override
+ protected void onReceive(Message message) {
+ LOGGER.info(
+ "[{}]Received : {} from : [{}]", getActorId(), message.getContent(), message.getSenderId());
+ Actor sender = actorSystem.getActorById(message.getSenderId()); // sender actor id
+ // Reply of the message
+ if (sender != null && !message.getSenderId().equals(getActorId())) {
+ sender.send(new Message("I got your message ", getActorId()));
+ }
+ }
+}
diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java
new file mode 100644
index 000000000000..037f96716558
--- /dev/null
+++ b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java
@@ -0,0 +1,46 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.actormodel;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ExampleActor2 extends Actor {
+ private final ActorSystem actorSystem;
+ @Getter private final List receivedMessages = new ArrayList<>();
+
+ public ExampleActor2(ActorSystem actorSystem) {
+ this.actorSystem = actorSystem;
+ }
+
+ @Override
+ protected void onReceive(Message message) {
+ receivedMessages.add(message.getContent());
+ LOGGER.info("[{}]Received : {}", getActorId(), message.getContent());
+ }
+}
diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/Message.java b/actor-model/src/main/java/com/iluwatar/actormodel/Message.java
new file mode 100644
index 000000000000..03ca6e02cac0
--- /dev/null
+++ b/actor-model/src/main/java/com/iluwatar/actormodel/Message.java
@@ -0,0 +1,35 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.actormodel;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class Message {
+ private final String content;
+ private final String senderId;
+}
diff --git a/actor-model/src/test/java/com/iluwatar/actor/ActorModelTest.java b/actor-model/src/test/java/com/iluwatar/actor/ActorModelTest.java
new file mode 100644
index 000000000000..a4a0dee569ab
--- /dev/null
+++ b/actor-model/src/test/java/com/iluwatar/actor/ActorModelTest.java
@@ -0,0 +1,63 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.actor;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import com.iluwatar.actormodel.ActorSystem;
+import com.iluwatar.actormodel.App;
+import com.iluwatar.actormodel.ExampleActor;
+import com.iluwatar.actormodel.ExampleActor2;
+import com.iluwatar.actormodel.Message;
+import org.junit.jupiter.api.Test;
+
+public class ActorModelTest {
+ @Test
+ void testMainMethod() throws InterruptedException {
+ App.main(new String[] {});
+ }
+
+ @Test
+ public void testMessagePassing() throws InterruptedException {
+ ActorSystem system = new ActorSystem();
+
+ ExampleActor srijan = new ExampleActor(system);
+ ExampleActor2 ansh = new ExampleActor2(system);
+
+ system.startActor(srijan);
+ system.startActor(ansh);
+
+ // Ansh recieves a message from Srijan
+ ansh.send(new Message("Hello ansh", srijan.getActorId()));
+
+ // Wait briefly to allow async processing
+ Thread.sleep(200);
+
+ // Check that Srijan received the message
+ assertTrue(
+ ansh.getReceivedMessages().contains("Hello ansh"),
+ "ansh should receive the message from Srijan");
+ }
+}
diff --git a/acyclic-visitor/README.md b/acyclic-visitor/README.md
new file mode 100644
index 000000000000..fb57d5681fd4
--- /dev/null
+++ b/acyclic-visitor/README.md
@@ -0,0 +1,186 @@
+---
+title: "Acyclic Visitor Pattern in Java: Streamlining Object Interactions"
+shortTitle: Acyclic Visitor
+description: "Learn about the Acyclic Visitor pattern in Java. This guide explains how it decouples operations from object hierarchies, providing examples and real-world applications."
+category: Behavioral
+language: en
+tag:
+ - Decoupling
+ - Extensibility
+ - Interface
+ - Object composition
+---
+
+## Intent of Acyclic Visitor Design Pattern
+
+The Acyclic Visitor pattern in Java decouples operations from an object hierarchy, providing a flexible design for various applications.
+
+## Detailed Explanation of Acyclic Visitor Pattern with Real-World Examples
+
+Real-world example
+
+> An analogous real-world example of the Acyclic Visitor pattern in Java is a museum guide system, demonstrating the practical application of this design pattern. Imagine a museum with various exhibits like paintings, sculptures, and historical artifacts. The museum has different types of guides (audio guide, human guide, virtual reality guide) that provide information about each exhibit. Instead of modifying the exhibits every time a new guide type is introduced, each guide implements an interface to visit different exhibit types. This way, the museum can add new types of guides without altering the existing exhibits, ensuring that the system remains extensible and maintainable without forming any dependency cycles.
+
+In plain words
+
+> Acyclic Visitor allows functions to be added to existing class hierarchies without modifying the hierarchies.
+
+[WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) says
+
+> 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 GangOfFour VisitorPattern.
+
+Sequence diagram
+
+
+
+
+## Programmatic Example of Acyclic Visitor in Java
+
+In this Java example, we have a hierarchy of modem classes illustrating the Acyclic Visitor pattern. The modems in this hierarchy need to be visited by an external algorithm based on filtering criteria (is it Unix or DOS compatible modem).
+
+Here's the `Modem` hierarchy.
+
+```java
+public abstract class Modem {
+ public abstract void accept(ModemVisitor modemVisitor);
+}
+
+public class Zoom extends Modem {
+
+ // Other properties and methods...
+
+ @Override
+ public void accept(ModemVisitor modemVisitor) {
+ if (modemVisitor instanceof ZoomVisitor) {
+ ((ZoomVisitor) modemVisitor).visit(this);
+ } else {
+ LOGGER.info("Only ZoomVisitor is allowed to visit Zoom modem");
+ }
+ }
+}
+
+public class Hayes extends Modem {
+
+ // Other properties and methods...
+
+ @Override
+ public void accept(ModemVisitor modemVisitor) {
+ if (modemVisitor instanceof HayesVisitor) {
+ ((HayesVisitor) modemVisitor).visit(this);
+ } else {
+ LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem");
+ }
+ }
+}
+```
+
+Next, we introduce the `ModemVisitor` hierarchy.
+
+```java
+public interface ModemVisitor {
+}
+
+public interface HayesVisitor extends ModemVisitor {
+ void visit(Hayes hayes);
+}
+
+public interface ZoomVisitor extends ModemVisitor {
+ void visit(Zoom zoom);
+}
+
+public interface AllModemVisitor extends ZoomVisitor, HayesVisitor {
+}
+
+public class ConfigureForDosVisitor implements AllModemVisitor {
+
+ // Other properties and methods...
+
+ @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.");
+ }
+}
+
+public class ConfigureForUnixVisitor implements ZoomVisitor {
+
+ // Other properties and methods...
+
+ @Override
+ public void visit(Zoom zoom) {
+ LOGGER.info(zoom + " used with Unix configurator.");
+ }
+}
+```
+
+Finally, here are the visitors in action.
+
+```java
+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
+}
+```
+
+Program output:
+
+```
+09:15:11.125 [main] INFO com.iluwatar.acyclicvisitor.ConfigureForDosVisitor -- Hayes modem used with Dos configurator.
+09:15:11.127 [main] INFO com.iluwatar.acyclicvisitor.ConfigureForDosVisitor -- Zoom modem used with Dos configurator.
+09:15:11.127 [main] INFO com.iluwatar.acyclicvisitor.Hayes -- Only HayesVisitor is allowed to visit Hayes modem
+09:15:11.127 [main] INFO com.iluwatar.acyclicvisitor.ConfigureForUnixVisitor -- Zoom modem used with Unix configurator.
+```
+
+## When to Use the Acyclic Visitor Pattern in Java
+
+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.
+* When the visited class hierarchy will be frequently extended with new derivatives of the Element class.
+* When the recompilation, relinking, retesting or redistribution of the derivatives of Element is very expensive.
+
+## Acyclic Visitor Pattern Java Tutorials
+
+* [The Acyclic Visitor Pattern (Code Crafter)](https://codecrafter.blogspot.com/2012/12/the-acyclic-visitor-pattern.html)
+
+## Benefits and Trade-offs of Acyclic Visitor Pattern
+
+Benefits:
+
+* Extensible: New operations can be added easily without changing the object structure.
+* Decoupled: Reduces coupling between the objects and the operations on them.
+* No dependency cycles: Ensures acyclic dependencies, improving maintainability and reducing complexity.
+
+Trade-offs:
+
+* Increased complexity: Can introduce additional complexity with the need for multiple visitor interfaces.
+* Maintenance overhead: Modifying the object hierarchy requires updating all visitors.
+
+## Related Java Design Patterns
+
+* [Composite](https://java-design-patterns.com/patterns/composite/): Often used in conjunction with Acyclic Visitor to allow treating individual objects and compositions uniformly.
+* [Decorator](https://java-design-patterns.com/patterns/decorator/): Can be used alongside to add responsibilities to objects dynamically.
+* [Visitor](https://java-design-patterns.com/patterns/visitor/): The Acyclic Visitor pattern is a variation of the Visitor pattern that avoids cyclic dependencies.
+
+## References and Credits
+
+* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
+* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq)
+* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525)
+* [Patterns in Java: A Catalog of Reusable Design Patterns Illustrated with UML](https://amzn.to/4bOtzwF)
+* [Acyclic Visitor (Robert C. Martin)](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf)
+* [Acyclic Visitor (WikiWikiWeb)](https://wiki.c2.com/?AcyclicVisitor)
diff --git a/acyclic-visitor/etc/acyclic-visitor-sequence-diagram.png b/acyclic-visitor/etc/acyclic-visitor-sequence-diagram.png
new file mode 100644
index 000000000000..a3c2ba56b89f
Binary files /dev/null and b/acyclic-visitor/etc/acyclic-visitor-sequence-diagram.png differ
diff --git a/acyclic-visitor/etc/acyclic-visitor.png b/acyclic-visitor/etc/acyclic-visitor.png
new file mode 100644
index 000000000000..7b4df13d80f8
Binary files /dev/null and b/acyclic-visitor/etc/acyclic-visitor.png differ
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
new file mode 100644
index 000000000000..b4f5646b71c0
--- /dev/null
+++ b/acyclic-visitor/pom.xml
@@ -0,0 +1,77 @@
+
+
+
+ 4.0.0
+
+ com.iluwatar
+ java-design-patterns
+ 1.26.0-SNAPSHOT
+
+ acyclic-visitor
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.mockito
+ mockito-core
+ 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
new file mode 100644
index 000000000000..a3b1679a2d9f
--- /dev/null
+++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/AllModemVisitor.java
@@ -0,0 +1,31 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.acyclicvisitor;
+
+/**
+ * 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 {}
diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java
new file mode 100644
index 000000000000..3b7c6cd61e4b
--- /dev/null
+++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java
@@ -0,0 +1,53 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.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
+ */
+public class App {
+
+ /** Program's entry point. */
+ 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
new file mode 100644
index 000000000000..267a8d66ac45
--- /dev/null
+++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java
@@ -0,0 +1,44 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.acyclicvisitor;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * ConfigureForDosVisitor class implements both zoom's and hayes' visit method for Dos manufacturer.
+ */
+@Slf4j
+public class ConfigureForDosVisitor implements AllModemVisitor {
+
+ @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
new file mode 100644
index 000000000000..d9fd14f69435
--- /dev/null
+++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java
@@ -0,0 +1,40 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.acyclicvisitor;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * ConfigureForUnixVisitor class implements zoom's visit method for Unix manufacturer, unlike
+ * traditional visitor pattern, this class may selectively implement visit for other modems.
+ */
+@Slf4j
+public class ConfigureForUnixVisitor implements ZoomVisitor {
+
+ @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
new file mode 100644
index 000000000000..e0b2fcc2b530
--- /dev/null
+++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java
@@ -0,0 +1,48 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.acyclicvisitor;
+
+import lombok.extern.slf4j.Slf4j;
+
+/** Hayes class implements its accept method. */
+@Slf4j
+public class Hayes implements Modem {
+
+ /** Accepts all visitors but honors only HayesVisitor. */
+ @Override
+ public void accept(ModemVisitor modemVisitor) {
+ if (modemVisitor instanceof HayesVisitor) {
+ ((HayesVisitor) modemVisitor).visit(this);
+ } else {
+ LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem");
+ }
+ }
+
+ /** Hayes' modem's toString method. */
+ @Override
+ public String toString() {
+ return "Hayes modem";
+ }
+}
diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java
new file mode 100644
index 000000000000..aad9b970994f
--- /dev/null
+++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java
@@ -0,0 +1,30 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.acyclicvisitor;
+
+/** 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
new file mode 100644
index 000000000000..8552574453e5
--- /dev/null
+++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java
@@ -0,0 +1,30 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.acyclicvisitor;
+
+/** //Modem abstract class. converted to an interface */
+public interface Modem {
+ 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
new file mode 100644
index 000000000000..9391a980cd29
--- /dev/null
+++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ModemVisitor.java
@@ -0,0 +1,33 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.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
+ */
+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
new file mode 100644
index 000000000000..59b50a54a12f
--- /dev/null
+++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java
@@ -0,0 +1,48 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.acyclicvisitor;
+
+import lombok.extern.slf4j.Slf4j;
+
+/** Zoom class implements its accept method. */
+@Slf4j
+public class Zoom implements Modem {
+
+ /** Accepts all visitors but honors only ZoomVisitor. */
+ @Override
+ public void accept(ModemVisitor modemVisitor) {
+ if (modemVisitor instanceof ZoomVisitor) {
+ ((ZoomVisitor) modemVisitor).visit(this);
+ } else {
+ LOGGER.info("Only ZoomVisitor is allowed to visit Zoom modem");
+ }
+ }
+
+ /** Zoom modem's toString method. */
+ @Override
+ public String toString() {
+ return "Zoom modem";
+ }
+}
diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java
new file mode 100644
index 000000000000..5388ded6f735
--- /dev/null
+++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java
@@ -0,0 +1,30 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.acyclicvisitor;
+
+/** 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
new file mode 100644
index 000000000000..7a21498a63ea
--- /dev/null
+++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java
@@ -0,0 +1,45 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.acyclicvisitor;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import org.junit.jupiter.api.Test;
+
+/** Tests that the Acyclic Visitor example runs without errors. */
+class AppTest {
+
+ /**
+ * Issue: Add at least one assertion to this test case.
+ *
+ *
Solution: Inserted assertion to check whether the execution of the main method in {@link
+ * App} throws an exception.
+ */
+ @Test
+ void shouldExecuteApplicationWithoutException() {
+
+ assertDoesNotThrow(() -> App.main(new String[] {}));
+ }
+}
diff --git a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java
new file mode 100644
index 000000000000..a989d9287921
--- /dev/null
+++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java
@@ -0,0 +1,53 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.acyclicvisitor;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.Test;
+
+/** Hayes test class */
+class HayesTest {
+
+ @Test
+ void testAcceptForDos() {
+ var hayes = new Hayes();
+ var mockVisitor = mock(ConfigureForDosVisitor.class);
+
+ hayes.accept(mockVisitor);
+ verify((HayesVisitor) mockVisitor).visit(eq(hayes));
+ }
+
+ @Test
+ void testAcceptForUnix() {
+ var hayes = new Hayes();
+ var mockVisitor = mock(ConfigureForUnixVisitor.class);
+
+ hayes.accept(mockVisitor);
+
+ verifyNoMoreInteractions(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
new file mode 100644
index 000000000000..d5fe79965d47
--- /dev/null
+++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ZoomTest.java
@@ -0,0 +1,53 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.acyclicvisitor;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import org.junit.jupiter.api.Test;
+
+/** Zoom test class */
+class ZoomTest {
+
+ @Test
+ void testAcceptForDos() {
+ var zoom = new Zoom();
+ var mockVisitor = mock(ConfigureForDosVisitor.class);
+
+ zoom.accept(mockVisitor);
+ verify((ZoomVisitor) mockVisitor).visit(eq(zoom));
+ }
+
+ @Test
+ void testAcceptForUnix() {
+ 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
new file mode 100644
index 000000000000..489742494709
--- /dev/null
+++ b/adapter/README.md
@@ -0,0 +1,154 @@
+---
+title: "Adapter Pattern in Java: Seamless Integration of Incompatible Systems"
+shortTitle: Adapter
+description: "Learn how the Adapter Design Pattern works in Java with detailed examples and use cases. Understand how it enables compatibility between incompatible interfaces."
+category: Structural
+language: en
+tag:
+ - Compatibility
+ - Decoupling
+ - Gang of Four
+ - Interface
+ - Object composition
+ - Wrapping
+---
+
+## Also known as
+
+* Wrapper
+
+## Intent of Adapter Design Pattern
+
+The Adapter Design Pattern in Java converts the interface of a class into another interface that clients expect, enabling compatibility.
+
+## Detailed Explanation of Adapter Pattern with Real-World Examples
+
+Real-world example
+
+> Consider that you have some pictures on your memory card and you need to transfer them to your computer. To transfer them, you need some kind of adapter that is compatible with your computer ports so that you can attach a memory card to your computer. In this case card reader is an adapter. Another example would be the famous power adapter; a three-legged plug can't be connected to a two-pronged outlet, it needs to use a power adapter that makes it compatible with the two-pronged outlets. Yet another example would be a translator translating words spoken by one person to another
+
+In plain words
+
+> Adapter pattern lets you wrap an otherwise incompatible object in an adapter to make it compatible with another class.
+
+Wikipedia says
+
+> In software engineering, the adapter pattern is a software design pattern that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.
+
+Sequence diagram
+
+
+
+## Programmatic Example of Adapter Pattern in Java
+
+The Adapter Pattern example in Java shows how a class with an incompatible interface can be adapted to work with another class.
+
+Consider a wannabe captain that can only use rowing boats but can't sail at all.
+
+First, we have interfaces `RowingBoat` and `FishingBoat`
+
+```java
+public interface RowingBoat {
+ void row();
+}
+
+@Slf4j
+public class FishingBoat {
+ public void sail() {
+ LOGGER.info("The fishing boat is sailing");
+ }
+}
+```
+
+The captain expects an implementation of `RowingBoat` interface to be able to move.
+
+```java
+public class Captain {
+
+ private final RowingBoat rowingBoat;
+
+ // default constructor and setter for rowingBoat
+ public Captain(RowingBoat rowingBoat) {
+ this.rowingBoat = rowingBoat;
+ }
+
+ public void row() {
+ rowingBoat.row();
+ }
+}
+```
+
+Now, let's say the pirates are coming and our captain needs to escape but there is only a fishing boat available. We need to create an adapter that allows the captain to operate the fishing boat with his rowing boat skills.
+
+```java
+@Slf4j
+public class FishingBoatAdapter implements RowingBoat {
+
+ private final FishingBoat boat;
+
+ public FishingBoatAdapter() {
+ boat = new FishingBoat();
+ }
+
+ @Override
+ public void row() {
+ boat.sail();
+ }
+}
+```
+
+Now the `Captain` can use the `FishingBoat` to escape the pirates.
+
+```java
+ 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();
+}
+```
+
+The program outputs:
+
+```
+10:25:08.074 [main] INFO com.iluwatar.adapter.FishingBoat -- The fishing boat is sailing
+```
+
+## When to Use the Adapter Pattern in Java
+
+Use the Adapter pattern in Java when
+
+* You want to use an existing class, and its interface does not match the one you need
+* You want to create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that don't necessarily have compatible interfaces
+* You need to use several existing subclasses, but it's impractical to adapt their interface by subclassing everyone. An object adapter can adapt the interface of its parent class.
+* Most of the applications using third-party libraries use adapters as a middle layer between the application and the 3rd party library to decouple the application from the library. If another library has to be used only an adapter for the new library is required without having to change the application code.
+
+## Adapter Pattern Java Tutorials
+
+* [Using the Adapter Design Pattern in Java (Dzone)](https://dzone.com/articles/adapter-design-pattern-in-java)
+* [Adapter in Java (Refactoring Guru)](https://refactoring.guru/design-patterns/adapter/java/example)
+* [The Adapter Pattern in Java (Baeldung)](https://www.baeldung.com/java-adapter-pattern)
+* [Adapter Design Pattern (GeeksForGeeks)](https://www.geeksforgeeks.org/adapter-pattern/)
+
+## Benefits and Trade-offs of Adapter Pattern
+
+Class and object adapters offer different benefits and drawbacks. A class adapter adapts the Adaptee to the Target by binding to a specific Adaptee class, which means it cannot adapt a class and all its subclasses. This type of adapter allows the Adapter to override some of the Adaptee’s behavior because the Adapter is a subclass of the Adaptee. Additionally, it introduces only one object without needing extra pointer indirection to reach the Adaptee.
+
+On the other hand, an object adapter allows a single Adapter to work with multiple Adaptees, including the Adaptee and all its subclasses. This type of adapter can add functionality to all Adaptees simultaneously. However, it makes overriding the Adaptee’s behavior more difficult, as it requires subclassing the Adaptee and having the Adapter refer to this subclass instead of the Adaptee itself.
+
+## Real-World Applications of Adapter Pattern in Java
+
+* `java.io.InputStreamReader` and `java.io.OutputStreamWriter` in the Java IO library.
+* GUI component libraries that allow for plug-ins or adapters to convert between different GUI component interfaces.
+* [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29)
+* [java.util.Collections#list()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#list-java.util.Enumeration-)
+* [java.util.Collections#enumeration()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#enumeration-java.util.Collection-)
+* [javax.xml.bind.annotation.adapters.XMLAdapter](http://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/adapters/XmlAdapter.html#marshal-BoundType-)
+
+## References and Credits
+
+* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
+* [Effective Java](https://amzn.to/4cGk2Jz)
+* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq)
+* [J2EE Design Patterns](https://amzn.to/4dpzgmx)
+* [Refactoring to Patterns](https://amzn.to/3VOO4F5)
diff --git a/adapter/etc/adapter-sequence-diagram.png b/adapter/etc/adapter-sequence-diagram.png
new file mode 100644
index 000000000000..a9bb557ea61e
Binary files /dev/null and b/adapter/etc/adapter-sequence-diagram.png differ
diff --git a/adapter/etc/adapter.png b/adapter/etc/adapter.png
deleted file mode 100644
index 511bb5880ee7..000000000000
Binary files a/adapter/etc/adapter.png and /dev/null differ
diff --git a/adapter/etc/adapter.ucls b/adapter/etc/adapter.ucls
deleted file mode 100644
index 8c09f039930c..000000000000
--- a/adapter/etc/adapter.ucls
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
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/etc/adapter_1.png b/adapter/etc/adapter_1.png
deleted file mode 100644
index 64eb34b84f79..000000000000
Binary files a/adapter/etc/adapter_1.png and /dev/null differ
diff --git a/adapter/index.md b/adapter/index.md
deleted file mode 100644
index 36a2a0ad3f58..000000000000
--- a/adapter/index.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-layout: pattern
-title: Adapter
-folder: adapter
-permalink: /patterns/adapter/
-categories: Structural
-tags:
- - Java
- - Gang Of Four
----
-
-**Also known as:** Wrapper
-
-**Intent:** Convert the interface of a class into another interface the clients
-expect. Adapter lets classes work together that couldn't otherwise because of
-incompatible interfaces.
-
-
-
-**Applicability:** Use the Adapter pattern when
-
-* you want to use an existing class, and its interface does not match the one you need
-* you want to create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that don't necessarily have compatible interfaces
-* you need to use several existing subclasses, but it's impractical to adapt their interface by subclassing every one. An object adapter can adapt the interface of its parent class.
-
-**Real world examples:**
-
-* [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29)
-
-**Credits**
-
-* [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)
diff --git a/adapter/pom.xml b/adapter/pom.xml
index ddb81441fc4c..6e7f45a515b5 100644
--- a/adapter/pom.xml
+++ b/adapter/pom.xml
@@ -1,17 +1,50 @@
-
-
+
+
+
4.0.0
com.iluwatar
java-design-patterns
- 1.8.0-SNAPSHOT
+ 1.26.0-SNAPSHOT
adapter
- junit
- junit
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
test
@@ -20,4 +53,25 @@
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 d2353eec4f06..a4fa74274f74 100644
--- a/adapter/src/main/java/com/iluwatar/adapter/App.java
+++ b/adapter/src/main/java/com/iluwatar/adapter/App.java
@@ -1,3 +1,27 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
/**
@@ -5,24 +29,33 @@
* for an adapter. Interfaces may be incompatible but the inner functionality should suit the need.
* The Adapter design pattern allows otherwise incompatible classes to work together by converting
* the interface of one class into an interface expected by the clients.
- *
+ *
* There are two variations of the Adapter pattern: The class adapter implements the adaptee's
* interface whereas the object adapter uses composition to contain the adaptee in the adapter
* object. This example uses the object adapter approach.
- *
- *
The Adapter ({@link GnomeEngineer}) converts the interface of the target class (
- * {@link GoblinGlider}) into a suitable one expected by the client ({@link GnomeEngineeringManager}
- * ).
+ *
+ *
The Adapter ({@link 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) {
- Engineer manager = new GnomeEngineeringManager(new GnomeEngineer());
- manager.operateDevice();
+ 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
new file mode 100644
index 000000000000..3b771e9d833e
--- /dev/null
+++ b/adapter/src/main/java/com/iluwatar/adapter/Captain.java
@@ -0,0 +1,45 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * The Captain uses {@link RowingBoat} to sail.
+ * This is the client in the pattern.
+ */
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public final class Captain {
+
+ private RowingBoat rowingBoat;
+
+ void row() {
+ rowingBoat.row();
+ }
+}
diff --git a/adapter/src/main/java/com/iluwatar/adapter/Engineer.java b/adapter/src/main/java/com/iluwatar/adapter/Engineer.java
deleted file mode 100644
index a973cb530d04..000000000000
--- a/adapter/src/main/java/com/iluwatar/adapter/Engineer.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.iluwatar.adapter;
-
-/**
- *
- * Engineers can operate devices.
- *
- */
-public interface Engineer {
-
- void operateDevice();
-}
diff --git a/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java b/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java
new file mode 100644
index 000000000000..dd39f88f1ce0
--- /dev/null
+++ b/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java
@@ -0,0 +1,39 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Device class (adaptee in the pattern). We want to reuse this class. Fishing boat moves by
+ * sailing.
+ */
+@Slf4j
+final class FishingBoat {
+
+ 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
new file mode 100644
index 000000000000..8c7e9e88e220
--- /dev/null
+++ b/adapter/src/main/java/com/iluwatar/adapter/FishingBoatAdapter.java
@@ -0,0 +1,38 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+/**
+ * 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 {
+
+ private final FishingBoat boat = new FishingBoat();
+
+ public final void row() {
+ boat.sail();
+ }
+}
diff --git a/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineer.java b/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineer.java
deleted file mode 100644
index 70e166ac3c11..000000000000
--- a/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineer.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.iluwatar.adapter;
-
-/**
- *
- * Adapter class. Adapts the interface of the device ({@link GoblinGlider}) into {@link Engineer}
- * interface expected by the client ({@link GnomeEngineeringManager}).
- *
- */
-public class GnomeEngineer implements Engineer {
-
- private GoblinGlider glider;
-
- public GnomeEngineer() {
- glider = new GoblinGlider();
- }
-
- @Override
- public void operateDevice() {
- glider.attachGlider();
- glider.gainSpeed();
- glider.takeOff();
- }
-}
diff --git a/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineeringManager.java b/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineeringManager.java
deleted file mode 100644
index ff4ddb617e45..000000000000
--- a/adapter/src/main/java/com/iluwatar/adapter/GnomeEngineeringManager.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.iluwatar.adapter;
-
-/**
- * GnomeEngineering manager uses {@link Engineer} to operate devices.
- */
-public class GnomeEngineeringManager implements Engineer {
-
- private Engineer engineer;
-
- public GnomeEngineeringManager() {
-
- }
-
- public GnomeEngineeringManager(Engineer engineer) {
- this.engineer = engineer;
- }
-
- @Override
- public void operateDevice() {
- engineer.operateDevice();
- }
-
- public void setEngineer(Engineer engineer) {
- this.engineer = engineer;
- }
-}
diff --git a/adapter/src/main/java/com/iluwatar/adapter/GoblinGlider.java b/adapter/src/main/java/com/iluwatar/adapter/GoblinGlider.java
deleted file mode 100644
index 79a9acef66a3..000000000000
--- a/adapter/src/main/java/com/iluwatar/adapter/GoblinGlider.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.iluwatar.adapter;
-
-/**
- *
- * Device class (adaptee in the pattern).
- *
- */
-public class GoblinGlider {
-
- public void attachGlider() {
- System.out.println("Glider attached.");
- }
-
- public void gainSpeed() {
- System.out.println("Gaining speed.");
- }
-
- public void takeOff() {
- System.out.println("Lift-off!");
- }
-}
diff --git a/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java b/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java
new file mode 100644
index 000000000000..55eeeaf4bd42
--- /dev/null
+++ b/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java
@@ -0,0 +1,34 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+/**
+ * The interface expected by the client.
+ * A rowing boat is rowed to move.
+ */
+public interface RowingBoat {
+
+ void row();
+}
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..c42f2ac86ceb
--- /dev/null
+++ b/adapter/src/main/java/com/iluwatar/adapter/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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 866bfb968d9b..bc4984da3d6f 100644
--- a/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java
+++ b/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java
@@ -1,3 +1,27 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
import static org.mockito.Mockito.spy;
@@ -5,64 +29,46 @@
import java.util.HashMap;
import java.util.Map;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * An adapter helps two incompatible interfaces to work together. This is the real world definition
- * for an adapter. Interfaces may be incompatible but the inner functionality should suit the need.
- * The Adapter design pattern allows otherwise incompatible classes to work together by converting
- * the interface of one class into an interface expected by the clients.
- *
- *
There are two variations of the Adapter pattern:
- * The class adapter implements the adaptee's
- * interface whereas the object adapter uses composition to contain the adaptee in the adapter
- * object. This example uses the object adapter approach.
- *
- *
The Adapter ({@link GnomeEngineer}) converts the interface
- * of the target class ({@link GoblinGlider}) into a suitable one expected by
- * the client ({@link GnomeEngineeringManager}
- * ).
- */
-public class AdapterPatternTest {
+/** Tests for the adapter pattern. */
+class AdapterPatternTest {
private Map beans;
- private static final String ENGINEER_BEAN = "engineer";
+ private static final String FISHING_BEAN = "fisher";
- private static final String MANAGER_BEAN = "manager";
+ private static final String ROWING_BEAN = "captain";
- /**
- * This method runs before the test execution and sets the bean objects in the beans Map.
- */
- @Before
- public void setup() {
+ /** This method runs before the test execution and sets the bean objects in the beans Map. */
+ @BeforeEach
+ void setup() {
beans = new HashMap<>();
- GnomeEngineer gnomeEngineer = spy(new GnomeEngineer());
- beans.put(ENGINEER_BEAN, gnomeEngineer);
+ var fishingBoatAdapter = spy(new FishingBoatAdapter());
+ beans.put(FISHING_BEAN, fishingBoatAdapter);
- GnomeEngineeringManager manager = new GnomeEngineeringManager();
- manager.setEngineer((GnomeEngineer) beans.get(ENGINEER_BEAN));
- beans.put(MANAGER_BEAN, manager);
+ var captain = new Captain();
+ captain.setRowingBoat((FishingBoatAdapter) beans.get(FISHING_BEAN));
+ beans.put(ROWING_BEAN, captain);
}
/**
- * This test asserts that when we call operateDevice() method on a manager bean, it is internally
- * calling operateDevice method on the engineer object. The Adapter ({@link GnomeEngineer})
- * converts the interface of the target class ( {@link GoblinGlider}) into a suitable one expected
- * by the client ({@link GnomeEngineeringManager} ).
+ * This test asserts that when we use the 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() {
- Engineer manager = (Engineer) beans.get(MANAGER_BEAN);
+ void testAdapter() {
+ var captain = (Captain) beans.get(ROWING_BEAN);
- // when manager is asked to operate device
- manager.operateDevice();
+ // when captain moves
+ captain.row();
- // Manager internally calls the engineer object to operateDevice
- Engineer engineer = (Engineer) beans.get(ENGINEER_BEAN);
- verify(engineer).operateDevice();
+ // the captain internally calls the battleship object to move
+ 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
new file mode 100644
index 000000000000..a2cc4c22bcaa
--- /dev/null
+++ b/adapter/src/test/java/com/iluwatar/adapter/AppTest.java
@@ -0,0 +1,40 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import org.junit.jupiter.api.Test;
+
+/** Tests that Adapter example runs without errors. */
+class AppTest {
+
+ /** Check whether the execution of the main method in {@link App} throws an exception. */
+ @Test
+ void shouldExecuteApplicationWithoutException() {
+
+ assertDoesNotThrow(() -> App.main(new String[] {}));
+ }
+}
diff --git a/ambassador/README.md b/ambassador/README.md
new file mode 100644
index 000000000000..fa08223feb75
--- /dev/null
+++ b/ambassador/README.md
@@ -0,0 +1,236 @@
+---
+title: "Ambassador Pattern in Java: Simplifying Remote Resource Management"
+shortTitle: Ambassador
+description: "Explore the Ambassador Pattern in Java, its benefits, use cases, and practical examples. Learn how to decouple and offload common functionalities to improve system performance and maintainability."
+category: Integration
+language: en
+tag:
+ - API design
+ - Decoupling
+ - Fault tolerance
+ - Proxy
+ - Resilience
+ - Scalability
+---
+
+## Intent of Ambassador Design Pattern
+
+The Ambassador Pattern in Java helps offload common functionalities such as monitoring, logging, and routing from a shared resource to a helper service instance, enhancing performance and maintainability in distributed systems.
+
+## Detailed Explanation of Ambassador Pattern with Real-World Examples
+
+Real-world example
+
+> Imagine a busy hotel where guests frequently request restaurant reservations, event tickets, or transportation arrangements. Instead of each guest individually contacting these services, the hotel provides a concierge. The concierge handles these tasks on behalf of the guests, ensuring that reservations are made smoothly, tickets are booked on time, and transportation is scheduled efficiently.
+>
+> In this analogy, the guests are the client services, the external providers (restaurants, ticket vendors, transportation) are the remote services, and the concierge represents the ambassador service. This setup allows the guests to focus on enjoying their stay while the concierge manages the complexities of external interactions, providing a seamless and enhanced experience.
+
+In plain words
+
+> With the Ambassador pattern, we can implement less-frequent polling from clients along with latency checks and logging.
+
+Microsoft documentation states
+
+> An ambassador service can be thought of as an out-of-process proxy which is co-located with the client. This pattern can be useful for offloading common client connectivity tasks such as monitoring, logging, routing, security (such as TLS), and resiliency patterns in a language agnostic way. It is often used with legacy applications, or other applications that are difficult to modify, in order to extend their networking capabilities. It can also enable a specialized team to implement those features.
+
+Sequence diagram
+
+
+
+## Programmatic Example of Ambassador Pattern in Java
+
+In this example of the Ambassador Pattern in Java, we demonstrate how to implement latency checks, logging, and retry mechanisms to improve system reliability.
+
+A remote service has many clients accessing a function it provides. The service is a legacy application and is impossible to update. Large numbers of requests from users are causing connectivity issues. New rules for request frequency should be implemented along with latency checks and client-side logging.
+
+With the above introduction in mind we will imitate the functionality in this example. We have an interface implemented by the remote service as well as the ambassador service.
+
+```java
+interface RemoteServiceInterface {
+ long doRemoteFunction(int value) throws Exception;
+}
+```
+
+A remote services represented as a singleton.
+
+```java
+
+@Slf4j
+public class RemoteService implements RemoteServiceInterface {
+ private static RemoteService service = null;
+
+ static synchronized RemoteService getRemoteService() {
+ if (service == null) {
+ service = new RemoteService();
+ }
+ return service;
+ }
+
+ private RemoteService() {
+ }
+
+ @Override
+ public long doRemoteFunction(int value) {
+ long waitTime = (long) Math.floor(Math.random() * 1000);
+
+ try {
+ sleep(waitTime);
+ } catch (InterruptedException e) {
+ LOGGER.error("Thread sleep interrupted", e);
+ }
+
+ return waitTime >= 200 ? value * 10 : -1;
+ }
+}
+```
+
+A service ambassador adds additional features such as logging and latency checks.
+
+```java
+
+@Slf4j
+public class ServiceAmbassador implements RemoteServiceInterface {
+ private static final int RETRIES = 3;
+ private static final int DELAY_MS = 3000;
+
+ ServiceAmbassador() {
+ }
+
+ @Override
+ public long doRemoteFunction(int value) {
+ return safeCall(value);
+ }
+
+ private long checkLatency(int value) {
+ 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) {
+ var retries = 0;
+ var result = (long) FAILURE;
+
+ for (int i = 0; i < RETRIES; i++) {
+ if (retries >= RETRIES) {
+ return FAILURE;
+ }
+
+ if ((result = checkLatency(value)) == FAILURE) {
+ LOGGER.info("Failed to reach remote: (" + (i + 1) + ")");
+ retries++;
+ try {
+ sleep(DELAY_MS);
+ } catch (InterruptedException e) {
+ LOGGER.error("Thread sleep state interrupted", e);
+ }
+ } else {
+ break;
+ }
+ }
+ return result;
+ }
+}
+```
+
+A client has a local service ambassador used to interact with the remote service.
+
+```java
+
+@Slf4j
+public class Client {
+ private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
+
+ long useService(int value) {
+ var result = serviceAmbassador.doRemoteFunction(value);
+ LOGGER.info("Service result: " + result);
+ return result;
+ }
+}
+```
+
+Here are two clients using the service.
+
+```java
+public class App {
+ public static void main(String[] args) {
+ var host1 = new Client();
+ var host2 = new Client();
+ host1.useService(12);
+ host2.useService(73);
+ }
+}
+```
+
+Here's the output for running the example:
+
+```java
+Time taken(ms):111
+Service result:120
+Time taken(ms):931
+Failed to reach remote:(1)
+Time taken(ms):665
+Failed to reach remote:(2)
+Time taken(ms):538
+Failed to reach remote:(3)
+Service result:-1
+```
+
+## When to Use the Ambassador Pattern in Java
+
+* The Ambassador Pattern is particularly beneficial for Cloud Native and Microservices Architectures in Java. It helps in monitoring, logging, and securing inter-service communication, making it ideal for distributed systems.
+* Legacy System Integration: Facilitates communication with newer services by handling necessary but non-core functionalities.
+* Performance Enhancement: Can be used to cache results or compress data to improve communication efficiency.
+
+Typical use cases include:
+
+* Control access to another object
+* Implement logging
+* Implement circuit breaking
+* Offload remote service tasks
+* Facilitate network connection
+
+## Benefits and Trade-offs of Ambassador Pattern
+
+Benefits:
+
+* Separation of Concerns: Offloads cross-cutting concerns from the service logic, leading to cleaner, more maintainable code.
+* Reusable Infrastructure Logic: The ambassador pattern allows the same logic (e.g., logging, monitoring) to be reused across multiple services.
+* Improved Security: Centralizes security features like SSL termination or authentication, reducing the risk of misconfiguration.
+* Flexibility: Makes it easier to update or replace infrastructure concerns without modifying the service code.
+
+Trade-offs:
+
+* Increased Complexity: Adds another layer to the architecture, which can complicate the system design and debugging.
+* Potential Performance Overhead: The additional network hop can introduce latency and overhead, particularly if not optimized.
+* Deployment Overhead: Requires additional resources and management for deploying and scaling ambassador services.
+
+## Real-World Applications of Ambassador Pattern in Java
+
+* Service Mesh Implementations: In a service mesh architecture, like Istio or Linkerd, the Ambassador pattern is often employed as a sidecar proxy that handles inter-service communications. This includes tasks such as service discovery, routing, load balancing, telemetry (metrics and tracing), and security (authentication and authorization).
+* API Gateways: API gateways can use the Ambassador pattern to encapsulate common functionalities like rate limiting, caching, request shaping, and authentication. This allows backend services to focus on their core business logic without being burdened by these cross-cutting concerns.
+* Logging and Monitoring: An Ambassador can aggregate logs and metrics from various services and forward them to centralized monitoring tools like Prometheus or ELK Stack (Elasticsearch, Logstash, Kibana). This simplifies the logging and monitoring setup for each service and provides a unified view of the system's health.
+* Security: Security-related functionalities such as SSL/TLS termination, identity verification, and encryption can be managed by an Ambassador. This ensures consistent security practices across services and reduces the likelihood of security breaches due to misconfigurations.
+* Resilience: The Ambassador can implement resilience patterns like circuit breakers, retries, and timeouts. For instance, Netflix's Hystrix library can be used within an Ambassador to prevent cascading failures in a microservices ecosystem.
+* Database Proxy: Ambassadors can act as proxies for database connections, providing functionalities like connection pooling, read/write splitting for replicas, and query caching. This offloads significant complexity from the application services.
+* Legacy System Integration: In scenarios where modern microservices need to communicate with legacy systems, an Ambassador can serve as an intermediary that translates protocols, handles necessary transformations, and implements modern security practices, easing the integration process.
+* Network Optimization: For services deployed across different geographical locations or cloud regions, Ambassadors can optimize communication by compressing data, batching requests, or even implementing smart routing to reduce latency and costs.
+* [Kubernetes-native API gateway for microservices](https://github.com/datawire/ambassador)
+
+## Related Java Design Patterns
+
+* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/): Often used in conjunction to manage fault tolerance by stopping calls to an unresponsive service.
+* [Decorator](https://java-design-patterns.com/patterns/decorator/): The decorator pattern is used to add functionality to an object dynamically, while the ambassador pattern is used to offload functionality to a separate object.
+* [Proxy](https://java-design-patterns.com/patterns/proxy/): Shares similarities with the proxy pattern, but the ambassador pattern specifically focuses on offloading ancillary functionalities.
+* Sidecar: A similar pattern used in the context of containerized applications, where a sidecar container provides additional functionality to the main application container.
+
+## References and Credits
+
+* [Building Microservices: Designing Fine-Grained Systems](https://amzn.to/43aGpSR)
+* [Cloud Native Patterns: Designing Change-tolerant Software](https://amzn.to/3wUAl4O)
+* [Designing Distributed Systems: Patterns and Paradigms for Scalable, Reliable Services](https://amzn.to/3T9g9Uj)
+* [Microservices Patterns: With examples in Java](https://amzn.to/3UyWD5O)
+* [Ambassador pattern (Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador)
diff --git a/ambassador/etc/ambassador-sequence-diagram.png b/ambassador/etc/ambassador-sequence-diagram.png
new file mode 100644
index 000000000000..71e6a947c5dd
Binary files /dev/null and b/ambassador/etc/ambassador-sequence-diagram.png differ
diff --git a/ambassador/etc/ambassador.urm.png b/ambassador/etc/ambassador.urm.png
new file mode 100644
index 000000000000..9b50a02ad356
Binary files /dev/null and b/ambassador/etc/ambassador.urm.png differ
diff --git a/ambassador/etc/ambassador.urm.puml b/ambassador/etc/ambassador.urm.puml
new file mode 100644
index 000000000000..517b0bf51d07
--- /dev/null
+++ b/ambassador/etc/ambassador.urm.puml
@@ -0,0 +1,47 @@
+@startuml
+package com.iluwatar.ambassador.util {
+ interface RandomProvider {
+ + random() : double {abstract}
+ }
+}
+package com.iluwatar.ambassador {
+ class App {
+ + App()
+ + main(args : String[]) {static}
+ }
+ class Client {
+ - LOGGER : Logger {static}
+ - serviceAmbassador : ServiceAmbassador
+ + Client()
+ ~ useService(value : int) : long
+ }
+ class RemoteService {
+ - LOGGER : Logger {static}
+ - THRESHOLD : int {static}
+ - randomProvider : RandomProvider
+ - service : RemoteService {static}
+ - RemoteService()
+ ~ RemoteService(randomProvider : RandomProvider)
+ + doRemoteFunction(value : int) : long
+ ~ getRemoteService() : RemoteService {static}
+ }
+ ~interface RemoteServiceInterface {
+ + FAILURE : int {static}
+ + doRemoteFunction(int) : long {abstract}
+ }
+ class ServiceAmbassador {
+ - DELAY_MS : int {static}
+ - LOGGER : Logger {static}
+ - RETRIES : int {static}
+ ~ ServiceAmbassador()
+ - checkLatency(value : int) : long
+ + doRemoteFunction(value : int) : long
+ - safeCall(value : int) : long
+ }
+}
+RemoteService --> "-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
new file mode 100644
index 000000000000..15e4a07f0dc3
--- /dev/null
+++ b/ambassador/pom.xml
@@ -0,0 +1,70 @@
+
+
+
+
+ java-design-patterns
+ com.iluwatar
+ 1.26.0-SNAPSHOT
+
+ 4.0.0
+ ambassador
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ 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
new file mode 100644
index 000000000000..8de149fe0813
--- /dev/null
+++ b/ambassador/src/main/java/com/iluwatar/ambassador/App.java
@@ -0,0 +1,51 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.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.
+ *
+ *
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. */
+ public static void main(String[] args) {
+ 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
new file mode 100644
index 000000000000..0baabf4ffc06
--- /dev/null
+++ b/ambassador/src/main/java/com/iluwatar/ambassador/Client.java
@@ -0,0 +1,40 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.ambassador;
+
+import lombok.extern.slf4j.Slf4j;
+
+/** A simple Client. */
+@Slf4j
+public class Client {
+
+ private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
+
+ long useService(int 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
new file mode 100644
index 000000000000..d99348040cfe
--- /dev/null
+++ b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java
@@ -0,0 +1,78 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.ambassador;
+
+import static java.lang.Thread.sleep;
+
+import com.iluwatar.ambassador.util.RandomProvider;
+import lombok.extern.slf4j.Slf4j;
+
+/** A remote legacy application represented by a Singleton implementation. */
+@Slf4j
+public class RemoteService implements RemoteServiceInterface {
+ private static final int THRESHOLD = 200;
+ private static RemoteService service = null;
+ private final RandomProvider randomProvider;
+
+ static synchronized RemoteService getRemoteService() {
+ if (service == null) {
+ service = new RemoteService();
+ }
+ return service;
+ }
+
+ private RemoteService() {
+ this(Math::random);
+ }
+
+ /** 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.
+ *
+ * @param value integer value to be multiplied.
+ * @return if waitTime is less than {@link RemoteService#THRESHOLD}, it returns value * 10,
+ * otherwise {@link RemoteServiceStatus#FAILURE}.
+ */
+ @Override
+ public long doRemoteFunction(int value) {
+
+ long waitTime = (long) Math.floor(randomProvider.random() * 1000);
+
+ try {
+ sleep(waitTime);
+ } catch (InterruptedException e) {
+ LOGGER.error("Thread sleep state interrupted", e);
+ Thread.currentThread().interrupt();
+ }
+ return waitTime <= THRESHOLD
+ ? value * 10
+ : RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue();
+ }
+}
diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java
new file mode 100644
index 000000000000..aa6012bae33f
--- /dev/null
+++ b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java
@@ -0,0 +1,31 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.ambassador;
+
+/** Interface shared by ({@link RemoteService}) and ({@link ServiceAmbassador}). */
+interface RemoteServiceInterface {
+
+ long doRemoteFunction(int value);
+}
diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceStatus.java b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceStatus.java
new file mode 100644
index 000000000000..8549ed7247f3
--- /dev/null
+++ b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceStatus.java
@@ -0,0 +1,44 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.ambassador;
+
+import lombok.Getter;
+
+/**
+ * Holds information regarding the status of the Remote Service.
+ *
+ *
This Enum replaces the integer value previously stored in {@link RemoteServiceInterface} as
+ * SonarCloud was identifying it as an issue. All test cases have been checked after changes,
+ * without failures.
+ */
+public enum RemoteServiceStatus {
+ FAILURE(-1);
+
+ @Getter private final long remoteServiceStatusValue;
+
+ RemoteServiceStatus(long remoteServiceStatusValue) {
+ this.remoteServiceStatusValue = remoteServiceStatusValue;
+ }
+}
diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java b/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java
new file mode 100644
index 000000000000..4d310169770b
--- /dev/null
+++ b/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java
@@ -0,0 +1,83 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.ambassador;
+
+import static com.iluwatar.ambassador.RemoteServiceStatus.FAILURE;
+import static java.lang.Thread.sleep;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 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.
+ */
+@Slf4j
+public class ServiceAmbassador implements RemoteServiceInterface {
+
+ private static final int RETRIES = 3;
+ private static final int DELAY_MS = 3000;
+
+ ServiceAmbassador() {}
+
+ @Override
+ public long doRemoteFunction(int value) {
+ return safeCall(value);
+ }
+
+ private long checkLatency(int value) {
+ 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) {
+ var retries = 0;
+ var result = FAILURE.getRemoteServiceStatusValue();
+
+ for (int i = 0; i < RETRIES; i++) {
+ if (retries >= RETRIES) {
+ return FAILURE.getRemoteServiceStatusValue();
+ }
+
+ if ((result = checkLatency(value)) == FAILURE.getRemoteServiceStatusValue()) {
+ LOGGER.info("Failed to reach remote: ({})", i + 1);
+ retries++;
+ try {
+ sleep(DELAY_MS);
+ } catch (InterruptedException e) {
+ LOGGER.error("Thread sleep state interrupted", e);
+ Thread.currentThread().interrupt();
+ }
+ } else {
+ break;
+ }
+ }
+ return result;
+ }
+}
diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java b/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java
new file mode 100644
index 000000000000..4eba2fada7fa
--- /dev/null
+++ b/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java
@@ -0,0 +1,30 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.ambassador.util;
+
+/** An interface for randomness. Useful for testing purposes. */
+public interface RandomProvider {
+ double random();
+}
diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java
new file mode 100644
index 000000000000..ddb2d6eff411
--- /dev/null
+++ b/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java
@@ -0,0 +1,45 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.ambassador;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import org.junit.jupiter.api.Test;
+
+/** Application test */
+class AppTest {
+
+ /**
+ * Issue: Add at least one assertion to this test case.
+ *
+ *
Solution: Inserted assertion to check whether the execution of the main method in {@link
+ * App} throws an exception.
+ */
+ @Test
+ void shouldExecuteApplicationWithoutException() {
+
+ assertDoesNotThrow(() -> 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
new file mode 100644
index 000000000000..24603efff9d4
--- /dev/null
+++ b/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java
@@ -0,0 +1,42 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.ambassador;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/** Test for {@link Client} */
+class ClientTest {
+
+ @Test
+ void test() {
+ Client client = new Client();
+ var result = client.useService(10);
+
+ assertTrue(
+ result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue());
+ }
+}
diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java
new file mode 100644
index 000000000000..81e4f744128f
--- /dev/null
+++ b/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java
@@ -0,0 +1,61 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.ambassador;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import com.iluwatar.ambassador.util.RandomProvider;
+import org.junit.jupiter.api.Test;
+
+/** Test for {@link RemoteService} */
+class RemoteServiceTest {
+
+ @Test
+ void testFailedCall() {
+ var remoteService = new RemoteService(new StaticRandomProvider(0.21));
+ var result = remoteService.doRemoteFunction(10);
+ assertEquals(RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue(), result);
+ }
+
+ @Test
+ void testSuccessfulCall() {
+ var remoteService = new RemoteService(new StaticRandomProvider(0.2));
+ var result = remoteService.doRemoteFunction(10);
+ assertEquals(100, result);
+ }
+
+ private static class StaticRandomProvider implements RandomProvider {
+ private final double value;
+
+ StaticRandomProvider(double value) {
+ this.value = value;
+ }
+
+ @Override
+ public double random() {
+ return value;
+ }
+ }
+}
diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java
new file mode 100644
index 000000000000..0543b2e7e370
--- /dev/null
+++ b/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java
@@ -0,0 +1,40 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.ambassador;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/** Test for {@link ServiceAmbassador} */
+class ServiceAmbassadorTest {
+
+ @Test
+ void test() {
+ long result = new ServiceAmbassador().doRemoteFunction(10);
+ assertTrue(
+ result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue());
+ }
+}
diff --git a/anti-corruption-layer/README.md b/anti-corruption-layer/README.md
new file mode 100644
index 000000000000..9b6042850eba
--- /dev/null
+++ b/anti-corruption-layer/README.md
@@ -0,0 +1,185 @@
+---
+title: "Anti-Corruption Layer Pattern in Java: Ensuring System Integrity Amidst Legacy Systems"
+shortTitle: Anti-Corruption Layer
+description: "Learn how the Anti-Corruption Layer design pattern helps in decoupling subsystems, preventing data corruption, and facilitating seamless integration in Java applications."
+category: Integration
+language: en
+tag:
+ - Architecture
+ - Decoupling
+ - Integration
+ - Isolation
+ - Layered architecture
+ - Migration
+ - Modernization
+ - Refactoring
+ - Wrapping
+---
+
+## Also known as
+
+* ACL
+* Interface layer
+* Translation layer
+
+## Intent of Anti-Corruption Layer Design Pattern
+
+The Anti-Corruption Layer (ACL) is a crucial design pattern in Java development, particularly for system integration and maintaining data integrity. Implement a façade or adapter layer between different subsystems that don't share the same semantics. It translates between different data formats and systems, ensuring that the integration between systems does not lead to corruption of business logic or data integrity.
+
+## Detailed Explanation of Anti-Corruption Layer Pattern with Real-World Examples
+
+Real-world example
+
+> This example demonstrates how the Anti-Corruption Layer ensures seamless integration between legacy systems and modern platforms, crucial for maintaining business logic integrity during system migration.
+>
+> Imagine a large retail company transitioning its inventory management system from an old legacy software to a new modern platform. The legacy system has been in use for decades and contains complex business rules and data formats that are incompatible with the new system. Instead of directly connecting the new system to the legacy one, the company implements an Anti-Corruption Layer (ACL).
+>
+> The ACL acts as a mediator, translating and adapting data between the two systems. When the new system requests inventory data, the ACL translates the request into a format the legacy system understands, retrieves the data, and then translates it back into a format suitable for the new system. This approach ensures that the new system remains unaffected by the intricacies of the legacy system, preventing corruption of data and business logic while facilitating a smooth transition.
+
+In plain words
+
+> The Anti-Corruption Layer design pattern protects a system from the complexities and changes of external systems by providing an intermediary translation layer.
+
+[Microsoft's documentation](https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer) says
+
+> Implement a façade or adapter layer between different subsystems that don't share the same semantics. This layer translates requests that one subsystem makes to the other subsystem. Use this pattern to ensure that an application's design is not limited by dependencies on outside subsystems. This pattern was first described by Eric Evans in Domain-Driven Design.
+
+Sequence diagram
+
+
+
+## Programmatic Example of Anti-Corruption Layer Pattern in Java
+
+The ACL design pattern in Java provides an intermediary layer that translates data formats, ensuring that integration between different systems does not lead to data corruption.
+
+Here are 2 shop-ordering systems: `Legacy` and `Modern`.
+
+The aforementioned systems have different domain models and have to operate simultaneously. Since they work independently the orders can come either from the `Legacy` or `Modern` system. Therefore, the system that receives the legacyOrder needs to check if the legacyOrder is valid and not present in the other system. Then it can place the legacyOrder in its own system.
+
+But for that, the system needs to know the domain model of the other system and to avoid that, the anti-corruption layer(ACL) is introduced. The ACL is a layer that translates the domain model of the `Legacy` system to the domain model of the `Modern` system and vice versa. Also, it hides all other operations with the other system, uncoupling the systems.
+
+Domain model of the `Legacy` system:
+
+```java
+public class LegacyOrder {
+ private String id;
+ private String customer;
+ private String item;
+ private String qty;
+ private String price;
+}
+```
+
+Domain model of the `Modern` system:
+
+```java
+public class ModernOrder {
+ private String id;
+ private Customer customer;
+
+ private Shipment shipment;
+
+ private String extra;
+}
+
+public class Customer {
+ private String address;
+}
+
+public class Shipment {
+ private String item;
+ private String qty;
+ private String price;
+}
+```
+
+Anti-corruption layer:
+
+```java
+public class AntiCorruptionLayer {
+
+ @Autowired
+ private ModernShop modernShop;
+
+ @Autowired
+ private LegacyShop legacyShop;
+
+ public Optional findOrderInModernSystem(String id) {
+ return modernShop.findOrder(id).map(o -> /* map to legacyOrder*/);
+ }
+
+ public Optional findOrderInLegacySystem(String id) {
+ return legacyShop.findOrder(id).map(o -> /* map to modernOrder*/);
+ }
+
+}
+```
+
+The connection between the systems. Wherever the `Legacy` or `Modern` system needs to communicate with the counterpart the ACL needs to be used to avoid corrupting the current domain model. The example below shows how the `Legacy` system places an order with a validation from the `Modern` system.
+
+```java
+public class LegacyShop {
+ @Autowired
+ private AntiCorruptionLayer acl;
+
+ public void placeOrder(LegacyOrder legacyOrder) throws ShopException {
+
+ String id = legacyOrder.getId();
+
+ Optional orderInModernSystem = acl.findOrderInModernSystem(id);
+
+ if (orderInModernSystem.isPresent()) {
+ // order is already in the modern system
+ } else {
+ // place order in the current system
+ }
+ }
+}
+```
+
+## When to Use the Anti-Corruption Layer Pattern in Java
+
+Use this pattern when:
+
+* A migration is planned to happen over multiple stages, but integration between new and legacy systems needs to be maintained
+* Two or more subsystems have different semantics, but still need to communicate
+* When integrating with legacy systems or external systems where direct integration might pollute the domain model of the new system
+* In scenarios where different subsystems within a larger system use different data formats or structures
+* When there is a need to ensure loose coupling between different subsystems or external services to facilitate easier maintenance and scalability
+
+## Anti-Corruption Layer Pattern Java Tutorials
+
+* [Anti-Corruption Layer (Microsoft)](https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer)
+* [Anti-Corruption Layer Pattern (Amazon)](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/acl.html)
+
+## Real-World Applications of Anti-Corruption Layer Pattern in Java
+
+* Microservices architectures where individual services must communicate without being tightly coupled to each other’s data schemas
+* Enterprise systems integration, especially when integrating modern systems with legacy systems
+* In bounded contexts within Domain-Driven Design (DDD) to maintain the integrity of a domain model when interacting with external systems or subsystems
+
+## Benefits and Trade-offs of Anti-Corruption Layer Pattern
+
+Benefits:
+
+* Protects the integrity of the domain model by providing a clear boundary
+* Promotes loose coupling between systems, making the system more resilient to changes in external systems
+* Facilitates cleaner and more maintainable code by isolating integration code from business logic
+
+Trade-offs:
+
+* Introduces additional complexity and potential performance overhead due to the translation process
+* Requires extra effort in design and implementation to ensure the layer is effective without becoming a bottleneck
+* Can lead to duplication of models if not carefully managed
+
+## Related Java Design Patterns
+
+* [Adapter](https://java-design-patterns.com/patterns/adapter/): The Anti-Corruption Layer can be implemented using the Adapter pattern to translate between different data formats or structures
+* [Facade](https://java-design-patterns.com/patterns/facade/): The Anti-Corruption Layer can be seen as a specialized form of the Facade pattern that is used to isolate different subsystems
+* [Gateway](https://java-design-patterns.com/patterns/gateway/): The Anti-Corruption Layer can be used as a Gateway to external systems to provide a unified interface
+
+## References and Credits
+
+* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3vptcJz)
+* [Implementing Domain-Driven Design](https://amzn.to/3ISOSRA)
+* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR)
diff --git a/anti-corruption-layer/etc/anti-corruption-layer-sequence-diagram.png b/anti-corruption-layer/etc/anti-corruption-layer-sequence-diagram.png
new file mode 100644
index 000000000000..835ff4e84a0f
Binary files /dev/null and b/anti-corruption-layer/etc/anti-corruption-layer-sequence-diagram.png differ
diff --git a/anti-corruption-layer/etc/anti-corruption-layer.urm.png b/anti-corruption-layer/etc/anti-corruption-layer.urm.png
new file mode 100644
index 000000000000..96e432121e7e
Binary files /dev/null and b/anti-corruption-layer/etc/anti-corruption-layer.urm.png differ
diff --git a/anti-corruption-layer/etc/anti-corruption-layer.urm.puml b/anti-corruption-layer/etc/anti-corruption-layer.urm.puml
new file mode 100644
index 000000000000..6b1d1cdb9b81
--- /dev/null
+++ b/anti-corruption-layer/etc/anti-corruption-layer.urm.puml
@@ -0,0 +1,25 @@
+@startuml
+package com.iluwatar.corruption {
+class LegacyShop {
+ private Store store;
+ private AntiCorruptionLayer acl;
+}
+
+class ModernShop {
+ private Store store;
+ private AntiCorruptionLayer acl;
+}
+
+class AntiCorruptionLayer{
+ private LegacyShop legacyShop;
+ private ModernShop modernShop;
+
+
+ }
+LegacyShop ---> "findOrderInModernSystem" AntiCorruptionLayer
+ModernShop ---> "findOrderInLegacySystem" AntiCorruptionLayer
+AntiCorruptionLayer ..|> ModernShop
+AntiCorruptionLayer ..|> LegacyShop
+}
+
+@enduml
\ No newline at end of file
diff --git a/anti-corruption-layer/pom.xml b/anti-corruption-layer/pom.xml
new file mode 100644
index 000000000000..2fddfd3ecc46
--- /dev/null
+++ b/anti-corruption-layer/pom.xml
@@ -0,0 +1,72 @@
+
+
+
+
+ java-design-patterns
+ com.iluwatar
+ 1.26.0-SNAPSHOT
+
+ 4.0.0
+ anti-corruption-layer
+ jar
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+
+
+ com.iluwatar.corruption.App
+
+
+
+
+
+
+
+
+
diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/App.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/App.java
new file mode 100644
index 000000000000..f7cf8f075f83
--- /dev/null
+++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/App.java
@@ -0,0 +1,40 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.corruption;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * This layer translates communications between the two systems, allowing one system to remain
+ * unchanged while the other can avoid compromising its design and technological approach.
+ */
+@SpringBootApplication
+public class App {
+
+ public static void main(String[] args) {
+ SpringApplication.run(App.class, args);
+ }
+}
diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/package-info.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/package-info.java
new file mode 100644
index 000000000000..880d98c7d5b0
--- /dev/null
+++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/package-info.java
@@ -0,0 +1,48 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.
+ */
+/**
+ * Context and problem Most applications rely on other systems for some data or functionality. For
+ * example, when a legacy application is migrated to a modern system, it may still need existing
+ * legacy resources. New features must be able to call the legacy system. This is especially true of
+ * gradual migrations, where different features of a larger application are moved to a modern system
+ * over time.
+ *
+ * Often these legacy systems suffer from quality issues such as convoluted data schemas or
+ * obsolete APIs. The features and technologies used in legacy systems can vary widely from more
+ * modern systems. To interoperate with the legacy system, the new application may need to support
+ * outdated infrastructure, protocols, data models, APIs, or other features that you wouldn't
+ * otherwise put into a modern application.
+ *
+ *
Maintaining access between new and legacy systems can force the new system to adhere to at
+ * least some of the legacy system's APIs or other semantics. When these legacy features have
+ * quality issues, supporting them "corrupts" what might otherwise be a cleanly designed modern
+ * application. Similar issues can arise with any external system that your development team doesn't
+ * control, not just legacy systems.
+ *
+ *
Solution Isolate the different subsystems by placing an anti-corruption layer between them.
+ * This layer translates communications between the two systems, allowing one system to remain
+ * unchanged while the other can avoid compromising its design and technological approach.
+ */
+package com.iluwatar.corruption;
diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/AntiCorruptionLayer.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/AntiCorruptionLayer.java
new file mode 100644
index 000000000000..4e8a17fa5d2a
--- /dev/null
+++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/AntiCorruptionLayer.java
@@ -0,0 +1,66 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.corruption.system;
+
+import com.iluwatar.corruption.system.legacy.LegacyShop;
+import com.iluwatar.corruption.system.modern.Customer;
+import com.iluwatar.corruption.system.modern.ModernOrder;
+import com.iluwatar.corruption.system.modern.Shipment;
+import java.util.Optional;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * The class represents an anti-corruption layer. The main purpose of the class is to provide a
+ * layer between the modern and legacy systems. The class is responsible for converting the data
+ * from one system to another decoupling the systems to each other
+ *
+ *
It allows using one system a domain model of the other system without changing the domain
+ * model of the system.
+ */
+@Service
+public class AntiCorruptionLayer {
+
+ @Autowired private LegacyShop legacyShop;
+
+ /**
+ * The method converts the order from the legacy system to the modern system.
+ *
+ * @param id the id of the order
+ * @return the order in the modern system
+ */
+ public Optional findOrderInLegacySystem(String id) {
+
+ return legacyShop
+ .findOrder(id)
+ .map(
+ o ->
+ new ModernOrder(
+ o.getId(),
+ new Customer(o.getCustomer()),
+ new Shipment(o.getItem(), o.getQty(), o.getPrice()),
+ ""));
+ }
+}
diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/DataStore.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/DataStore.java
new file mode 100644
index 000000000000..e84578528be7
--- /dev/null
+++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/DataStore.java
@@ -0,0 +1,49 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.corruption.system;
+
+import java.util.HashMap;
+import java.util.Optional;
+
+/**
+ * The class represents a data store for the modern system.
+ *
+ * @param the type of the value stored in the data store
+ */
+public abstract class DataStore {
+ private final HashMap inner;
+
+ public DataStore() {
+ inner = new HashMap<>();
+ }
+
+ public Optional get(String key) {
+ return Optional.ofNullable(inner.get(key));
+ }
+
+ public Optional put(String key, V value) {
+ return Optional.ofNullable(inner.put(key, value));
+ }
+}
diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/ShopException.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/ShopException.java
new file mode 100644
index 000000000000..c0acd288ed0c
--- /dev/null
+++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/ShopException.java
@@ -0,0 +1,50 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.corruption.system;
+
+/** The class represents a general exception for the shop. */
+public class ShopException extends Exception {
+ public ShopException(String message) {
+ super(message);
+ }
+
+ /**
+ * Throws an exception that the order is already placed but has an incorrect data.
+ *
+ * @param lhs the incoming order
+ * @param rhs the existing order
+ * @return the exception
+ * @throws ShopException the exception
+ */
+ public static ShopException throwIncorrectData(String lhs, String rhs) throws ShopException {
+ throw new ShopException(
+ "The order is already placed but has an incorrect data:\n"
+ + "Incoming order: "
+ + lhs
+ + "\n"
+ + "Existing order: "
+ + rhs);
+ }
+}
diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyOrder.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyOrder.java
new file mode 100644
index 000000000000..45faa06cb26c
--- /dev/null
+++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyOrder.java
@@ -0,0 +1,43 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.corruption.system.legacy;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * The class represents an order in the legacy system. The class is used by the legacy system to
+ * store the data.
+ */
+@Data
+@AllArgsConstructor
+public class LegacyOrder {
+ private String id;
+ private String customer;
+
+ private String item;
+ private int qty;
+ private int price;
+}
diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyShop.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyShop.java
new file mode 100644
index 000000000000..b74eb1c29718
--- /dev/null
+++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyShop.java
@@ -0,0 +1,52 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.corruption.system.legacy;
+
+import java.util.Optional;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * The class represents a legacy shop system. The main purpose is to place the order from the
+ * customers.
+ */
+@Service
+public class LegacyShop {
+ @Autowired private LegacyStore store;
+
+ /**
+ * Places the order in the legacy system. If the order is already present in the modern system,
+ * then the order is placed only if the data is the same. If the order is not present in the
+ * modern system, then the order is placed in the legacy system.
+ */
+ public void placeOrder(LegacyOrder legacyOrder) {
+ store.put(legacyOrder.getId(), legacyOrder);
+ }
+
+ /** Finds the order in the legacy system. */
+ public Optional findOrder(String orderId) {
+ return store.get(orderId);
+ }
+}
diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyStore.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyStore.java
new file mode 100644
index 000000000000..ec1d613a7235
--- /dev/null
+++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyStore.java
@@ -0,0 +1,35 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.corruption.system.legacy;
+
+import com.iluwatar.corruption.system.DataStore;
+import org.springframework.stereotype.Service;
+
+/**
+ * The class represents a data store for the legacy system. The class is used by the legacy system
+ * to store the data.
+ */
+@Service
+public class LegacyStore extends DataStore {}
diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Customer.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Customer.java
new file mode 100644
index 000000000000..130f36d39674
--- /dev/null
+++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Customer.java
@@ -0,0 +1,35 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.corruption.system.modern;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/** The class represents a customer in the modern system. */
+@Data
+@AllArgsConstructor
+public class Customer {
+ private String address;
+}
diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernOrder.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernOrder.java
new file mode 100644
index 000000000000..7b62985015d6
--- /dev/null
+++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernOrder.java
@@ -0,0 +1,40 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.corruption.system.modern;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/** The class represents an order in the modern system. */
+@Data
+@AllArgsConstructor
+public class ModernOrder {
+ private String id;
+ private Customer customer;
+
+ private Shipment shipment;
+
+ private String extra;
+}
diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernShop.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernShop.java
new file mode 100644
index 000000000000..24080abe1533
--- /dev/null
+++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernShop.java
@@ -0,0 +1,67 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.corruption.system.modern;
+
+import com.iluwatar.corruption.system.AntiCorruptionLayer;
+import com.iluwatar.corruption.system.ShopException;
+import java.util.Optional;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * The class represents a modern shop system. The main purpose of the class is to place orders and
+ * find orders.
+ */
+@Service
+public class ModernShop {
+ @Autowired private ModernStore store;
+
+ @Autowired private AntiCorruptionLayer acl;
+
+ /**
+ * Places the order in the modern system. If the order is already present in the legacy system,
+ * then no need to place it again.
+ */
+ public void placeOrder(ModernOrder order) throws ShopException {
+
+ String id = order.getId();
+ // check if the order is already present in the legacy system
+ Optional orderInObsoleteSystem = acl.findOrderInLegacySystem(id);
+
+ if (orderInObsoleteSystem.isPresent()) {
+ var legacyOrder = orderInObsoleteSystem.get();
+ if (!order.equals(legacyOrder)) {
+ throw ShopException.throwIncorrectData(legacyOrder.toString(), order.toString());
+ }
+ } else {
+ store.put(id, order);
+ }
+ }
+
+ /** Finds the order in the modern system. */
+ public Optional findOrder(String orderId) {
+ return store.get(orderId);
+ }
+}
diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernStore.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernStore.java
new file mode 100644
index 000000000000..4fb3952fae5e
--- /dev/null
+++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernStore.java
@@ -0,0 +1,32 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.corruption.system.modern;
+
+import com.iluwatar.corruption.system.DataStore;
+import org.springframework.stereotype.Service;
+
+/** The class represents a data store for the modern system. */
+@Service
+public class ModernStore extends DataStore {}
diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Shipment.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Shipment.java
new file mode 100644
index 000000000000..085a3921ceeb
--- /dev/null
+++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Shipment.java
@@ -0,0 +1,40 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.corruption.system.modern;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * The class represents a shipment in the modern system. The class is used by the modern system to
+ * store the data.
+ */
+@Data
+@AllArgsConstructor
+public class Shipment {
+ private String item;
+ private int qty;
+ private int price;
+}
diff --git a/anti-corruption-layer/src/main/resources/application.properties b/anti-corruption-layer/src/main/resources/application.properties
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/anti-corruption-layer/src/test/java/com/iluwatar/corruption/system/AntiCorruptionLayerTest.java b/anti-corruption-layer/src/test/java/com/iluwatar/corruption/system/AntiCorruptionLayerTest.java
new file mode 100644
index 000000000000..ee46d124eee6
--- /dev/null
+++ b/anti-corruption-layer/src/test/java/com/iluwatar/corruption/system/AntiCorruptionLayerTest.java
@@ -0,0 +1,96 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.corruption.system;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import com.iluwatar.corruption.system.legacy.LegacyOrder;
+import com.iluwatar.corruption.system.legacy.LegacyShop;
+import com.iluwatar.corruption.system.modern.Customer;
+import com.iluwatar.corruption.system.modern.ModernOrder;
+import com.iluwatar.corruption.system.modern.ModernShop;
+import com.iluwatar.corruption.system.modern.Shipment;
+import java.util.Optional;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest
+public class AntiCorruptionLayerTest {
+
+ @Autowired private LegacyShop legacyShop;
+
+ @Autowired private ModernShop modernShop;
+
+ /**
+ * Test the anti-corruption layer. Main intention is to demonstrate how the anti-corruption layer
+ * works. The 2 shops (modern and legacy) should operate independently and in the same time
+ * synchronize the data.
+ */
+ @Test
+ public void antiCorruptionLayerTest() throws ShopException {
+ // a new order comes to the legacy shop.
+ LegacyOrder legacyOrder = new LegacyOrder("1", "addr1", "item1", 1, 1);
+ // place the order in the legacy shop.
+ legacyShop.placeOrder(legacyOrder);
+ // the order is placed as usual since there is no other orders with the id in the both systems.
+ Optional legacyOrderWithIdOne = legacyShop.findOrder("1");
+ assertEquals(Optional.of(legacyOrder), legacyOrderWithIdOne);
+
+ // a new order (or maybe just the same order) appears in the modern shop
+ ModernOrder modernOrder =
+ new ModernOrder("1", new Customer("addr1"), new Shipment("item1", 1, 1), "");
+ // the system places it, but it checks if there is an order with the same id in the legacy shop.
+ modernShop.placeOrder(modernOrder);
+
+ Optional modernOrderWithIdOne = modernShop.findOrder("1");
+ // there is no new order placed since there is already an order with the same id in the legacy
+ // shop.
+ assertTrue(modernOrderWithIdOne.isEmpty());
+ }
+
+ /**
+ * Test the anti-corruption layer when a conflict occurs between systems. This test ensures that
+ * an exception is thrown when conflicting orders are placed.
+ */
+ @Test
+ public void antiCorruptionLayerWithExTest() throws ShopException {
+ // a new order comes to the legacy shop.
+ LegacyOrder legacyOrder = new LegacyOrder("1", "addr1", "item1", 1, 1);
+ // place the order in the legacy shop.
+ legacyShop.placeOrder(legacyOrder);
+ // the order is placed as usual since there is no other orders with the id in the both systems.
+ Optional legacyOrderWithIdOne = legacyShop.findOrder("1");
+ assertEquals(Optional.of(legacyOrder), legacyOrderWithIdOne);
+ // a new order but with the same id and different data appears in the modern shop
+ ModernOrder modernOrder =
+ new ModernOrder("1", new Customer("addr1"), new Shipment("item1", 10, 1), "");
+ // the system rejects the order since there are 2 orders with contradiction there.
+ assertThrows(ShopException.class, () -> modernShop.placeOrder(modernOrder));
+ }
+}
diff --git a/arrange-act-assert/README.md b/arrange-act-assert/README.md
new file mode 100644
index 000000000000..584d8601a23e
--- /dev/null
+++ b/arrange-act-assert/README.md
@@ -0,0 +1,167 @@
+---
+title: "Arrange/Act/Assert Pattern in Java: Enhance Testing Clarity and Simplicity"
+shortTitle: Arrange/Act/Assert
+description: "Learn how to use the Arrange/Act/Assert pattern to structure your unit tests in Java. Improve readability and maintainability of your code with clear testing phases."
+category: Testing
+language: en
+tag:
+ - Code simplification
+ - Isolation
+ - Testing
+---
+
+## Also known as
+
+* Given/When/Then
+
+## Intent of Arrange/Act/Assert Design Pattern
+
+The Arrange/Act/Assert pattern is essential in unit testing in Java. This testing method structures unit tests clearly by dividing them into three distinct sections: setup (Arrange), execution (Act), and verification (Assert).
+
+## Detailed Explanation of Arrange/Act/Assert Pattern with Real-World Examples
+
+Real-world example
+
+> Imagine you are organizing a small event. To ensure everything runs smoothly, you follow a pattern similar to Arrange/Act/Assert:
+>
+> 1. **Arrange**: You set up the venue, prepare the guest list, arrange seating, and organize the catering.
+> 2. **Act**: You conduct the event according to the plan, welcoming guests, serving food, and following the schedule.
+> 3. **Assert**: After the event, you evaluate its success by checking guest feedback, ensuring all tasks were completed, and reviewing if everything went as planned.
+>
+> This clear separation of preparation, execution, and evaluation helps ensure the event is well-organized and successful, mirroring the structured approach of the Arrange/Act/Assert pattern in software testing.
+
+In plain words
+
+> Arrange/Act/Assert is a testing pattern that organizes tests into three clear steps for easy maintenance.
+
+WikiWikiWeb says
+
+> Arrange/Act/Assert is a pattern for arranging and formatting code in UnitTest methods.
+
+Flowchart
+
+
+
+## Programmatic Example of Arrange/Act/Assert Pattern in Java
+
+We need to write comprehensive and clear unit test suite for a class. Using the Arrange/Act/Assert pattern in Java testing ensures clarity.
+
+Let's first introduce our `Cash` class to be unit tested.
+
+```java
+public class Cash {
+
+ private int amount;
+
+ Cash(int amount) {
+ this.amount = amount;
+ }
+
+ void plus(int addend) {
+ amount += addend;
+ }
+
+ boolean minus(int subtrahend) {
+ if (amount >= subtrahend) {
+ amount -= subtrahend;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ int count() {
+ return amount;
+ }
+}
+```
+
+Then we write our unit tests according to Arrange/Act/Assert pattern. Notice the clearly separated steps for each unit test.
+
+```java
+class CashAAATest {
+
+ @Test
+ void testPlus() {
+ //Arrange
+ var cash = new Cash(3);
+ //Act
+ cash.plus(4);
+ //Assert
+ assertEquals(7, cash.count());
+ }
+
+ @Test
+ void testMinus() {
+ //Arrange
+ var cash = new Cash(8);
+ //Act
+ var result = cash.minus(5);
+ //Assert
+ assertTrue(result);
+ assertEquals(3, cash.count());
+ }
+
+ @Test
+ void testInsufficientMinus() {
+ //Arrange
+ var cash = new Cash(1);
+ //Act
+ var result = cash.minus(6);
+ //Assert
+ assertFalse(result);
+ assertEquals(1, cash.count());
+ }
+
+ @Test
+ void testUpdate() {
+ //Arrange
+ var cash = new Cash(5);
+ //Act
+ cash.plus(6);
+ var result = cash.minus(3);
+ //Assert
+ assertTrue(result);
+ assertEquals(8, cash.count());
+ }
+}
+```
+
+## When to Use the Arrange/Act/Assert Pattern in Java
+
+Use Arrange/Act/Assert pattern when
+
+* Unit testing, especially within the context of TDD and BDD
+* Anywhere clarity and structure are needed in test cases
+
+## Real-World Applications of Arrange/Act/Assert Pattern in Java
+
+* This pattern is particularly useful when practicing TDD and/or BDD methodologies in Java.
+* Utilized in various programming languages and testing frameworks, such as JUnit (Java), NUnit (.NET), and xUnit frameworks.
+
+## Benefits and Trade-offs of Arrange/Act/Assert Pattern
+
+Benefits:
+
+* Improved readability of tests by clearly separating the setup, action, and verification steps.
+* Easier maintenance and understanding of tests, as each test is structured in a predictable way.
+* Facilitates debugging by isolating test failures to specific phases within the test.
+
+Trade-offs:
+
+* May introduce redundancy in tests, as similar arrangements may be repeated across tests.
+* Some complex tests might not fit neatly into this structure, requiring additional context or setup outside these three phases.
+
+## Related Java Design Patterns
+
+* [Page Object](https://java-design-patterns.com/patterns/page-object/): A pattern for organizing UI tests that can be used in conjunction with Arrange/Act/Assert.
+
+## References and Credits
+
+* [The Art of Unit Testing: with examples in C#](https://amzn.to/49IbdwO)
+* [Test Driven Development: By Example](https://amzn.to/3wEwKbF)
+* [Unit Testing Principles, Practices, and Patterns: Effective testing styles, patterns, and reliable automation for unit testing, mocking, and integration testing with examples in C#](https://amzn.to/4ayjpiM)
+* [xUnit Test Patterns: Refactoring Test Code](https://amzn.to/4dHGDpm)
+* [Arrange, Act, Assert: What is AAA Testing?](https://blog.ncrunch.net/post/arrange-act-assert-aaa-testing.aspx)
+* [Bill Wake: 3A – Arrange, Act, Assert (NCrunch)](https://xp123.com/articles/3a-arrange-act-assert/)
+* [GivenWhenThen (Martin Fowler)](https://martinfowler.com/bliki/GivenWhenThen.html)
diff --git a/arrange-act-assert/etc/arrange-act-assert-flowchart.png b/arrange-act-assert/etc/arrange-act-assert-flowchart.png
new file mode 100644
index 000000000000..8b7615352523
Binary files /dev/null and b/arrange-act-assert/etc/arrange-act-assert-flowchart.png differ
diff --git a/arrange-act-assert/etc/arrange-act-assert.urm.puml b/arrange-act-assert/etc/arrange-act-assert.urm.puml
new file mode 100644
index 000000000000..4b22d2a4e4bb
--- /dev/null
+++ b/arrange-act-assert/etc/arrange-act-assert.urm.puml
@@ -0,0 +1,11 @@
+@startuml
+package com.iluwatar.arrangeactassert {
+ class Cash {
+ - amount : int
+ ~ Cash(amount : int)
+ ~ count() : int
+ ~ minus(subtrahend : int) : boolean
+ ~ plus(addend : int)
+ }
+}
+@enduml
\ No newline at end of file
diff --git a/arrange-act-assert/pom.xml b/arrange-act-assert/pom.xml
new file mode 100644
index 000000000000..6b8ba1b2d490
--- /dev/null
+++ b/arrange-act-assert/pom.xml
@@ -0,0 +1,43 @@
+
+
+
+
+ java-design-patterns
+ com.iluwatar
+ 1.26.0-SNAPSHOT
+
+ 4.0.0
+ arrange-act-assert
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+
diff --git a/arrange-act-assert/src/main/java/com/iluwatar/arrangeactassert/Cash.java b/arrange-act-assert/src/main/java/com/iluwatar/arrangeactassert/Cash.java
new file mode 100644
index 000000000000..0c31b1f89f44
--- /dev/null
+++ b/arrange-act-assert/src/main/java/com/iluwatar/arrangeactassert/Cash.java
@@ -0,0 +1,57 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.arrangeactassert;
+
+import lombok.AllArgsConstructor;
+
+/**
+ * Arrange/Act/Assert (AAA) is a unit test pattern. In this simple example, we have a ({@link Cash})
+ * object for plus, minus and counting amount.
+ */
+@AllArgsConstructor
+public class Cash {
+
+ private int amount;
+
+ // plus
+ void plus(int addend) {
+ amount += addend;
+ }
+
+ // minus
+ boolean minus(int subtrahend) {
+ if (amount >= subtrahend) {
+ amount -= subtrahend;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // count
+ int count() {
+ return amount;
+ }
+}
diff --git a/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java
new file mode 100644
index 000000000000..ebb261277080
--- /dev/null
+++ b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java
@@ -0,0 +1,103 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.arrangeactassert;
+
+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 org.junit.jupiter.api.Test;
+
+/**
+ * Arrange/Act/Assert (AAA) is a pattern for organizing unit tests. It is a way to structure your
+ * tests, so they're easier to read, maintain and enhance.
+ *
+ * It breaks tests down into three clear and distinct steps:
+ *
+ *
1. Arrange: Perform the setup and initialization required for the test.
+ *
+ *
2. Act: Take action(s) required for the test.
+ *
+ *
3. Assert: Verify the outcome(s) of the test.
+ *
+ *
This pattern has several significant benefits. It creates a clear separation between a test's
+ * setup, operations, and results. This structure makes the code easier to read and understand. If
+ * you place the steps in order and format your code to separate them, you can scan a test and
+ * quickly comprehend what it does.
+ *
+ *
It also enforces a certain degree of discipline when you write your tests. You have to think
+ * clearly about the three steps your test will perform. But it makes tests more natural to write at
+ * the same time since you already have an outline.
+ *
+ *
In ({@link CashAAATest}) we have four test methods. Each of them has only one reason to change
+ * and one reason to fail. In a large and complicated code base, tests that honor the single
+ * responsibility principle are much easier to troubleshoot.
+ */
+class CashAAATest {
+
+ @Test
+ void testPlus() {
+ // Arrange
+ var cash = new Cash(3);
+ // Act
+ cash.plus(4);
+ // Assert
+ assertEquals(7, cash.count());
+ }
+
+ @Test
+ void testMinus() {
+ // Arrange
+ var cash = new Cash(8);
+ // Act
+ var result = cash.minus(5);
+ // Assert
+ assertTrue(result);
+ assertEquals(3, cash.count());
+ }
+
+ @Test
+ void testInsufficientMinus() {
+ // Arrange
+ var cash = new Cash(1);
+ // Act
+ var result = cash.minus(6);
+ // Assert
+ assertFalse(result);
+ assertEquals(1, cash.count());
+ }
+
+ @Test
+ void testUpdate() {
+ // Arrange
+ var cash = new Cash(5);
+ // Act
+ cash.plus(6);
+ var result = cash.minus(3);
+ // Assert
+ assertTrue(result);
+ assertEquals(8, cash.count());
+ }
+}
diff --git a/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java
new file mode 100644
index 000000000000..5756822516b8
--- /dev/null
+++ b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java
@@ -0,0 +1,60 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.arrangeactassert;
+
+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 org.junit.jupiter.api.Test;
+
+/**
+ * ({@link CashAAATest}) is an anti-example of AAA pattern. This test is functionally correct, but
+ * with the addition of a new feature, it needs refactoring. There are an awful lot of steps in that
+ * test method, but it verifies the class' important behavior in just eleven lines. It violates the
+ * single responsibility principle. If this test method failed after a small code change, it might
+ * take some digging to discover why.
+ */
+class CashAntiAAATest {
+
+ @Test
+ void testCash() {
+ // initialize
+ var cash = new Cash(3);
+ // test plus
+ cash.plus(4);
+ assertEquals(7, cash.count());
+ // test minus
+ cash = new Cash(8);
+ assertTrue(cash.minus(5));
+ assertEquals(3, cash.count());
+ assertFalse(cash.minus(6));
+ assertEquals(3, cash.count());
+ // test update
+ cash.plus(5);
+ assertTrue(cash.minus(5));
+ assertEquals(3, cash.count());
+ }
+}
diff --git a/async-method-invocation/README.md b/async-method-invocation/README.md
new file mode 100644
index 000000000000..519152e1268c
--- /dev/null
+++ b/async-method-invocation/README.md
@@ -0,0 +1,217 @@
+---
+title: "Async Method Invocation Pattern in Java: Elevate Performance with Asynchronous Programming"
+shortTitle: Async Method Invocation
+description: "Learn about the Async Method Invocation pattern in Java for asynchronous method calls, enhancing concurrency, scalability, and responsiveness in your applications. Explore real-world examples and code implementations."
+category: Concurrency
+language: en
+tag:
+ - Asynchronous
+ - Decoupling
+ - Reactive
+ - Scalability
+ - Thread management
+---
+
+## Also known as
+
+* Asynchronous Procedure Call
+
+## Intent of Async Method Invocation Design Pattern
+
+The Async Method Invocation pattern is designed to enhance concurrency by allowing methods to be called asynchronously. This pattern helps in executing parallel tasks, reducing wait times, and improving system throughput.
+
+## Detailed Explanation of Async Method Invocation Pattern with Real-World Examples
+
+Real-world example
+
+> Asynchronous method invocation enables non-blocking operations, allowing multiple processes to run concurrently. This pattern is particularly useful in applications requiring high scalability and performance, such as web servers and microservices.
+>
+> In the context of space rockets, an analogous example of the Async Method Invocation pattern can be seen in the communication between the mission control center and the onboard systems of the rocket. When mission control sends a command to the rocket to adjust its trajectory or perform a system check, they do not wait idly for the rocket to complete the task and report back. Instead, mission control continues to monitor other aspects of the mission and manage different tasks. The rocket executes the command asynchronously and sends a status update or result back to mission control once the operation is complete. This allows mission control to efficiently manage multiple concurrent operations without being blocked by any single task, similar to how asynchronous method invocation works in software systems.
+
+In plain words
+
+> Asynchronous method invocation starts task processing and returns immediately before the task is ready. The results of the task processing are returned to the caller later.
+
+Wikipedia says
+
+> In multithreaded computer programming, asynchronous method invocation (AMI), also known as asynchronous method calls or the asynchronous pattern is a design pattern in which the call site is not blocked while waiting for the called code to finish. Instead, the calling thread is notified when the reply arrives. Polling for a reply is an undesired option.
+
+Sequence diagram
+
+
+
+## Programmatic Example of Async Method Invocation Pattern in Java
+
+Consider a scenario where multiple tasks need to be executed simultaneously. Using the Async Method Invocation pattern, you can initiate these tasks without waiting for each to complete, thus optimizing resource usage and reducing latency.
+
+In this example, we are launching space rockets and deploying lunar rovers.
+
+The application demonstrates the async method invocation pattern. The 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.
+
+```java
+public interface AsyncResult {
+ boolean isCompleted();
+
+ T getValue() throws ExecutionException;
+
+ void await() throws InterruptedException;
+}
+```
+
+```java
+public interface AsyncCallback {
+ void onComplete(T value);
+
+ void onError(Exception ex);
+}
+```
+
+```java
+public interface AsyncExecutor {
+ AsyncResult startProcess(Callable task);
+
+ AsyncResult startProcess(Callable task, AsyncCallback callback);
+
+ T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException;
+}
+```
+
+`ThreadAsyncExecutor` is an implementation of `AsyncExecutor`. Some of its key parts are highlighted next.
+
+```java
+public class ThreadAsyncExecutor implements AsyncExecutor {
+
+ @Override
+ public AsyncResult startProcess(Callable task) {
+ return startProcess(task, null);
+ }
+
+ @Override
+ public AsyncResult startProcess(Callable task, AsyncCallback callback) {
+ var result = new CompletableResult<>(callback);
+ new Thread(
+ () -> {
+ try {
+ result.setValue(task.call());
+ } catch (Exception ex) {
+ result.setException(ex);
+ }
+ },
+ "executor-" + idx.incrementAndGet())
+ .start();
+ return result;
+ }
+
+ @Override
+ public T endProcess(AsyncResult asyncResult)
+ throws ExecutionException, InterruptedException {
+ if (!asyncResult.isCompleted()) {
+ asyncResult.await();
+ }
+ return asyncResult.getValue();
+ }
+}
+```
+
+Then we are ready to launch some rockets to see how everything works together.
+
+```java
+ public static void main(String[] args) throws Exception {
+ // construct a new executor that will run async tasks
+ var executor = new ThreadAsyncExecutor();
+
+ // start few async tasks with varying processing times, two last with callback handlers
+ 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("Deploying lunar rover"));
+ final var asyncResult5 =
+ executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover"));
+
+ // emulate processing in the current thread while async tasks are running in their own threads
+ Thread.sleep(350); // Oh boy, we are working hard here
+ log("Mission command is sipping coffee");
+
+ // wait for completion of the tasks
+ final var result1 = executor.endProcess(asyncResult1);
+ final var result2 = executor.endProcess(asyncResult2);
+ final var result3 = executor.endProcess(asyncResult3);
+ asyncResult4.await();
+ asyncResult5.await();
+
+ // log the results of the tasks, callbacks log immediately when complete
+ log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result1));
+ log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result2));
+ log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result3));
+}
+```
+
+Here's the program console output.
+
+```
+21:47:08.227[executor-2]INFO com.iluwatar.async.method.invocation.App-Space rocket launched successfully
+21:47:08.269[main]INFO com.iluwatar.async.method.invocation.App-Mission command is sipping coffee
+21:47:08.318[executor-4]INFO com.iluwatar.async.method.invocation.App-Space rocket<20>launched successfully
+21:47:08.335[executor-4]INFO com.iluwatar.async.method.invocation.App-Deploying lunar rover<20>
+21:47:08.414[executor-1]INFO com.iluwatar.async.method.invocation.App-Space rocket<10>launched successfully
+21:47:08.519[executor-5]INFO com.iluwatar.async.method.invocation.App-Space rocket launched successfully
+21:47:08.519[executor-5]INFO com.iluwatar.async.method.invocation.App-Deploying lunar rover
+21:47:08.616[executor-3]INFO com.iluwatar.async.method.invocation.App-Space rocket<50>launched successfully
+21:47:08.617[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<10>launch complete
+21:47:08.617[main]INFO com.iluwatar.async.method.invocation.App-Space rocket launch complete
+21:47:08.618[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<50>launch complete
+```
+
+## When to Use the Async Method Invocation Pattern in Java
+
+This pattern is ideal for applications needing to manage multiple parallel tasks efficiently. It is commonly used in scenarios such as handling background processes, improving user interface responsiveness, and managing asynchronous data processing.
+
+Use the async method invocation pattern when
+
+* When operations do not need to complete before proceeding with the next steps in a program.
+* For tasks that are resource-intensive or time-consuming, such as IO operations, network requests, or complex computations, where making the operation synchronous would significantly impact performance or user experience.
+* In GUI applications to prevent freezing or unresponsiveness during long-running tasks.
+* In web applications for non-blocking IO operations.
+
+## Real-World Applications of Async Method Invocation Pattern in Java
+
+Many modern applications leverage the Async Method Invocation pattern, including web servers handling concurrent requests, microservices architectures, and systems requiring intensive background processing.
+
+* Web servers handling HTTP requests asynchronously to improve throughput and reduce latency.
+* Desktop and mobile applications using background threads to perform time-consuming operations without blocking the user interface.
+* Microservices architectures where services perform asynchronous communications via messaging queues or event streams.
+* [FutureTask](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/FutureTask.html)
+* [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html)
+* [ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html)
+* [Task-based Asynchronous Pattern](https://msdn.microsoft.com/en-us/library/hh873175.aspx)
+
+## Benefits and Trade-offs of Async Method Invocation Pattern
+
+While this pattern offers significant performance benefits, it also introduces complexity in error handling and resource management. Proper implementation is essential to avoid potential pitfalls such as race conditions and deadlocks.
+
+Benefits:
+
+* Improved Responsiveness: The main thread or application flow remains unblocked, improving the user experience in GUI applications and overall responsiveness.
+* Better Resource Utilization: By enabling parallel execution, system resources (like CPU and IO) are utilized more efficiently, potentially improving the application's throughput.
+* Scalability: Easier to scale applications, as tasks can be distributed across available resources more effectively.
+
+Trade-offs:
+
+* Complexity: Introducing asynchronous operations can complicate the codebase, making it more challenging to understand, debug, and maintain.
+* Resource Management: Requires careful management of threads or execution contexts, which can introduce overhead and potential resource exhaustion issues.
+* Error Handling: Asynchronous operations can make error handling more complex, as exceptions may occur in different threads or at different times.
+
+## Related Java Design Patterns
+
+The Async Method Invocation pattern often works well with other design patterns like the Command Pattern for encapsulating requests, the Observer Pattern for event handling, and the Promise Pattern for managing asynchronous results.
+
+* [Command](https://java-design-patterns.com/patterns/command/): Asynchronous method invocation can be used to implement the Command pattern, where commands are executed asynchronously.
+* [Observer](https://java-design-patterns.com/patterns/observer/): Asynchronous method invocation can be used to notify observers asynchronously when a subject's state changes.
+* [Promise](https://java-design-patterns.com/patterns/promise/): The AsyncResult interface can be considered a form of Promise, representing a value that may not be available yet.
+
+## References and Credits
+
+* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3Ti1N4f)
+* [Effective Java](https://amzn.to/4cGk2Jz)
+* [Java Concurrency in Practice](https://amzn.to/4ab97VU)
diff --git a/async-method-invocation/etc/async-method-invocation-sequence-diagram.png b/async-method-invocation/etc/async-method-invocation-sequence-diagram.png
new file mode 100644
index 000000000000..28420d783379
Binary files /dev/null and b/async-method-invocation/etc/async-method-invocation-sequence-diagram.png differ
diff --git a/async-method-invocation/etc/async-method-invocation.ucls b/async-method-invocation/etc/async-method-invocation.ucls
deleted file mode 100644
index f2e189eaeae0..000000000000
--- a/async-method-invocation/etc/async-method-invocation.ucls
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/async-method-invocation/etc/async-method-invocation.urm.png b/async-method-invocation/etc/async-method-invocation.urm.png
new file mode 100644
index 000000000000..898c1e199bda
Binary files /dev/null and b/async-method-invocation/etc/async-method-invocation.urm.png differ
diff --git a/async-method-invocation/etc/async-method-invocation.urm.puml b/async-method-invocation/etc/async-method-invocation.urm.puml
new file mode 100644
index 000000000000..d44665081856
--- /dev/null
+++ b/async-method-invocation/etc/async-method-invocation.urm.puml
@@ -0,0 +1,55 @@
+@startuml
+package com.iluwatar.async.method.invocation {
+ class App {
+ - LOGGER : Logger {static}
+ + App()
+ - callback(name : String) : AsyncCallback {static}
+ - lazyval(value : T, delayMillis : long) : Callable {static}
+ - log(msg : String) {static}
+ + main(args : String[]) {static}
+ }
+ interface AsyncCallback {
+ + onComplete(T) {abstract}
+ + onError(Exception) {abstract}
+ }
+ interface AsyncExecutor {
+ + endProcess(AsyncResult) : T {abstract}
+ + startProcess(Callable) : AsyncResult {abstract}
+ + startProcess(Callable, AsyncCallback) : AsyncResult {abstract}
+ }
+ interface AsyncResult {
+ + await() {abstract}
+ + getValue() : T {abstract}
+ + isCompleted() : boolean {abstract}
+ }
+ class ThreadAsyncExecutor {
+ - idx : AtomicInteger
+ + ThreadAsyncExecutor()
+ + endProcess(asyncResult : AsyncResult) : T
+ + startProcess(task : Callable) : AsyncResult
+ + startProcess(task : Callable, callback : AsyncCallback) : AsyncResult
+ }
+ -class CompletableResult {
+ ~ COMPLETED : int {static}
+ ~ FAILED : int {static}
+ ~ RUNNING : int {static}
+ ~ callback : AsyncCallback
+ ~ exception : Exception
+ ~ lock : Object
+ ~ state : int
+ ~ value : T
+ ~ CompletableResult(callback : AsyncCallback)
+ + await()
+ + getValue() : T
+ ~ hasCallback() : boolean
+ + isCompleted() : boolean
+ ~ setException(exception : Exception)
+ ~ setValue(value : T)
+ }
+}
+ThreadAsyncExecutor ..|> AsyncCallback
+ThreadAsyncExecutor ..|> AsyncResult
+ThreadAsyncExecutor ..|> AsyncExecutor
+CompletableResult ..+ ThreadAsyncExecutor
+CompletableResult ..|> AsyncResult
+@enduml
diff --git a/async-method-invocation/index.md b/async-method-invocation/index.md
deleted file mode 100644
index dfcee0208cce..000000000000
--- a/async-method-invocation/index.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-layout: pattern
-title: Async Method Invocation
-folder: async-method-invocation
-permalink: /patterns/async-method-invocation/
-categories: Concurrency
-tags: Java
----
-
-**Intent:** Asynchronous method invocation is pattern where the calling thread
-is not blocked while waiting results of tasks. The pattern provides parallel
-processing of multiple independent tasks and retrieving the results via
-callbacks or waiting until everything is done.
-
-
-
-**Applicability:** Use async method invocation pattern when
-
-* you have multiple independent tasks that can run in parallel
-* you need to improve the performance of a group of sequential tasks
-* you have limited amount of processing capacity or long running tasks and the
- caller should not wait the tasks to be ready
-
-**Real world examples:**
-
-* [FutureTask](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/FutureTask.html), [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) and [ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) (Java)
-* [Task-based Asynchronous Pattern](https://msdn.microsoft.com/en-us/library/hh873175.aspx) (.NET)
diff --git a/async-method-invocation/pom.xml b/async-method-invocation/pom.xml
index 87dd343acb13..d9ddd918c8e9 100644
--- a/async-method-invocation/pom.xml
+++ b/async-method-invocation/pom.xml
@@ -1,18 +1,75 @@
-
-
+
+
+
4.0.0
com.iluwatar
java-design-patterns
- 1.8.0-SNAPSHOT
+ 1.26.0-SNAPSHOT
async-method-invocation
- junit
- junit
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.mockito
+ mockito-core
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 0b8ee364952a..ec3beed3be4d 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,23 +1,50 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.async.method.invocation;
import java.util.concurrent.Callable;
+import lombok.extern.slf4j.Slf4j;
/**
- * This application demonstrates the async method invocation pattern. Key parts of the pattern are
- * AsyncResult which is an intermediate container for an asynchronously evaluated
+ * In this example, we are launching space rockets and deploying lunar rovers.
+ *
+ * The application demonstrates the async method invocation pattern. The 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
+ *
+ *
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
+ *
+ *
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,
+ *
+ *
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
@@ -26,41 +53,44 @@
* @see AsyncResult
* @see AsyncCallback
* @see AsyncExecutor
- *
* @see java.util.concurrent.FutureTask
* @see java.util.concurrent.CompletableFuture
* @see java.util.concurrent.ExecutorService
*/
+@Slf4j
public class App {
+ private static final String ROCKET_LAUNCH_LOG_PATTERN = "Space rocket <%s> launched successfully";
+
+ /** 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("Deploying lunar rover"));
+ final var asyncResult5 =
+ executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover"));
// 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");
+ Thread.sleep(350); // Oh boy, we are working hard here
+ log("Mission command is sipping coffee");
// 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();
// log the results of the tasks, callbacks log immediately when complete
- log("Result 1: " + result1);
- log("Result 2: " + result2);
- log("Result 3: " + result3);
+ log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result1));
+ log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result2));
+ log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result3));
}
/**
@@ -73,7 +103,7 @@ public static void main(String[] args) throws Exception {
private static Callable lazyval(T value, long delayMillis) {
return () -> {
Thread.sleep(delayMillis);
- log("Task completed with: " + value);
+ log(String.format(ROCKET_LAUNCH_LOG_PATTERN, value));
return value;
};
}
@@ -85,16 +115,20 @@ private static Callable lazyval(T value, long delayMillis) {
* @return new async callback
*/
private static AsyncCallback callback(String name) {
- return (value, ex) -> {
- if (ex.isPresent()) {
- log(name + " failed: " + ex.map(Exception::getMessage).orElse(""));
- } else {
- log(name + ": " + value);
+ return new AsyncCallback<>() {
+ @Override
+ public void onComplete(T value) {
+ log(name + " <" + value + ">");
+ }
+
+ @Override
+ public void onError(Exception ex) {
+ log(name + " failed: " + ex.getMessage());
}
};
}
private static void log(String msg) {
- System.out.println(String.format("[%1$-10s] - %2$s", Thread.currentThread().getName(), msg));
+ LOGGER.info(msg);
}
}
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 8e4d77443841..2dfbaf9a188a 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,21 +1,47 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.async.method.invocation;
-import java.util.Optional;
-
/**
- *
- * AsyncCallback interface
+ * AsyncCallback interface.
*
- * @param
- *
+ * @param Type of Result
*/
public interface AsyncCallback {
/**
- * Complete handler which is executed when async task is completed or fails execution.
+ * Complete handler which is executed when async task is completed.
+ *
+ * @param value the evaluated value from async task
+ */
+ void onComplete(T value);
+
+ /**
+ * Error handler which is executed when async task 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 exception which was thrown during async task execution(non-null)
*/
- void onComplete(T value, Optional ex);
+ void onError(Exception 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 bd3c98339270..3bae90830098 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,13 +1,33 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.async.method.invocation;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
-/**
- *
- * AsyncExecutor interface
- *
- */
+/** AsyncExecutor interface. */
public interface AsyncExecutor {
/**
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 6d77df8ecd56..3eebdc4e773d 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,12 +1,35 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.async.method.invocation;
import java.util.concurrent.ExecutionException;
/**
- *
- * AsyncResult interface
+ * AsyncResult interface.
*
- * @param
+ * @param parameter returned when getValue is invoked
*/
public interface AsyncResult {
diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java
index 3009345626b6..a1261f34184c 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,18 +1,37 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.async.method.invocation;
-import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
-/**
- *
- * Implementation of async executor that creates a new thread for every task.
- *
- */
+/** 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
@@ -22,26 +41,27 @@ public AsyncResult startProcess(Callable task) {
@Override
public AsyncResult startProcess(Callable task, AsyncCallback callback) {
- CompletableResult result = new CompletableResult<>(callback);
- new Thread(() -> {
- try {
- result.setValue(task.call());
- } catch (Exception ex) {
- result.setException(ex);
- }
- }, "executor-" + idx.incrementAndGet()).start();
+ var result = new CompletableResult<>(callback);
+ new Thread(
+ () -> {
+ try {
+ result.setValue(task.call());
+ } catch (Exception ex) {
+ result.setException(ex);
+ }
+ },
+ "executor-" + idx.incrementAndGet())
+ .start();
return result;
}
@Override
- public T endProcess(AsyncResult asyncResult) throws ExecutionException,
- InterruptedException {
- if (asyncResult.isCompleted()) {
- return asyncResult.getValue();
- } else {
+ public T endProcess(AsyncResult asyncResult)
+ throws ExecutionException, InterruptedException {
+ if (!asyncResult.isCompleted()) {
asyncResult.await();
- return asyncResult.getValue();
}
+ return asyncResult.getValue();
}
/**
@@ -59,7 +79,7 @@ private static class CompletableResult implements AsyncResult {
static final int COMPLETED = 3;
final Object lock;
- final Optional> callback;
+ final AsyncCallback callback;
volatile int state = RUNNING;
T value;
@@ -67,7 +87,11 @@ private static class CompletableResult implements AsyncResult {
CompletableResult(AsyncCallback callback) {
this.lock = new Object();
- this.callback = Optional.ofNullable(callback);
+ this.callback = callback;
+ }
+
+ boolean hasCallback() {
+ return callback != null;
}
/**
@@ -79,7 +103,9 @@ private static class CompletableResult implements AsyncResult {
void setValue(T value) {
this.value = value;
this.state = COMPLETED;
- this.callback.ifPresent(ac -> ac.onComplete(value, Optional.empty()));
+ if (hasCallback()) {
+ callback.onComplete(value);
+ }
synchronized (lock) {
lock.notifyAll();
}
@@ -94,7 +120,9 @@ void setValue(T value) {
void setException(Exception exception) {
this.exception = exception;
this.state = FAILED;
- this.callback.ifPresent(ac -> ac.onComplete(null, Optional.of(exception)));
+ if (hasCallback()) {
+ callback.onError(exception);
+ }
synchronized (lock) {
lock.notifyAll();
}
@@ -102,7 +130,7 @@ void setException(Exception exception) {
@Override
public boolean isCompleted() {
- return (state > RUNNING);
+ return state > RUNNING;
}
@Override
@@ -119,7 +147,7 @@ public T getValue() throws ExecutionException {
@Override
public void await() throws InterruptedException {
synchronized (lock) {
- if (!isCompleted()) {
+ while (!isCompleted()) {
lock.wait();
}
}
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 e077701c08d4..d58a3f6b5c30 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,17 +1,45 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.async.method.invocation;
-import org.junit.Test;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-/**
- *
- * Application test
- *
- */
-public class AppTest {
+import org.junit.jupiter.api.Test;
+/** Application test */
+class AppTest {
+
+ /**
+ * Issue: Add at least one assertion to this test case.
+ *
+ * Solution: Inserted assertion to check whether the execution of the main method in {@link
+ * App} throws an exception.
+ */
@Test
- public void test() throws Exception {
- String[] args = {};
- App.main(args);
+ void shouldExecuteApplicationWithoutException() {
+
+ assertDoesNotThrow(() -> 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
new file mode 100644
index 000000000000..d6540ce77309
--- /dev/null
+++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java
@@ -0,0 +1,349 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.async.method.invocation;
+
+import static java.time.Duration.ofMillis;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
+
+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.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** ThreadAsyncExecutorTest */
+class ThreadAsyncExecutorTest {
+
+ @Captor private ArgumentCaptor exceptionCaptor;
+
+ @Mock private Callable task;
+
+ @Mock private AsyncCallback callback;
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ /** Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)} */
+ @Test
+ void testSuccessfulTaskWithoutCallback() {
+ assertTimeout(
+ ofMillis(3000),
+ () -> {
+ // Instantiate a new executor and start a new 'null' task ...
+ final var executor = new ThreadAsyncExecutor();
+
+ final var result = new Object();
+ when(task.call()).thenReturn(result);
+
+ final var asyncResult = executor.startProcess(task);
+ assertNotNull(asyncResult);
+ asyncResult.await(); // Prevent timing issues, and wait until the result is available
+ assertTrue(asyncResult.isCompleted());
+
+ // Our task should only execute once ...
+ verify(task, times(1)).call();
+
+ // ... and the result should be exactly the same object
+ assertSame(result, asyncResult.getValue());
+ });
+ }
+
+ /**
+ * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable,
+ * AsyncCallback)}
+ */
+ @Test
+ void testSuccessfulTaskWithCallback() {
+ assertTimeout(
+ ofMillis(3000),
+ () -> {
+ // Instantiate a new executor and start a new 'null' task ...
+ final var executor = new ThreadAsyncExecutor();
+
+ final var result = new Object();
+ when(task.call()).thenReturn(result);
+
+ final var asyncResult = executor.startProcess(task, callback);
+ assertNotNull(asyncResult);
+ asyncResult.await(); // Prevent timing issues, and wait until the result is available
+ assertTrue(asyncResult.isCompleted());
+
+ // Our task should only execute once ...
+ verify(task, times(1)).call();
+
+ // ... same for the callback, we expect our object
+ verify(callback, times(1)).onComplete(eq(result));
+ verify(callback, times(0)).onError(exceptionCaptor.capture());
+
+ // ... and the result should be exactly the same object
+ assertSame(result, asyncResult.getValue());
+ });
+ }
+
+ /**
+ * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)} when a
+ * task takes a while to execute
+ */
+ @Test
+ void testLongRunningTaskWithoutCallback() {
+ assertTimeout(
+ ofMillis(5000),
+ () -> {
+ // Instantiate a new executor and start a new 'null' task ...
+ final var executor = new ThreadAsyncExecutor();
+
+ final var result = new Object();
+ when(task.call())
+ .thenAnswer(
+ i -> {
+ Thread.sleep(1500);
+ return result;
+ });
+
+ final var asyncResult = executor.startProcess(task);
+ assertNotNull(asyncResult);
+ assertFalse(asyncResult.isCompleted());
+
+ try {
+ asyncResult.getValue();
+ fail(
+ "Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task");
+ } catch (IllegalStateException e) {
+ assertNotNull(e.getMessage());
+ }
+
+ // Our task should only execute once, but it can take a while ...
+ verify(task, timeout(3000).times(1)).call();
+
+ // Prevent timing issues, and wait until the result is available
+ asyncResult.await();
+ assertTrue(asyncResult.isCompleted());
+ verifyNoMoreInteractions(task);
+
+ // ... and the result should be exactly the same object
+ assertSame(result, asyncResult.getValue());
+ });
+ }
+
+ /**
+ * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable,
+ * AsyncCallback)} when a task takes a while to execute
+ */
+ @Test
+ void testLongRunningTaskWithCallback() {
+ assertTimeout(
+ ofMillis(5000),
+ () -> {
+ // Instantiate a new executor and start a new 'null' task ...
+ final var executor = new ThreadAsyncExecutor();
+
+ final var result = new Object();
+ when(task.call())
+ .thenAnswer(
+ i -> {
+ Thread.sleep(1500);
+ return result;
+ });
+
+ final var asyncResult = executor.startProcess(task, callback);
+ assertNotNull(asyncResult);
+ assertFalse(asyncResult.isCompleted());
+
+ verifyNoMoreInteractions(callback);
+
+ try {
+ asyncResult.getValue();
+ fail(
+ "Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task");
+ } catch (IllegalStateException e) {
+ assertNotNull(e.getMessage());
+ }
+
+ // Our task should only execute once, but it can take a while ...
+ verify(task, timeout(3000).times(1)).call();
+ verify(callback, timeout(3000).times(1)).onComplete(eq(result));
+ verify(callback, times(0)).onError(isA(Exception.class));
+
+ // Prevent timing issues, and wait until the result is available
+ asyncResult.await();
+ assertTrue(asyncResult.isCompleted());
+ verifyNoMoreInteractions(task, callback);
+
+ // ... and the result should be exactly the same object
+ assertSame(result, asyncResult.getValue());
+ });
+ }
+
+ /**
+ * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)} when a
+ * task takes a while to execute, while waiting on the result using {@link
+ * ThreadAsyncExecutor#endProcess(AsyncResult)}
+ */
+ @Test
+ void testEndProcess() {
+ assertTimeout(
+ ofMillis(5000),
+ () -> {
+ // Instantiate a new executor and start a new 'null' task ...
+ final var executor = new ThreadAsyncExecutor();
+
+ final var result = new Object();
+ when(task.call())
+ .thenAnswer(
+ i -> {
+ Thread.sleep(1500);
+ return result;
+ });
+
+ final var asyncResult = executor.startProcess(task);
+ assertNotNull(asyncResult);
+ assertFalse(asyncResult.isCompleted());
+
+ try {
+ asyncResult.getValue();
+ fail(
+ "Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task");
+ } catch (IllegalStateException e) {
+ assertNotNull(e.getMessage());
+ }
+
+ assertSame(result, executor.endProcess(asyncResult));
+ verify(task, times(1)).call();
+ assertTrue(asyncResult.isCompleted());
+
+ // Calling end process a second time while already finished should give the same result
+ assertSame(result, executor.endProcess(asyncResult));
+ verifyNoMoreInteractions(task);
+ });
+ }
+
+ /**
+ * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable)} when
+ * the callable is 'null'
+ */
+ @Test
+ void testNullTask() {
+ assertTimeout(
+ ofMillis(3000),
+ () -> {
+ // Instantiate a new executor and start a new 'null' task ...
+ 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
+ assertTrue(asyncResult.isCompleted());
+
+ try {
+ asyncResult.getValue();
+ fail("Expected ExecutionException with NPE as cause");
+ } catch (final ExecutionException e) {
+ assertNotNull(e.getMessage());
+ assertNotNull(e.getCause());
+ assertEquals(NullPointerException.class, e.getCause().getClass());
+ }
+ });
+ }
+
+ /**
+ * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable,
+ * AsyncCallback)} when the callable is 'null', but the asynchronous callback is provided
+ */
+ @Test
+ void testNullTaskWithCallback() {
+ assertTimeout(
+ ofMillis(3000),
+ () -> {
+ // Instantiate a new executor and start a new 'null' task ...
+ 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());
+ verify(callback, times(0)).onComplete(any());
+ verify(callback, times(1)).onError(exceptionCaptor.capture());
+
+ final var exception = exceptionCaptor.getValue();
+ assertNotNull(exception);
+
+ assertEquals(NullPointerException.class, exception.getClass());
+
+ try {
+ asyncResult.getValue();
+ fail("Expected ExecutionException with NPE as cause");
+ } catch (final ExecutionException e) {
+ assertNotNull(e.getMessage());
+ assertNotNull(e.getCause());
+ assertEquals(NullPointerException.class, e.getCause().getClass());
+ }
+ });
+ }
+
+ /**
+ * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable,
+ * AsyncCallback)} when both the callable and the asynchronous callback are 'null'
+ */
+ @Test
+ void testNullTaskWithNullCallback() {
+ assertTimeout(
+ ofMillis(3000),
+ () -> {
+ // Instantiate a new executor and start a new 'null' task ...
+ final var executor = new ThreadAsyncExecutor();
+ final var asyncResult = executor.startProcess(null, null);
+
+ assertNotNull(
+ asyncResult,
+ "The AsyncResult should not be 'null', even though the task and callback were 'null'.");
+ asyncResult.await(); // Prevent timing issues, and wait until the result is available
+ assertTrue(asyncResult.isCompleted());
+
+ try {
+ asyncResult.getValue();
+ fail("Expected ExecutionException with NPE as cause");
+ } catch (final ExecutionException e) {
+ assertNotNull(e.getMessage());
+ assertNotNull(e.getCause());
+ assertEquals(NullPointerException.class, e.getCause().getClass());
+ }
+ });
+ }
+}
diff --git a/backpressure/README.md b/backpressure/README.md
new file mode 100644
index 000000000000..d0734ad1f934
--- /dev/null
+++ b/backpressure/README.md
@@ -0,0 +1,156 @@
+---
+title: "Backpressure Pattern in Java: Gracefully regulate producer-to-consumer data flow to prevent overload."
+shortTitle: Backpressure
+description: "Dive into the Backpressure design pattern in Java through practical examples, discovering how it prevents overload while ensuring stability and peak performance by aligning data flow with consumer capacity."
+category: Concurrency
+language: en
+tag:
+ - Asynchronous
+ - Event-driven
+ - Reactive
+ - Resilience
+---
+
+## Also known as
+
+* Flow Control
+* Rate Limiting Mechanism
+
+## Intent of the Backpressure Design Pattern
+
+Control the rate of data production so downstream consumers are not overwhelmed by excessive load.
+
+## Detailed Explanation of Backpressure Pattern with Real-World Examples
+
+Real-world example
+
+> Imagine a busy coffee shop where multiple baristas brew drinks (producers), and a single barista is responsible for carefully crafting latte art (consumer). If drinks are brewed faster than the latte-art barista can decorate them, they pile up, risking quality issues or discarded drinks. By introducing a pacing system—only sending new cups once the latte-art barista is ready—everyone stays synchronized, ensuring minimal waste and a consistently enjoyable customer experience.
+
+In plain words
+
+> The Backpressure design pattern is a flow control mechanism that prevents overwhelming a system by regulating data production based on the consumer’s processing capacity.
+
+Wikipedia says
+
+> Back pressure (or backpressure) is the term for a resistance to the desired flow of fluid through pipes. Obstructions or tight bends create backpressure via friction loss and pressure drop. In distributed systems in particular event-driven architecture, back pressure is a technique to regulate flow of data, ensuring that components do not become overwhelmed.
+
+Sequence diagram
+
+
+
+## Programmatic Example of Backpressure Pattern in Java
+
+This example demonstrates how backpressure can be implemented using Project Reactor. We begin by creating a simple publisher that emits a stream of integers, introducing a small delay to mimic a slower production rate:
+
+```java
+public class Publisher {
+ public static Flux publish(int start, int count, int delay) {
+ return Flux.range(start, count).delayElements(Duration.ofMillis(delay)).log();
+ }
+}
+```
+
+Next, we define a custom subscriber by extending Reactor’s BaseSubscriber. It simulates slow processing by sleeping for 500ms per item. Initially, the subscriber requests ten items; for every five items processed, it requests five more:
+
+```java
+public class Subscriber extends BaseSubscriber {
+
+ private static final Logger logger = LoggerFactory.getLogger(Subscriber.class);
+
+ @Override
+ protected void hookOnSubscribe(@NonNull Subscription subscription) {
+ logger.info("subscribe()");
+ request(10); //request 10 items initially
+ }
+
+ @Override
+ protected void hookOnNext(@NonNull Integer value) {
+ processItem();
+ logger.info("process({})", value);
+ if (value % 5 == 0) {
+ // request for the next 5 items after processing first 5
+ request(5);
+ }
+ }
+
+ @Override
+ protected void hookOnComplete() {
+ //completed processing.
+ }
+
+ private void processItem() {
+ try {
+ Thread.sleep(500); // simulate slow processing
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+}
+```
+
+Finally, in the `main` method, we publish a range of integers and subscribe using the custom subscriber. A short sleep in the main thread allows the emission, backpressure requests, and processing to be fully observed:
+
+```java
+public static void main(String[] args) throws InterruptedException {
+ Subscriber sub = new Subscriber();
+ Publisher.publish(1, 8, 200).subscribe(sub);
+ Thread.sleep(5000); //wait for execution
+
+}
+```
+
+Below is an example of the program’s output. It shows the subscriber’s log entries, including when it requests more data and when each integer is processed:
+
+```
+23:09:55.746 [main] DEBUG reactor.util.Loggers -- Using Slf4j logging framework
+23:09:55.762 [main] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onSubscribe(FluxConcatMapNoPrefetch.FluxConcatMapNoPrefetchSubscriber)
+23:09:55.762 [main] INFO com.iluwatar.backpressure.Subscriber -- subscribe()
+23:09:55.763 [main] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- request(10)
+23:09:55.969 [parallel-1] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(1)
+23:09:56.475 [parallel-1] INFO com.iluwatar.backpressure.Subscriber -- process(1)
+23:09:56.680 [parallel-2] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(2)
+23:09:57.185 [parallel-2] INFO com.iluwatar.backpressure.Subscriber -- process(2)
+23:09:57.389 [parallel-3] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(3)
+23:09:57.894 [parallel-3] INFO com.iluwatar.backpressure.Subscriber -- process(3)
+23:09:58.099 [parallel-4] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(4)
+23:09:58.599 [parallel-4] INFO com.iluwatar.backpressure.Subscriber -- process(4)
+23:09:58.805 [parallel-5] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(5)
+23:09:59.311 [parallel-5] INFO com.iluwatar.backpressure.Subscriber -- process(5)
+23:09:59.311 [parallel-5] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- request(5)
+23:09:59.516 [parallel-6] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(6)
+23:10:00.018 [parallel-6] INFO com.iluwatar.backpressure.Subscriber -- process(6)
+23:10:00.223 [parallel-7] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(7)
+23:10:00.729 [parallel-7] INFO com.iluwatar.backpressure.Subscriber -- process(7)
+23:10:00.930 [parallel-8] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(8)
+23:10:01.436 [parallel-8] INFO com.iluwatar.backpressure.Subscriber -- process(8)
+23:10:01.437 [parallel-8] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onComplete()
+```
+
+## When to Use the Backpressure Pattern In Java
+
+* Use in Java systems where data is produced at high velocity and consumers risk overload.
+* Applicable in reactive or event-driven architectures to maintain stability under varying load conditions.
+
+## Benefits and Trade-offs of Backpressure Pattern
+
+Benefits:
+
+* Protects consumers from saturation and resource exhaustion.
+
+Trade-offs:
+
+* Introduces possible delays if production must slow down to match consumer capacity.
+* Requires careful orchestration in complex systems with multiple concurrent data sources.
+
+## Related Java Design Patterns
+
+* [Observer Pattern](https://java-design-patterns.com/patterns/observer/): Both patterns involve a producer notifying consumers. Observer is synchronous and tightly coupled (observers know the subject).
+* [Publish-Subscribe Pattern](https://java-design-patterns.com/patterns/publish-subscribe/): Both patterns deal with asynchronous data flow and can work together to manage message distribution and consumption effectively.
+
+## References and Credits
+
+* [Backpressure Explained (RedHat Developers Blog)](https://developers.redhat.com/articles/backpressure-explained)
+* [Hands-On Reactive Programming in Spring 5](https://amzn.to/3YuYfyO)
+* [Reactive Programming with RxJava: Creating Asynchronous, Event-Based Applications](https://amzn.to/42negbf)
+* [Reactive Streams in Java](https://amzn.to/3RJjUzA)
+* [Reactive Streams Specification](https://www.reactive-streams.org/)
diff --git a/backpressure/etc/backpressure-sequence-diagram.png b/backpressure/etc/backpressure-sequence-diagram.png
new file mode 100644
index 000000000000..b74a5d2a5362
Binary files /dev/null and b/backpressure/etc/backpressure-sequence-diagram.png differ
diff --git a/backpressure/etc/backpressure.png b/backpressure/etc/backpressure.png
new file mode 100644
index 000000000000..0de9ff54b673
Binary files /dev/null and b/backpressure/etc/backpressure.png differ
diff --git a/backpressure/pom.xml b/backpressure/pom.xml
new file mode 100644
index 000000000000..fcc15892fb8a
--- /dev/null
+++ b/backpressure/pom.xml
@@ -0,0 +1,81 @@
+
+
+
+ 4.0.0
+
+ com.iluwatar
+ java-design-patterns
+ 1.26.0-SNAPSHOT
+
+
+ backpressure
+
+
+
+ io.projectreactor
+ reactor-core
+ 3.8.0-M1
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ io.projectreactor
+ reactor-test
+ 3.8.0-M1
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+
+
+ com.iluwatar.backpressure.App
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backpressure/src/main/java/com/iluwatar/backpressure/App.java b/backpressure/src/main/java/com/iluwatar/backpressure/App.java
new file mode 100644
index 000000000000..4632c42a467f
--- /dev/null
+++ b/backpressure/src/main/java/com/iluwatar/backpressure/App.java
@@ -0,0 +1,76 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.backpressure;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * The Backpressure pattern is a flow control mechanism. It allows a consumer to signal to a
+ * producer to slow down or stop sending data when it's overwhelmed.
+ * Prevents memory overflow, CPU thrashing, and resource exhaustion.
+ * Ensures fair usage of resources in distributed systems.
+ * Avoids buffer bloat and latency spikes. Key concepts of this design paradigm involves
+ * Publisher/Producer: Generates data.
+ * Subscriber/Consumer: Receives and processes data.
+ *
+ * In this example we will create a {@link Publisher} and a {@link Subscriber}. Publisher
+ * will emit a stream of integer values with a predefined delay. Subscriber takes 500 ms to
+ * process one integer. Since the subscriber can't process the items fast enough we apply
+ * backpressure to the publisher so that it will request 10 items first, process 5 items and
+ * request for the next 5 again. After processing 5 items subscriber will keep requesting for
+ * another 5 until the stream ends.
+ */
+public class App {
+
+ protected static CountDownLatch latch;
+
+ /**
+ * Program entry point.
+ *
+ * @param args command line args
+ */
+ public static void main(String[] args) throws InterruptedException {
+
+ /*
+ * This custom subscriber applies backpressure:
+ * - Has a processing delay of 0.5 milliseconds
+ * - Requests 10 items initially
+ * - Process 5 items and request for the next 5 items
+ */
+ Subscriber sub = new Subscriber();
+ // slow publisher emit 15 numbers with a delay of 200 milliseconds
+ Publisher.publish(1, 17, 200).subscribe(sub);
+
+ latch = new CountDownLatch(1);
+ latch.await();
+
+ sub = new Subscriber();
+ // fast publisher emit 15 numbers with a delay of 1 millisecond
+ Publisher.publish(1, 17, 1).subscribe(sub);
+
+ latch = new CountDownLatch(1);
+ latch.await();
+ }
+}
diff --git a/backpressure/src/main/java/com/iluwatar/backpressure/Publisher.java b/backpressure/src/main/java/com/iluwatar/backpressure/Publisher.java
new file mode 100644
index 000000000000..1d39a070ae57
--- /dev/null
+++ b/backpressure/src/main/java/com/iluwatar/backpressure/Publisher.java
@@ -0,0 +1,46 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.backpressure;
+
+import java.time.Duration;
+import reactor.core.publisher.Flux;
+
+/** This class is the publisher that generates the data stream. */
+public class Publisher {
+
+ private Publisher() {}
+
+ /**
+ * On message method will trigger when the subscribed event is published.
+ *
+ * @param start starting integer
+ * @param count how many integers to emit
+ * @param delay delay between each item in milliseconds
+ * @return a flux stream of integers
+ */
+ public static Flux publish(int start, int count, int delay) {
+ return Flux.range(start, count).delayElements(Duration.ofMillis(delay)).log();
+ }
+}
diff --git a/backpressure/src/main/java/com/iluwatar/backpressure/Subscriber.java b/backpressure/src/main/java/com/iluwatar/backpressure/Subscriber.java
new file mode 100644
index 000000000000..40ff5aebc814
--- /dev/null
+++ b/backpressure/src/main/java/com/iluwatar/backpressure/Subscriber.java
@@ -0,0 +1,64 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.backpressure;
+
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.reactivestreams.Subscription;
+import reactor.core.publisher.BaseSubscriber;
+
+/** This class is the custom subscriber that subscribes to the data stream. */
+@Slf4j
+public class Subscriber extends BaseSubscriber {
+
+ @Override
+ protected void hookOnSubscribe(@NonNull Subscription subscription) {
+ request(10); // request 10 items initially
+ }
+
+ @Override
+ protected void hookOnNext(@NonNull Integer value) {
+ processItem();
+ LOGGER.info("process({})", value);
+ if (value % 5 == 0) {
+ // request for the next 5 items after processing first 5
+ request(5);
+ }
+ }
+
+ @Override
+ protected void hookOnComplete() {
+ App.latch.countDown();
+ }
+
+ private void processItem() {
+ try {
+ Thread.sleep(500); // simulate slow processing
+ } catch (InterruptedException e) {
+ LOGGER.error(e.getMessage(), e);
+ Thread.currentThread().interrupt();
+ }
+ }
+}
diff --git a/backpressure/src/test/java/com/iluwatar/backpressure/AppTest.java b/backpressure/src/test/java/com/iluwatar/backpressure/AppTest.java
new file mode 100644
index 000000000000..8fe2245a97b7
--- /dev/null
+++ b/backpressure/src/test/java/com/iluwatar/backpressure/AppTest.java
@@ -0,0 +1,37 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.backpressure;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import org.junit.jupiter.api.Test;
+
+public class AppTest {
+
+ @Test
+ void shouldExecuteApplicationWithoutException() {
+ assertDoesNotThrow(() -> App.main(new String[] {}));
+ }
+}
diff --git a/backpressure/src/test/java/com/iluwatar/backpressure/LoggerExtension.java b/backpressure/src/test/java/com/iluwatar/backpressure/LoggerExtension.java
new file mode 100644
index 000000000000..e99926e00a1a
--- /dev/null
+++ b/backpressure/src/test/java/com/iluwatar/backpressure/LoggerExtension.java
@@ -0,0 +1,60 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.backpressure;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.slf4j.LoggerFactory;
+
+public class LoggerExtension implements BeforeEachCallback, AfterEachCallback {
+
+ private final ListAppender listAppender = new ListAppender<>();
+ private final Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
+
+ @Override
+ public void afterEach(ExtensionContext extensionContext) {
+ listAppender.stop();
+ listAppender.list.clear();
+ logger.detachAppender(listAppender);
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext extensionContext) {
+ logger.addAppender(listAppender);
+ listAppender.start();
+ }
+
+ public List getFormattedMessages() {
+ return listAppender.list.stream()
+ .map(ILoggingEvent::getFormattedMessage)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/backpressure/src/test/java/com/iluwatar/backpressure/PublisherTest.java b/backpressure/src/test/java/com/iluwatar/backpressure/PublisherTest.java
new file mode 100644
index 000000000000..010a80e83959
--- /dev/null
+++ b/backpressure/src/test/java/com/iluwatar/backpressure/PublisherTest.java
@@ -0,0 +1,51 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.backpressure;
+
+import static com.iluwatar.backpressure.Publisher.publish;
+
+import java.time.Duration;
+import org.junit.jupiter.api.Test;
+import reactor.core.publisher.Flux;
+import reactor.test.StepVerifier;
+
+public class PublisherTest {
+
+ @Test
+ public void testPublish() {
+
+ Flux flux = publish(1, 3, 200);
+
+ StepVerifier.withVirtualTime(() -> flux)
+ .expectSubscription()
+ .expectNoEvent(Duration.ofMillis(200))
+ .expectNext(1)
+ .expectNoEvent(Duration.ofSeconds(200))
+ .expectNext(2)
+ .expectNoEvent(Duration.ofSeconds(200))
+ .expectNext(3)
+ .verifyComplete();
+ }
+}
diff --git a/backpressure/src/test/java/com/iluwatar/backpressure/SubscriberTest.java b/backpressure/src/test/java/com/iluwatar/backpressure/SubscriberTest.java
new file mode 100644
index 000000000000..7f7676612d02
--- /dev/null
+++ b/backpressure/src/test/java/com/iluwatar/backpressure/SubscriberTest.java
@@ -0,0 +1,54 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.backpressure;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.concurrent.CountDownLatch;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class SubscriberTest {
+
+ @RegisterExtension public LoggerExtension loggerExtension = new LoggerExtension();
+
+ @Test
+ public void testSubscribe() throws InterruptedException {
+
+ Subscriber sub = new Subscriber();
+ Publisher.publish(1, 8, 100).subscribe(sub);
+
+ App.latch = new CountDownLatch(1);
+ App.latch.await();
+
+ String result = String.join(",", loggerExtension.getFormattedMessages());
+ assertTrue(
+ result.endsWith(
+ "onSubscribe(FluxConcatMapNoPrefetch."
+ + "FluxConcatMapNoPrefetchSubscriber),request(10),onNext(1),process(1),onNext(2),"
+ + "process(2),onNext(3),process(3),onNext(4),process(4),onNext(5),process(5),request(5),"
+ + "onNext(6),process(6),onNext(7),process(7),onNext(8),process(8),onComplete()"));
+ }
+}
diff --git a/balking/README.md b/balking/README.md
new file mode 100644
index 000000000000..cdd09cf5b7d1
--- /dev/null
+++ b/balking/README.md
@@ -0,0 +1,161 @@
+---
+title: "Balking Pattern in Java: Smart Control Over Java Execution"
+shortTitle: Balking
+description: "Learn the Balking design pattern in Java, a concurrency pattern that prevents code execution in inappropriate states. Discover examples, use cases, and benefits."
+category: Concurrency
+language: en
+tag:
+ - Concurrency
+ - Decoupling
+ - Fault tolerance
+ - Synchronization
+---
+
+## Intent of Balking Design Pattern
+
+The Balking Pattern in Java is a concurrency design pattern that prevents an object from executing certain code if it is in an incomplete or inappropriate state. This pattern is crucial for managing state and concurrency in multithreaded Java applications.
+
+## Detailed Explanation of Balking Pattern with Real-World Examples
+
+Real-world example
+
+> A real-world analogy of the Balking design pattern can be seen in a laundry service. Imagine a washing machine at a laundromat that only starts washing clothes if the door is properly closed and locked. If a user tries to start the machine while the door is open, the machine balks and does nothing. This ensures that the washing process only begins when it is safe to do so, preventing water spillage and potential damage to the machine. Similarly, the Balking pattern in software design ensures that operations are only executed when the object is in an appropriate state, preventing erroneous actions and maintaining system stability.
+
+In plain words
+
+> Using the balking pattern, a certain code executes only if the object is in particular state.
+
+Wikipedia says
+
+> The balking pattern is a software design pattern that only executes an action on an object when the object is in a particular state. For example, if an object reads ZIP files and a calling method invokes a get method on the object when the ZIP file is not open, the object would "balk" at the request.
+
+Flowchart
+
+
+
+## Programmatic Example of Balking Pattern in Java
+
+This example demonstrates the Balking Pattern in a multithreaded Java application, highlighting state management and concurrency control. The Balking Pattern is exemplified by a washing machine's start button that initiates washing only if the machine is idle. This ensures state management and prevents concurrent issues.
+
+There's a start-button in a washing machine to initiate the laundry washing. When the washing machine is inactive the button works as expected, but if it's already washing the button does 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 changes to WASHING using a thread-safe method. On the other hand, if it already has been washing and any other thread executes `wash`it won't do that and returns without doing anything.
+
+Here are the relevant parts of the `WashingMachine` class.
+
+```java
+
+@Slf4j
+public class WashingMachine {
+
+ private final DelayProvider delayProvider;
+ private WashingMachineState washingMachineState;
+
+ public WashingMachine(DelayProvider delayProvider) {
+ this.delayProvider = delayProvider;
+ this.washingMachineState = WashingMachineState.ENABLED;
+ }
+
+ public WashingMachineState getWashingMachineState() {
+ return washingMachineState;
+ }
+
+ public void wash() {
+ synchronized (this) {
+ var machineState = getWashingMachineState();
+ LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState);
+ if (this.washingMachineState == WashingMachineState.WASHING) {
+ LOGGER.error("Cannot wash if the machine has been already washing!");
+ return;
+ }
+ this.washingMachineState = WashingMachineState.WASHING;
+ }
+ LOGGER.info("{}: Doing the washing", Thread.currentThread().getName());
+ this.delayProvider.executeAfterDelay(50, TimeUnit.MILLISECONDS, this::endOfWashing);
+ }
+
+ public synchronized void endOfWashing() {
+ washingMachineState = WashingMachineState.ENABLED;
+ LOGGER.info("{}: Washing completed.", Thread.currentThread().getId());
+ }
+}
+```
+
+Here's the simple `DelayProvider` interface used by the `WashingMachine`.
+
+```java
+public interface DelayProvider {
+ void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task);
+}
+```
+
+Now, we introduce the application using the `WashingMachine`.
+
+```java
+public static void main(String... args) {
+ final var washingMachine = new WashingMachine();
+ var executorService = Executors.newFixedThreadPool(3);
+ for (int i = 0; i < 3; i++) {
+ executorService.execute(washingMachine::wash);
+ }
+ executorService.shutdown();
+ try {
+ if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
+ executorService.shutdownNow();
+ }
+ } catch (InterruptedException ie) {
+ LOGGER.error("ERROR: Waiting on executor service shutdown!");
+ Thread.currentThread().interrupt();
+ }
+}
+```
+
+Here is the console output of the program.
+
+```
+14:02:52.268 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Actual machine state: ENABLED
+14:02:52.272 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Doing the washing
+14:02:52.272 [pool-1-thread-3] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-3: Actual machine state: WASHING
+14:02:52.273 [pool-1-thread-3] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing!
+14:02:52.273 [pool-1-thread-1] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-1: Actual machine state: WASHING
+14:02:52.273 [pool-1-thread-1] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing!
+14:02:52.324 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - 14: Washing completed.
+```
+
+## When to Use the Balking Pattern in Java
+
+Use the Balking pattern when
+
+* You want to invoke an action on an object only when it is in a particular state
+* Objects are generally only in a state that is prone to balking temporarily but for an unknown amount of time
+* In multithreaded applications where certain actions should only proceed when specific conditions are met, and those conditions are expected to change over time due to external factors or concurrent operations.
+
+## Real-World Applications of Balking Pattern in Java
+
+* Resource pooling, where resources are only allocated if they are in a valid state for allocation.
+* Thread management, where threads only proceed with tasks if certain conditions (like task availability or resource locks) are met.
+
+## Benefits and Trade-offs of Balking Pattern
+
+Benefits:
+
+* Reduces unnecessary lock acquisitions in situations where actions cannot proceed, enhancing performance in concurrent applications.
+* Encourages clear separation of state management and behavior, leading to cleaner code.
+* Simplifies the handling of operations that should only be performed under certain conditions without cluttering the caller code with state checks.
+
+Trade-offs:
+
+* Can introduce complexity by obscuring the conditions under which actions are taken or ignored, potentially making the system harder to debug and understand.
+* May lead to missed opportunities or actions if the state changes are not properly monitored or if the balking condition is too restrictive.
+
+## Related Java Design Patterns
+
+* [Double-Checked Locking](https://java-design-patterns.com/patterns/double-checked-locking/): Ensures that initialization occurs only when necessary and avoids unnecessary locking, which is related to Balking in terms of conditionally executing logic based on the object's state.
+* [Guarded Suspension](https://java-design-patterns.com/patterns/guarded-suspension/): Similar in ensuring actions are only performed when an object is in a certain state, but typically involves waiting until the state is valid.
+* [State](https://java-design-patterns.com/patterns/state/): The State pattern can be used in conjunction with Balking to manage the states and transitions of the object.
+
+## References and Credits
+
+* [Concurrent Programming in Java : Design Principles and Patterns](https://amzn.to/4dIBqxL)
+* [Java Concurrency in Practice](https://amzn.to/4aRMruW)
+* [Patterns in Java: A Catalog of Reusable Design Patterns Illustrated with UML](https://amzn.to/4bOtzwF)
diff --git a/balking/etc/balking-flowchart.png b/balking/etc/balking-flowchart.png
new file mode 100644
index 000000000000..400a1ce8d3e3
Binary files /dev/null and b/balking/etc/balking-flowchart.png differ
diff --git a/balking/etc/balking.png b/balking/etc/balking.png
new file mode 100644
index 000000000000..f409eaacbb95
Binary files /dev/null and b/balking/etc/balking.png differ
diff --git a/balking/etc/balking.ucls b/balking/etc/balking.ucls
new file mode 100644
index 000000000000..e750f888a9ef
--- /dev/null
+++ b/balking/etc/balking.ucls
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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
new file mode 100644
index 000000000000..818f7549c330
--- /dev/null
+++ b/balking/pom.xml
@@ -0,0 +1,70 @@
+
+
+
+
+ java-design-patterns
+ com.iluwatar
+ 1.26.0-SNAPSHOT
+
+ 4.0.0
+ balking
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ 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
new file mode 100644
index 000000000000..c7651ec54147
--- /dev/null
+++ b/balking/src/main/java/com/iluwatar/balking/App.java
@@ -0,0 +1,66 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.balking;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 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
+ *
+ * In this example implementation, {@link WashingMachine} is an object that has two states in
+ * which it can be: ENABLED and WASHING. If the machine is ENABLED, the state changes to WASHING
+ * using a thread-safe method. On the other hand, if it already has been washing and any other
+ * thread executes {@link WashingMachine#wash()} it won't do that and returns without doing
+ * anything.
+ */
+@Slf4j
+public class App {
+
+ /**
+ * Entry Point.
+ *
+ * @param args the command line arguments - not used
+ */
+ public static void main(String... args) {
+ final var washingMachine = new WashingMachine();
+ var executorService = Executors.newFixedThreadPool(3);
+ for (int i = 0; i < 3; i++) {
+ executorService.execute(washingMachine::wash);
+ }
+ executorService.shutdown();
+ try {
+ if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
+ executorService.shutdownNow();
+ }
+ } catch (InterruptedException ie) {
+ LOGGER.error("ERROR: Waiting on executor service shutdown!");
+ Thread.currentThread().interrupt();
+ }
+ }
+}
diff --git a/balking/src/main/java/com/iluwatar/balking/DelayProvider.java b/balking/src/main/java/com/iluwatar/balking/DelayProvider.java
new file mode 100644
index 000000000000..f27922219ea6
--- /dev/null
+++ b/balking/src/main/java/com/iluwatar/balking/DelayProvider.java
@@ -0,0 +1,32 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.balking;
+
+import java.util.concurrent.TimeUnit;
+
+/** An interface to simulate delay while executing some work. */
+public interface DelayProvider {
+ void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task);
+}
diff --git a/balking/src/main/java/com/iluwatar/balking/WashingMachine.java b/balking/src/main/java/com/iluwatar/balking/WashingMachine.java
new file mode 100644
index 000000000000..52ce7c593b6b
--- /dev/null
+++ b/balking/src/main/java/com/iluwatar/balking/WashingMachine.java
@@ -0,0 +1,83 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.balking;
+
+import java.util.concurrent.TimeUnit;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+/** Washing machine class. */
+@Slf4j
+public class WashingMachine {
+
+ private final DelayProvider delayProvider;
+
+ @Getter private WashingMachineState washingMachineState;
+
+ /** Creates a new instance of WashingMachine. */
+ public WashingMachine() {
+ this(
+ (interval, timeUnit, task) -> {
+ try {
+ Thread.sleep(timeUnit.toMillis(interval));
+ } catch (InterruptedException ie) {
+ LOGGER.error("", ie);
+ Thread.currentThread().interrupt();
+ }
+ task.run();
+ });
+ }
+
+ /**
+ * 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;
+ this.washingMachineState = WashingMachineState.ENABLED;
+ }
+
+ /** Method responsible for washing if the object is in appropriate state. */
+ public void wash() {
+ synchronized (this) {
+ var machineState = getWashingMachineState();
+ LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState);
+ if (this.washingMachineState == WashingMachineState.WASHING) {
+ LOGGER.error("Cannot wash if the machine has been already washing!");
+ return;
+ }
+ this.washingMachineState = WashingMachineState.WASHING;
+ }
+ LOGGER.info("{}: Doing the washing", Thread.currentThread().getName());
+
+ this.delayProvider.executeAfterDelay(50, TimeUnit.MILLISECONDS, this::endOfWashing);
+ }
+
+ /** Method is responsible for ending the washing by changing machine state. */
+ public synchronized void endOfWashing() {
+ washingMachineState = WashingMachineState.ENABLED;
+ LOGGER.info("{}: Washing completed.", Thread.currentThread().getId());
+ }
+}
diff --git a/balking/src/main/java/com/iluwatar/balking/WashingMachineState.java b/balking/src/main/java/com/iluwatar/balking/WashingMachineState.java
new file mode 100644
index 000000000000..e0b2ba7ac8f1
--- /dev/null
+++ b/balking/src/main/java/com/iluwatar/balking/WashingMachineState.java
@@ -0,0 +1,34 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.balking;
+
+/**
+ * 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
new file mode 100644
index 000000000000..40beabf553d0
--- /dev/null
+++ b/balking/src/test/java/com/iluwatar/balking/AppTest.java
@@ -0,0 +1,45 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.balking;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+
+/** Application test */
+class AppTest {
+
+ /**
+ * Issue: Add at least one assertion to this test case.
+ *
+ *
Solution: Inserted assertion to check whether the execution of the main method in {@link
+ * App} throws an exception.
+ */
+ @Test
+ void shouldExecuteApplicationWithoutException() {
+ assertDoesNotThrow((Executable) App::main);
+ }
+}
diff --git a/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java b/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java
new file mode 100644
index 000000000000..9bf7ac2548a0
--- /dev/null
+++ b/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java
@@ -0,0 +1,70 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.balking;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.concurrent.TimeUnit;
+import org.junit.jupiter.api.Test;
+
+/** Tests for {@link WashingMachine} */
+class WashingMachineTest {
+
+ private final FakeDelayProvider fakeDelayProvider = new FakeDelayProvider();
+
+ @Test
+ void wash() {
+ var washingMachine = new WashingMachine(fakeDelayProvider);
+
+ washingMachine.wash();
+ washingMachine.wash();
+
+ var machineStateGlobal = washingMachine.getWashingMachineState();
+
+ fakeDelayProvider.task.run();
+
+ // washing machine remains in washing state
+ assertEquals(WashingMachineState.WASHING, machineStateGlobal);
+
+ // washing machine goes back to enabled state
+ assertEquals(WashingMachineState.ENABLED, washingMachine.getWashingMachineState());
+ }
+
+ @Test
+ void endOfWashing() {
+ var washingMachine = new WashingMachine();
+ washingMachine.wash();
+ assertEquals(WashingMachineState.ENABLED, washingMachine.getWashingMachineState());
+ }
+
+ private static class FakeDelayProvider implements DelayProvider {
+ private Runnable task;
+
+ @Override
+ public void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task) {
+ this.task = task;
+ }
+ }
+}
diff --git a/bloc/README.md b/bloc/README.md
new file mode 100644
index 000000000000..76a4a0917485
--- /dev/null
+++ b/bloc/README.md
@@ -0,0 +1,230 @@
+---
+title: "Bloc Pattern in Java: State Management Simplified"
+shortTitle: Bloc
+description: "Learn how the Bloc pattern helps manage state changes in Java applications. This guide covers dynamic listener management, real-world examples, and clean code practices for state management."
+category: Architectural
+language: en
+tag:
+ - Abstraction
+ - Data binding
+ - Decoupling
+ - Event-driven
+ - Presentation
+ - Reactive
+ - Reusability
+ - State tracking
+---
+
+## Also known as
+
+* Business Logic Component
+* Business Logic Controller
+
+## Intent of the Bloc Pattern
+
+The Bloc pattern manages the state of an object and allows for dynamically notifying interested listeners about state changes. It separates state management logic from the rest of the application, improving code organization and flexibility.
+
+## Detailed explanation of the Bloc pattern with real-World examples
+
+Real-world example
+
+> Consider a digital counter application where multiple parts of the UI need to be updated whenever the counter changes. For example, a label displaying the counter value and an activity log showing changes. Instead of directly modifying these UI components, the Bloc pattern manages the counter state and notifies all registered listeners about the state change. Listeners can dynamically subscribe or unsubscribe from receiving updates.
+
+In plain words
+
+> The Bloc pattern manages a single state object and dynamically notifies registered listeners whenever the state changes.
+
+Wikipedia says
+
+> While not a formalized "Gang of Four" design pattern, Bloc is widely used in state-driven applications. It centralizes state management and propagates state changes to registered observers, following principles of separation of concerns.
+
+Sequence diagram
+
+
+
+## Programmatic Example of the Bloc Pattern in Java
+
+This example demonstrates how to implement the Bloc pattern using Java and Swing. The pattern separates the state of the application from UI components, and provides a reactive, event-driven approach to managing updates.
+
+Core components of the Bloc Pattern include a `State` object, a `Bloc` class responsible for managing and updating that state, and interfaces (`StateListener` and `ListenerManager`) for subscribing to changes.
+
+The `State` class represents the application's data at any given time.
+
+```java
+public record State(int value) {}
+```
+
+The `ListenerManager` interface declares methods to add and remove listeners, as well as retrieve them.
+
+```java
+public interface ListenerManager {
+ void addListener(StateListener listener);
+ void removeListener(StateListener listener);
+ List> getListeners();
+}
+```
+
+The `StateListener` interface defines how a listener reacts to state changes.
+
+```java
+public interface StateListener {
+ void onStateChange(T state);
+}
+```
+
+The `Bloc` class maintains the current state and notifies listeners of updates. It provides `increment` and `decrement` methods to update state and automatically notify registered listeners.
+
+```java
+public class Bloc implements ListenerManager {
+
+ private State currentState;
+ private final List> listeners = new ArrayList<>();
+
+ public Bloc() {
+ this.currentState = new State(0);
+ }
+
+ @Override
+ public void addListener(StateListener listener) {
+ listeners.add(listener);
+ listener.onStateChange(currentState);
+ }
+
+ @Override
+ public void removeListener(StateListener listener) {
+ listeners.remove(listener);
+ }
+
+ @Override
+ public List> getListeners() {
+ return Collections.unmodifiableList(listeners);
+ }
+
+ private void emitState(State newState) {
+ currentState = newState;
+ for (StateListener listener : listeners) {
+ listener.onStateChange(currentState);
+ }
+ }
+
+ public void increment() {
+ emitState(new State(currentState.value() + 1));
+ }
+
+ public void decrement() {
+ emitState(new State(currentState.value() - 1));
+ }
+}
+```
+
+This class demonstrates how to integrate the Bloc pattern with a simple Swing GUI. It sets up a counter, buttons to change the state, and a toggle to enable or disable the listener dynamically.
+
+```java
+public class Main {
+ public static void main(String[] args) {
+ BlocUi blocUi = new BlocUi();
+ blocUi.createAndShowUi();
+ }
+}
+
+public class BlocUi {
+
+ public void createAndShowUi() {
+ final Bloc bloc = new Bloc();
+
+ JFrame frame = new JFrame("BloC example");
+ frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+ frame.setSize(400, 300);
+
+ JLabel counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER);
+ counterLabel.setFont(new Font("Arial", Font.BOLD, 20));
+
+ JButton decrementButton = new JButton("Decrement");
+ JButton toggleListenerButton = new JButton("Disable Listener");
+ JButton incrementButton = new JButton("Increment");
+
+ frame.setLayout(new BorderLayout());
+ frame.add(counterLabel, BorderLayout.CENTER);
+ frame.add(incrementButton, BorderLayout.NORTH);
+ frame.add(decrementButton, BorderLayout.SOUTH);
+ frame.add(toggleListenerButton, BorderLayout.EAST);
+
+ StateListener stateListener = state -> counterLabel.setText("Counter: " + state.value());
+
+ bloc.addListener(stateListener);
+
+ toggleListenerButton.addActionListener(
+ e -> {
+ if (bloc.getListeners().contains(stateListener)) {
+ bloc.removeListener(stateListener);
+ toggleListenerButton.setText("Enable Listener");
+ } else {
+ bloc.addListener(stateListener);
+ toggleListenerButton.setText("Disable Listener");
+ }
+ });
+
+ incrementButton.addActionListener(e -> bloc.increment());
+ decrementButton.addActionListener(e -> bloc.decrement());
+
+ frame.setVisible(true);
+ }
+}
+```
+
+### Program Output
+
+- **On Increment**
+ `Counter: 1`
+
+- **On Decrement**
+ `Counter: 0`
+
+- **Dynamic Listener Toggle**
+ - Listener disabled: Counter stops updating.
+ - Listener enabled: Counter updates again.
+
+## When to Use the Bloc Pattern
+
+Use the Bloc pattern when:
+
+* When you want a clean separation of business logic and UI in Java applications
+* When you need a reactive approach to updating UI based on state changes
+* When you want to avoid coupling controllers or presenters directly to data manipulation
+* When multiple UI elements need access to the same business logic
+
+## Real-World Applications of Bloc Pattern
+
+* Java-based desktop applications that require real-time UI updates
+* Backend-driven Java frameworks that separate service layers from presentation
+* Cross-platform applications where the logic must remain consistent regardless of the UI technology
+
+## Benefits and Trade-offs of Bloc Pattern
+
+Benefits:
+
+* Simplifies UI components by removing direct business logic
+* Improves testability by isolating state and behavior
+* Encourages code reuse by centralizing data flows
+* Enhances maintainability through clear separation of concerns
+
+Trade-offs:
+
+* May introduce additional boilerplate code for managing streams or observers
+* Requires careful design to avoid a monolithic “god” component
+* Demands consistent reactive programming practices to be effective
+
+## Related Patterns
+
+- [Observer](https://java-design-patterns.com/patterns/observer/): Bloc is a specialized implementation of the Observer pattern.
+- [Mediator](https://java-design-patterns.com/patterns/mediator/): Orchestrates interactions among multiple objects through a central component
+- [MVC](https://java-design-patterns.com/patterns/model-view-controller/): Shares the idea of separating concerns between layers
+
+## References and Credits
+
+* [Bloc architecture(bloclibrary.dev)](https://bloclibrary.dev/architecture/)
+* [Clean Architecture: A Craftsman's Guide to Software Structure and Design](https://amzn.to/3UoKkaR)
+* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
+* [Effective Java](https://amzn.to/4cGk2Jz)
+* [Event-Driven Programming in Java (Oracle)](https://www.oracle.com/java/)
+* [Java Swing Documentation (Oracle)](https://docs.oracle.com/javase/tutorial/uiswing/)
diff --git a/bloc/etc/bloc-sequence-diagram.png b/bloc/etc/bloc-sequence-diagram.png
new file mode 100644
index 000000000000..588863985019
Binary files /dev/null and b/bloc/etc/bloc-sequence-diagram.png differ
diff --git a/bloc/etc/bloc.png b/bloc/etc/bloc.png
new file mode 100644
index 000000000000..60d6eb77c8fc
Binary files /dev/null and b/bloc/etc/bloc.png differ
diff --git a/bloc/etc/bloc.puml b/bloc/etc/bloc.puml
new file mode 100644
index 000000000000..5991f533ae70
--- /dev/null
+++ b/bloc/etc/bloc.puml
@@ -0,0 +1,41 @@
+@startuml
+package com.iluwatar.bloc {
+
+ class State {
+ - value : int
+ + State(value : int)
+ + getValue() : int
+ }
+
+ interface StateListener {
+ + onStateChange(state : T)
+ }
+
+ interface ListenerManager {
+ + addListener(listener : StateListener)
+ + removeListener(listener : StateListener)
+ + getListeners() : List>
+ }
+
+ class BloC {
+ - currentState : State
+ - listeners : List>
+ + BloC()
+ + addListener(listener : StateListener)
+ + removeListener(listener : StateListener)
+ + getListeners() : List>
+ - emitState(newState : State)
+ + increment()
+ + decrement()
+ }
+
+ class Main {
+ + main(args : String[])
+ }
+
+ ListenerManager <|.. BloC
+ StateListener <|.. BloC
+ BloC o-- State
+ BloC *-- StateListener
+}
+@enduml
diff --git a/bloc/etc/bloc.urm.puml b/bloc/etc/bloc.urm.puml
new file mode 100644
index 000000000000..6408aa76e6a4
--- /dev/null
+++ b/bloc/etc/bloc.urm.puml
@@ -0,0 +1,32 @@
+@startuml
+package com.iluwatar.bloc {
+ class Bloc {
+ - currentState : State
+ - listeners : List>
+ + Bloc()
+ + addListener(listener : StateListener)
+ + decrement()
+ - emitState(newState : State)
+ + getListeners() : List>
+ + increment()
+ + removeListener(listener : StateListener)
+ }
+ class BlocUi {
+ + BlocUi()
+ + createAndShowUi()
+ }
+ interface ListenerManager {
+ + addListener(StateListener) {abstract}
+ + getListeners() : List> {abstract}
+ + removeListener(StateListener) {abstract}
+ }
+ class Main {
+ + Main()
+ + main(args : String[]) {static}
+ }
+ interface StateListener {
+ + onStateChange(T) {abstract}
+ }
+}
+Bloc ..|> ListenerManager
+@enduml
\ No newline at end of file
diff --git a/bloc/pom.xml b/bloc/pom.xml
new file mode 100644
index 000000000000..cc52a3b99dc2
--- /dev/null
+++ b/bloc/pom.xml
@@ -0,0 +1,76 @@
+
+
+
+ 4.0.0
+
+ com.iluwatar
+ java-design-patterns
+ 1.26.0-SNAPSHOT
+
+ bloc
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.testng
+ testng
+ 7.11.0
+ test
+
+
+ org.assertj
+ assertj-core
+ 3.27.3
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+
+
+ com.iluwatar.bloc.Main
+
+
+
+
+
+
+
+
+
diff --git a/bloc/src/main/java/com/iluwatar/bloc/Bloc.java b/bloc/src/main/java/com/iluwatar/bloc/Bloc.java
new file mode 100644
index 000000000000..f6ab0a61cdbf
--- /dev/null
+++ b/bloc/src/main/java/com/iluwatar/bloc/Bloc.java
@@ -0,0 +1,98 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bloc;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The Bloc class is responsible for managing the current state and notifying registered listeners
+ * whenever the state changes. It implements the ListenerManager interface, allowing listeners to be
+ * added, removed, and notified of state changes.
+ */
+public class Bloc implements ListenerManager {
+
+ private State currentState;
+ private final List> listeners = new ArrayList<>();
+
+ /** Constructs a new Bloc instance with an initial state of value 0. */
+ public Bloc() {
+ this.currentState = new State(0);
+ }
+
+ /**
+ * Adds a listener to receive state change notifications.
+ *
+ * @param listener the listener to add
+ */
+ @Override
+ public void addListener(StateListener listener) {
+ listeners.add(listener);
+ listener.onStateChange(currentState);
+ }
+
+ /**
+ * Removes a listener from receiving state change notifications.
+ *
+ * @param listener the listener to remove
+ */
+ @Override
+ public void removeListener(StateListener listener) {
+ listeners.remove(listener);
+ }
+
+ /**
+ * Returns an unmodifiable list of all registered listeners.
+ *
+ * @return an unmodifiable list of listeners
+ */
+ @Override
+ public List> getListeners() {
+ return Collections.unmodifiableList(listeners);
+ }
+
+ /**
+ * Emits a new state and notifies all registered listeners of the change.
+ *
+ * @param newState the new state to emit
+ */
+ private void emitState(State newState) {
+ currentState = newState;
+ for (StateListener listener : listeners) {
+ listener.onStateChange(currentState);
+ }
+ }
+
+ /** Increments the current state value by 1 and notifies listeners of the change. */
+ public void increment() {
+ emitState(new State(currentState.value() + 1));
+ }
+
+ /** Decrements the current state value by 1 and notifies listeners of the change. */
+ public void decrement() {
+ emitState(new State(currentState.value() - 1));
+ }
+}
diff --git a/bloc/src/main/java/com/iluwatar/bloc/BlocUi.java b/bloc/src/main/java/com/iluwatar/bloc/BlocUi.java
new file mode 100644
index 000000000000..500d455d82e1
--- /dev/null
+++ b/bloc/src/main/java/com/iluwatar/bloc/BlocUi.java
@@ -0,0 +1,85 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bloc;
+
+import java.awt.BorderLayout;
+import java.awt.Font;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.SwingConstants;
+import javax.swing.WindowConstants;
+
+/** The BlocUI class handles the creation and management of the UI components. */
+public class BlocUi {
+
+ /** Creates and shows the UI. */
+ public void createAndShowUi() {
+ // Create a Bloc instance to manage the state
+ final Bloc bloc = new Bloc();
+
+ // setting up a frame window with a title
+ JFrame frame = new JFrame("BloC example");
+ frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+ frame.setSize(400, 300);
+
+ // label to display the counter value
+ JLabel counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER);
+ counterLabel.setFont(new Font("Arial", Font.BOLD, 20));
+
+ // buttons for increment, decrement, and toggling listener
+ JButton decrementButton = new JButton("Decrement");
+ JButton toggleListenerButton = new JButton("Disable Listener");
+ JButton incrementButton = new JButton("Increment");
+
+ frame.setLayout(new BorderLayout());
+ frame.add(counterLabel, BorderLayout.CENTER);
+ frame.add(incrementButton, BorderLayout.NORTH);
+ frame.add(decrementButton, BorderLayout.SOUTH);
+ frame.add(toggleListenerButton, BorderLayout.EAST);
+
+ // making a state listener to update the counter label when the state changes
+ StateListener stateListener = state -> counterLabel.setText("Counter: " + state.value());
+
+ // adding the listener to the Bloc instance
+ bloc.addListener(stateListener);
+
+ toggleListenerButton.addActionListener(
+ e -> {
+ if (bloc.getListeners().contains(stateListener)) {
+ bloc.removeListener(stateListener);
+ toggleListenerButton.setText("Enable Listener");
+ } else {
+ bloc.addListener(stateListener);
+ toggleListenerButton.setText("Disable Listener");
+ }
+ });
+
+ incrementButton.addActionListener(e -> bloc.increment());
+ decrementButton.addActionListener(e -> bloc.decrement());
+
+ frame.setVisible(true);
+ }
+}
diff --git a/bloc/src/main/java/com/iluwatar/bloc/ListenerManager.java b/bloc/src/main/java/com/iluwatar/bloc/ListenerManager.java
new file mode 100644
index 000000000000..cd55b0fb320d
--- /dev/null
+++ b/bloc/src/main/java/com/iluwatar/bloc/ListenerManager.java
@@ -0,0 +1,56 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bloc;
+
+import java.util.List;
+
+/**
+ * Interface for managing listeners for state changes.
+ *
+ * @param The type of state to be handled by the listeners.
+ */
+public interface ListenerManager {
+
+ /**
+ * Adds a listener that will be notified of state changes.
+ *
+ * @param listener the listener to be added
+ */
+ void addListener(StateListener listener);
+
+ /**
+ * Removes a listener so that it no longer receives state change notifications.
+ *
+ * @param listener the listener to be removed
+ */
+ void removeListener(StateListener listener);
+
+ /**
+ * Returns a list of all listeners currently registered for state changes.
+ *
+ * @return a list of registered listeners
+ */
+ List> getListeners();
+}
diff --git a/bloc/src/main/java/com/iluwatar/bloc/Main.java b/bloc/src/main/java/com/iluwatar/bloc/Main.java
new file mode 100644
index 000000000000..b7a929bcf2bd
--- /dev/null
+++ b/bloc/src/main/java/com/iluwatar/bloc/Main.java
@@ -0,0 +1,51 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bloc;
+
+/**
+ * The BLoC (Business Logic Component) pattern is a software design pattern primarily used in
+ * Flutter applications. It facilitates the separation of business logic from UI code, making the
+ * application more modular, testable, and scalable. The BLoC pattern uses streams to manage the
+ * flow of data and state changes, allowing widgets to react to new states as they arrive. In the
+ * BLoC pattern, the application is divided into three key components: - Input streams: Represent
+ * user interactions or external events fed into the BLoC. - Business logic: Processes the input and
+ * determines the resulting state or actions. - Output streams: Emit the updated state for the UI to
+ * consume. The BLoC pattern is especially useful in reactive programming scenarios and aligns well
+ * with the declarative nature of Flutter. By using this pattern, developers can ensure a clear
+ * separation of concerns, enhance reusability, and maintain consistent state management throughout
+ * the application.
+ */
+public class Main {
+
+ /**
+ * The entry point of the application. Initializes the GUI.
+ *
+ * @param args command-line arguments (not used in this example)
+ */
+ public static void main(String[] args) {
+ BlocUi blocUi = new BlocUi();
+ blocUi.createAndShowUi();
+ }
+}
diff --git a/bloc/src/main/java/com/iluwatar/bloc/State.java b/bloc/src/main/java/com/iluwatar/bloc/State.java
new file mode 100644
index 000000000000..430747548cd3
--- /dev/null
+++ b/bloc/src/main/java/com/iluwatar/bloc/State.java
@@ -0,0 +1,31 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bloc;
+
+/**
+ * The {@code State} class represents a state with an integer value. This class encapsulates the
+ * value and provides methods to retrieve it.
+ */
+public record State(int value) {}
diff --git a/bloc/src/main/java/com/iluwatar/bloc/StateListener.java b/bloc/src/main/java/com/iluwatar/bloc/StateListener.java
new file mode 100644
index 000000000000..77aac172e4e3
--- /dev/null
+++ b/bloc/src/main/java/com/iluwatar/bloc/StateListener.java
@@ -0,0 +1,42 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bloc;
+
+/**
+ * The {@code StateListener} interface defines the contract for listening to state changes.
+ * Implementations of this interface should handle state changes and define actions to take when the
+ * state changes.
+ *
+ * @param the type of state that this listener will handle
+ */
+public interface StateListener {
+
+ /**
+ * This method is called when the state has changed.
+ *
+ * @param state the updated state
+ */
+ void onStateChange(T state);
+}
diff --git a/bloc/src/test/java/com/iluwatar/bloc/BlocTest.java b/bloc/src/test/java/com/iluwatar/bloc/BlocTest.java
new file mode 100644
index 000000000000..98e34b8d4a22
--- /dev/null
+++ b/bloc/src/test/java/com/iluwatar/bloc/BlocTest.java
@@ -0,0 +1,85 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bloc;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class BlocTest {
+ private Bloc bloc;
+ private AtomicInteger stateValue;
+
+ @BeforeEach
+ void setUp() {
+ bloc = new Bloc();
+ stateValue = new AtomicInteger(0);
+ }
+
+ @Test
+ void initialState() {
+ assertTrue(bloc.getListeners().isEmpty(), "No listeners should be present initially.");
+ }
+
+ @Test
+ void IncrementUpdateState() {
+ bloc.addListener(state -> stateValue.set(state.value()));
+ bloc.increment();
+ assertEquals(1, stateValue.get(), "State should increment to 1");
+ }
+
+ @Test
+ void DecrementUpdateState() {
+ bloc.addListener(state -> stateValue.set(state.value()));
+ bloc.decrement();
+ assertEquals(-1, stateValue.get(), "State should decrement to -1");
+ }
+
+ @Test
+ void addingListener() {
+ bloc.addListener(state -> {});
+ assertEquals(1, bloc.getListeners().size(), "Listener count should be 1.");
+ }
+
+ @Test
+ void removingListener() {
+ StateListener listener = state -> {};
+ bloc.addListener(listener);
+ bloc.removeListener(listener);
+ assertTrue(bloc.getListeners().isEmpty(), "Listener count should be 0 after removal.");
+ }
+
+ @Test
+ void multipleListeners() {
+ AtomicInteger secondValue = new AtomicInteger();
+ bloc.addListener(state -> stateValue.set(state.value()));
+ bloc.addListener(state -> secondValue.set(state.value()));
+ bloc.increment();
+ assertEquals(1, stateValue.get(), "First listener should receive state 1.");
+ assertEquals(1, secondValue.get(), "Second listener should receive state 1.");
+ }
+}
diff --git a/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java b/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java
new file mode 100644
index 000000000000..f1fc73947896
--- /dev/null
+++ b/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java
@@ -0,0 +1,121 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bloc;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.awt.*;
+import javax.swing.*;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class BlocUiTest {
+
+ private JFrame frame;
+ private JLabel counterLabel;
+ private JButton incrementButton;
+ private JButton decrementButton;
+ private JButton toggleListenerButton;
+ private Bloc bloc;
+ private StateListener stateListener;
+
+ @BeforeEach
+ void setUp() {
+ bloc = new Bloc(); // Re-initialize the Bloc for each test
+
+ frame = new JFrame("BloC example");
+ frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ frame.setSize(400, 300);
+
+ counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER);
+ counterLabel.setFont(new Font("Arial", Font.BOLD, 20));
+
+ incrementButton = new JButton("Increment");
+ decrementButton = new JButton("Decrement");
+ toggleListenerButton = new JButton("Disable Listener");
+
+ frame.setLayout(new BorderLayout());
+ frame.add(counterLabel, BorderLayout.CENTER);
+ frame.add(incrementButton, BorderLayout.NORTH);
+ frame.add(decrementButton, BorderLayout.SOUTH);
+ frame.add(toggleListenerButton, BorderLayout.EAST);
+
+ stateListener = state -> counterLabel.setText("Counter: " + state.value());
+ bloc.addListener(stateListener);
+
+ incrementButton.addActionListener(e -> bloc.increment());
+ decrementButton.addActionListener(e -> bloc.decrement());
+ toggleListenerButton.addActionListener(
+ e -> {
+ if (bloc.getListeners().contains(stateListener)) {
+ bloc.removeListener(stateListener);
+ toggleListenerButton.setText("Enable Listener");
+ } else {
+ bloc.addListener(stateListener);
+ toggleListenerButton.setText("Disable Listener");
+ }
+ });
+
+ frame.setVisible(true);
+ }
+
+ @AfterEach
+ void tearDown() {
+ frame.dispose();
+ bloc = new Bloc(); // Reset Bloc state after each test to avoid state carryover
+ }
+
+ @Test
+ void testIncrementButton() {
+ simulateButtonClick(incrementButton);
+ assertEquals("Counter: 1", counterLabel.getText());
+ }
+
+ @Test
+ void testDecrementButton() {
+ simulateButtonClick(decrementButton);
+ assertEquals("Counter: -1", counterLabel.getText());
+ }
+
+ @Test
+ void testToggleListenerButton() {
+ // Disable listener
+ simulateButtonClick(toggleListenerButton);
+ simulateButtonClick(incrementButton);
+ assertEquals("Counter: 0", counterLabel.getText()); // Listener is disabled
+
+ // Enable listener
+ simulateButtonClick(toggleListenerButton);
+ simulateButtonClick(incrementButton);
+ assertEquals("Counter: 2", counterLabel.getText()); // Listener is re-enabled
+ }
+
+ private void simulateButtonClick(JButton button) {
+ for (var listener : button.getActionListeners()) {
+ listener.actionPerformed(null);
+ }
+ }
+}
diff --git a/bridge/README.md b/bridge/README.md
new file mode 100644
index 000000000000..26567b292c81
--- /dev/null
+++ b/bridge/README.md
@@ -0,0 +1,256 @@
+---
+title: "Bridge Pattern in Java: Decouple Abstraction from Implementation"
+shortTitle: Bridge
+description: "Learn about the Bridge design pattern in Java. Decouple abstraction from implementation to enhance flexibility and extensibility. Explore real-world examples, class diagrams, and use cases."
+category: Structural
+language: en
+tag:
+ - Abstraction
+ - Decoupling
+ - Extensibility
+ - Gang of Four
+ - Object composition
+---
+
+## Also known as
+
+* Handle/Body
+
+## Intent of Bridge Design Pattern
+
+The Bridge design pattern is a structural pattern in Java that decouples an abstraction from its implementation, allowing both to vary independently. This pattern is essential for developing flexible and extensible software systems.
+
+## Detailed Explanation of Bridge Pattern with Real-World Examples
+
+Real-world example
+
+> In Java, the Bridge pattern is commonly used in GUI frameworks, database drivers, and device drivers. For instance, a universal remote control (abstraction) can operate various TV brands (implementations) through a consistent interface.
+>
+> Imagine a universal remote control (abstraction) that can operate different brands and types of televisions (implementations). The remote control provides a consistent interface for operations like turning on/off, changing channels, and adjusting the volume. Each television brand or type has its own specific implementation of these operations. By using the Bridge pattern, the remote control interface is decoupled from the television implementations, allowing the remote control to work with any television regardless of its brand or internal workings. This separation allows new television models to be added without changing the remote control's code, and different remote controls can be developed to work with the same set of televisions.
+
+In Plain Words
+
+> Bridge pattern is about preferring composition to inheritance. Implementation details are pushed from a hierarchy to another object with a separate hierarchy.
+
+Wikipedia says
+
+> The bridge pattern is a design pattern used in software engineering that is meant to "decouple an abstraction from its implementation so that the two can vary independently"
+
+Sequence diagram
+
+
+
+## Programmatic Example of Bridge Pattern in Java
+
+Imagine you have a weapon that can have various enchantments, and you need to combine different weapons with different enchantments. How would you handle this? Would you create multiple copies of each weapon, each with a different enchantment, or would you create separate enchantments and apply them to the weapon as needed? The Bridge pattern enables you to do the latter.
+
+Here we have the `Weapon` hierarchy:
+
+```java
+public interface Weapon {
+ void wield();
+
+ void swing();
+
+ void unwield();
+
+ Enchantment getEnchantment();
+}
+
+public class Sword implements Weapon {
+
+ private final Enchantment enchantment;
+
+ public Sword(Enchantment enchantment) {
+ this.enchantment = enchantment;
+ }
+
+ @Override
+ public void wield() {
+ LOGGER.info("The sword is wielded.");
+ enchantment.onActivate();
+ }
+
+ @Override
+ public void swing() {
+ LOGGER.info("The sword is swung.");
+ enchantment.apply();
+ }
+
+ @Override
+ public void unwield() {
+ LOGGER.info("The sword is unwielded.");
+ enchantment.onDeactivate();
+ }
+
+ @Override
+ public Enchantment getEnchantment() {
+ return enchantment;
+ }
+}
+
+public class Hammer implements Weapon {
+
+ private final Enchantment enchantment;
+
+ public Hammer(Enchantment enchantment) {
+ this.enchantment = enchantment;
+ }
+
+ @Override
+ public void wield() {
+ LOGGER.info("The hammer is wielded.");
+ enchantment.onActivate();
+ }
+
+ @Override
+ public void swing() {
+ LOGGER.info("The hammer is swung.");
+ enchantment.apply();
+ }
+
+ @Override
+ public void unwield() {
+ LOGGER.info("The hammer is unwielded.");
+ enchantment.onDeactivate();
+ }
+
+ @Override
+ public Enchantment getEnchantment() {
+ return enchantment;
+ }
+}
+```
+
+Here's the separate `Enchantment` hierarchy:
+
+```java
+public interface Enchantment {
+ void onActivate();
+
+ void apply();
+
+ void onDeactivate();
+}
+
+public class FlyingEnchantment implements Enchantment {
+
+ @Override
+ public void onActivate() {
+ LOGGER.info("The item begins to glow faintly.");
+ }
+
+ @Override
+ public void apply() {
+ LOGGER.info("The item flies and strikes the enemies finally returning to owner's hand.");
+ }
+
+ @Override
+ public void onDeactivate() {
+ LOGGER.info("The item's glow fades.");
+ }
+}
+
+public class SoulEatingEnchantment implements Enchantment {
+
+ @Override
+ public void onActivate() {
+ LOGGER.info("The item spreads bloodlust.");
+ }
+
+ @Override
+ public void apply() {
+ LOGGER.info("The item eats the soul of enemies.");
+ }
+
+ @Override
+ public void onDeactivate() {
+ LOGGER.info("Bloodlust slowly disappears.");
+ }
+}
+```
+
+Here are both hierarchies in action:
+
+```java
+public static void main(String[] args) {
+ LOGGER.info("The knight receives an enchanted sword.");
+ var enchantedSword = new Sword(new SoulEatingEnchantment());
+ enchantedSword.wield();
+ enchantedSword.swing();
+ enchantedSword.unwield();
+
+ LOGGER.info("The valkyrie receives an enchanted hammer.");
+ var hammer = new Hammer(new FlyingEnchantment());
+ hammer.wield();
+ hammer.swing();
+ hammer.unwield();
+}
+```
+
+Here's the console output.
+
+```
+The knight receives an enchanted sword.
+The sword is wielded.
+The item spreads bloodlust.
+The sword is swung.
+The item eats the soul of enemies.
+The sword is unwielded.
+Bloodlust slowly disappears.
+The valkyrie receives an enchanted hammer.
+The hammer is wielded.
+The item begins to glow faintly.
+The hammer is swung.
+The item flies and strikes the enemies finally returning to owner's hand.
+The hammer is unwielded.
+The item's glow fades.
+```
+
+## When to Use the Bridge Pattern in Java
+
+Consider using the Bridge pattern when:
+
+* You need to avoid a permanent binding between an abstraction and its implementation, such as when the implementation must be chosen or switched at runtime.
+* Both the abstractions and their implementations should be extendable via subclassing, allowing independent extension of each component.
+* Changes to the implementation of an abstraction should not affect clients, meaning their code should not require recompilation.
+* You encounter a large number of classes in your hierarchy, indicating the need to split an object into two parts, a concept referred to as "nested generalizations" by Rumbaugh.
+* You want to share an implementation among multiple objects, potentially using reference counting, while keeping this detail hidden from the client, as exemplified by Coplien's String class, where multiple objects can share the same string representation.
+
+## Bridge Pattern Java Tutorials
+
+* [Bridge Pattern Tutorial (DigitalOcean)](https://www.digitalocean.com/community/tutorials/bridge-design-pattern-java)
+
+## Real-World Applications of Bridge Pattern in Java
+
+* GUI Frameworks where the abstraction is the window, and the implementation could be the underlying OS windowing system.
+* Database Drivers where the abstraction is a generic database interface, and the implementations are database-specific drivers.
+* Device Drivers where the abstraction is the device-independent code, and the implementation is the device-dependent code.
+
+## Benefits and Trade-offs of Bridge Pattern
+
+Benefits:
+
+* Decoupling Interface and Implementation: The Bridge pattern enhances modularity by separating the interface (the high-level operations) from the implementation (the low-level operations).
+* Improved Extensibility: You can extend the abstraction and implementation hierarchies independently.
+* Hiding Implementation Details: Clients only see the abstraction's interface, not its implementation.
+
+Trade-offs:
+
+* Increased Complexity: The pattern can complicate the system architecture and code, especially for clients unfamiliar with the pattern.
+* Runtime Overhead: The extra layer of abstraction can introduce a performance penalty, although it is often negligible in practice.
+
+## Related Java Design Patterns
+
+* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): The Abstract Factory pattern can be used along with the Bridge pattern to create platforms that are independent of the concrete classes used to create their objects.
+* [Adapter](https://java-design-patterns.com/patterns/adapter/): The Adapter pattern is used to provide a different interface to an object, while the Bridge pattern is used to separate an object's interface from its implementation.
+* [Composite](https://java-design-patterns.com/patterns/composite/): The Bridge pattern is often used with the Composite pattern to model the implementation details of a component.
+* [Strategy](https://java-design-patterns.com/patterns/strategy/): The Strategy pattern is like the Bridge pattern, but with a different intent. Both patterns are based on composition: Strategy uses composition to change the behavior of a class, while Bridge uses composition to separate an abstraction from its implementation.
+
+## References and Credits
+
+* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
+* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq)
+* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525)
+* [Pattern-Oriented Software Architecture Volume 1: A System of Patterns](https://amzn.to/3TEnhtl)
+* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR)
diff --git a/bridge/etc/bridge-sequence-diagram.png b/bridge/etc/bridge-sequence-diagram.png
new file mode 100644
index 000000000000..b797827628bc
Binary files /dev/null and b/bridge/etc/bridge-sequence-diagram.png differ
diff --git a/bridge/etc/bridge.png b/bridge/etc/bridge.png
deleted file mode 100644
index 00d3e611c1f7..000000000000
Binary files a/bridge/etc/bridge.png and /dev/null differ
diff --git a/bridge/etc/bridge.ucls b/bridge/etc/bridge.ucls
deleted file mode 100644
index 2eda6e1d1f5e..000000000000
--- a/bridge/etc/bridge.ucls
+++ /dev/null
@@ -1,157 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
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/index.md b/bridge/index.md
deleted file mode 100644
index 4a1d0bcbb3f0..000000000000
--- a/bridge/index.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-layout: pattern
-title: Bridge
-folder: bridge
-permalink: /patterns/bridge/
-categories: Structural
-tags:
- - Java
- - Gang Of Four
----
-
-**Also known as:** Handle/Body
-
-**Intent:** Decouple an abstraction from its implementation so that the two can
-vary independently.
-
-
-
-
-**Applicability:** Use the Bridge pattern when
-
-* you want to avoid a permanent binding between an abstraction and its implementation. This might be the case, for example, when the implementation must be selected or switched at run-time.
-* both the abstractions and their implementations should be extensible by subclassing. In this case, the Bridge pattern lets you combine the different abstractions and implementations and extend them independently
-* changes in the implementation of an abstraction should have no impact on clients; that is, their code should not have to be recompiled.
-* you have a proliferation of classes. Such a class hierarchy indicates the need for splitting an object into two parts. Rumbaugh uses the term "nested generalizations" to refer to such class hierarchies
-* you want to share an implementation among multiple objects (perhaps using reference counting), and this fact should be hidden from the client. A simple example is Coplien's String class, in which multiple objects can share the same string representation.
-
-**Credits**
-
-* [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)
diff --git a/bridge/pom.xml b/bridge/pom.xml
index c17b482a59df..3cfd33997c05 100644
--- a/bridge/pom.xml
+++ b/bridge/pom.xml
@@ -1,18 +1,75 @@
-
-
+
+
+
4.0.0
com.iluwatar
java-design-patterns
- 1.8.0-SNAPSHOT
+ 1.26.0-SNAPSHOT
bridge
- junit
- junit
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ 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 6d902fe2102e..c3ea3d50c35a 100644
--- a/bridge/src/main/java/com/iluwatar/bridge/App.java
+++ b/bridge/src/main/java/com/iluwatar/bridge/App.java
@@ -1,39 +1,63 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bridge;
+import lombok.extern.slf4j.Slf4j;
+
/**
- *
- * 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 MagicWeapon}) and implementation (
- * {@link MagicWeaponImpl}) have their own class hierarchies. The interface of the implementations
- * can be changed without affecting the clients.
- *
+ * 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.
*/
+@Slf4j
public class App {
/**
- * Program entry point
- *
+ * Program entry point.
+ *
* @param args command line args
*/
public static void main(String[] args) {
- BlindingMagicWeapon blindingMagicWeapon = new BlindingMagicWeapon(new Excalibur());
- blindingMagicWeapon.wield();
- blindingMagicWeapon.blind();
- blindingMagicWeapon.swing();
- blindingMagicWeapon.unwield();
-
- FlyingMagicWeapon flyingMagicWeapon = new FlyingMagicWeapon(new Mjollnir());
- flyingMagicWeapon.wield();
- flyingMagicWeapon.fly();
- flyingMagicWeapon.swing();
- flyingMagicWeapon.unwield();
+ LOGGER.info("The knight receives an enchanted sword.");
+ var enchantedSword = new Sword(new SoulEatingEnchantment());
+ enchantedSword.wield();
+ enchantedSword.swing();
+ enchantedSword.unwield();
- SoulEatingMagicWeapon soulEatingMagicWeapon = new SoulEatingMagicWeapon(new Stormbringer());
- soulEatingMagicWeapon.wield();
- soulEatingMagicWeapon.swing();
- soulEatingMagicWeapon.eatSoul();
- soulEatingMagicWeapon.unwield();
+ LOGGER.info("The valkyrie receives an enchanted hammer.");
+ var hammer = new Hammer(new FlyingEnchantment());
+ hammer.wield();
+ hammer.swing();
+ hammer.unwield();
}
}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/BlindingMagicWeapon.java b/bridge/src/main/java/com/iluwatar/bridge/BlindingMagicWeapon.java
deleted file mode 100644
index 572db251b4e6..000000000000
--- a/bridge/src/main/java/com/iluwatar/bridge/BlindingMagicWeapon.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.iluwatar.bridge;
-
-/**
- *
- * BlindingMagicWeapon
- *
- */
-public class BlindingMagicWeapon extends MagicWeapon {
-
- public BlindingMagicWeapon(BlindingMagicWeaponImpl imp) {
- super(imp);
- }
-
- @Override
- public BlindingMagicWeaponImpl getImp() {
- return (BlindingMagicWeaponImpl) imp;
- }
-
- @Override
- public void wield() {
- getImp().wieldImp();
- }
-
- @Override
- public void swing() {
- getImp().swingImp();
- }
-
- @Override
- public void unwield() {
- getImp().unwieldImp();
- }
-
- public void blind() {
- getImp().blindImp();
- }
-}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/BlindingMagicWeaponImpl.java b/bridge/src/main/java/com/iluwatar/bridge/BlindingMagicWeaponImpl.java
deleted file mode 100644
index cf1b47e9a39a..000000000000
--- a/bridge/src/main/java/com/iluwatar/bridge/BlindingMagicWeaponImpl.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.iluwatar.bridge;
-
-/**
- *
- * BlindingMagicWeaponImpl
- *
- */
-public abstract class BlindingMagicWeaponImpl extends MagicWeaponImpl {
-
- public abstract void blindImp();
-
-}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java b/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java
new file mode 100644
index 000000000000..4bdd4502fd47
--- /dev/null
+++ b/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java
@@ -0,0 +1,35 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bridge;
+
+/** Enchantment. */
+public interface Enchantment {
+
+ void onActivate();
+
+ void apply();
+
+ void onDeactivate();
+}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/Excalibur.java b/bridge/src/main/java/com/iluwatar/bridge/Excalibur.java
deleted file mode 100644
index 39365eaf810f..000000000000
--- a/bridge/src/main/java/com/iluwatar/bridge/Excalibur.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.iluwatar.bridge;
-
-/**
- *
- * Excalibur
- *
- */
-public class Excalibur extends BlindingMagicWeaponImpl {
-
- @Override
- public void wieldImp() {
- System.out.println("wielding Excalibur");
- }
-
- @Override
- public void swingImp() {
- System.out.println("swinging Excalibur");
- }
-
- @Override
- public void unwieldImp() {
- System.out.println("unwielding Excalibur");
- }
-
- @Override
- public void blindImp() {
- System.out.println("bright light streams from Excalibur blinding the enemy");
- }
-}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java b/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java
new file mode 100644
index 000000000000..42da3523fb31
--- /dev/null
+++ b/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java
@@ -0,0 +1,47 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bridge;
+
+import lombok.extern.slf4j.Slf4j;
+
+/** FlyingEnchantment. */
+@Slf4j
+public class FlyingEnchantment implements Enchantment {
+
+ @Override
+ public void onActivate() {
+ LOGGER.info("The item begins to glow faintly.");
+ }
+
+ @Override
+ public void apply() {
+ LOGGER.info("The item flies and strikes the enemies finally returning to owner's hand.");
+ }
+
+ @Override
+ public void onDeactivate() {
+ LOGGER.info("The item's glow fades.");
+ }
+}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/FlyingMagicWeapon.java b/bridge/src/main/java/com/iluwatar/bridge/FlyingMagicWeapon.java
deleted file mode 100644
index b4ae9a8f81c3..000000000000
--- a/bridge/src/main/java/com/iluwatar/bridge/FlyingMagicWeapon.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.iluwatar.bridge;
-
-/**
- *
- * FlyingMagicWeapon
- *
- */
-public class FlyingMagicWeapon extends MagicWeapon {
-
- public FlyingMagicWeapon(FlyingMagicWeaponImpl imp) {
- super(imp);
- }
-
- public FlyingMagicWeaponImpl getImp() {
- return (FlyingMagicWeaponImpl) imp;
- }
-
- @Override
- public void wield() {
- getImp().wieldImp();
- }
-
- @Override
- public void swing() {
- getImp().swingImp();
- }
-
- @Override
- public void unwield() {
- getImp().unwieldImp();
- }
-
- public void fly() {
- getImp().flyImp();
- }
-
-}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/FlyingMagicWeaponImpl.java b/bridge/src/main/java/com/iluwatar/bridge/FlyingMagicWeaponImpl.java
deleted file mode 100644
index 022c06565068..000000000000
--- a/bridge/src/main/java/com/iluwatar/bridge/FlyingMagicWeaponImpl.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.iluwatar.bridge;
-
-/**
- *
- * FlyingMagicWeaponImpl
- *
- */
-public abstract class FlyingMagicWeaponImpl extends MagicWeaponImpl {
-
- public abstract void flyImp();
-
-}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/Hammer.java b/bridge/src/main/java/com/iluwatar/bridge/Hammer.java
new file mode 100644
index 000000000000..328f3b79e9d6
--- /dev/null
+++ b/bridge/src/main/java/com/iluwatar/bridge/Hammer.java
@@ -0,0 +1,59 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bridge;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/** Hammer. */
+@Slf4j
+@AllArgsConstructor
+public class Hammer implements Weapon {
+
+ private final Enchantment enchantment;
+
+ @Override
+ public void wield() {
+ LOGGER.info("The hammer is wielded.");
+ enchantment.onActivate();
+ }
+
+ @Override
+ public void swing() {
+ LOGGER.info("The hammer is swung.");
+ enchantment.apply();
+ }
+
+ @Override
+ public void unwield() {
+ LOGGER.info("The hammer is unwielded.");
+ enchantment.onDeactivate();
+ }
+
+ @Override
+ public Enchantment getEnchantment() {
+ return enchantment;
+ }
+}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/MagicWeapon.java b/bridge/src/main/java/com/iluwatar/bridge/MagicWeapon.java
deleted file mode 100644
index 5e17d2b409b2..000000000000
--- a/bridge/src/main/java/com/iluwatar/bridge/MagicWeapon.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.iluwatar.bridge;
-
-/**
- *
- * MagicWeapon
- *
- */
-public abstract class MagicWeapon {
-
- protected MagicWeaponImpl imp;
-
- public MagicWeapon(MagicWeaponImpl imp) {
- this.imp = imp;
- }
-
- public abstract void wield();
-
- public abstract void swing();
-
- public abstract void unwield();
-
- public MagicWeaponImpl getImp() {
- return imp;
- }
-}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/MagicWeaponImpl.java b/bridge/src/main/java/com/iluwatar/bridge/MagicWeaponImpl.java
deleted file mode 100644
index bc672335e85d..000000000000
--- a/bridge/src/main/java/com/iluwatar/bridge/MagicWeaponImpl.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.iluwatar.bridge;
-
-/**
- *
- * MagicWeaponImpl
- *
- */
-public abstract class MagicWeaponImpl {
-
- public abstract void wieldImp();
-
- public abstract void swingImp();
-
- public abstract void unwieldImp();
-
-}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/Mjollnir.java b/bridge/src/main/java/com/iluwatar/bridge/Mjollnir.java
deleted file mode 100644
index 13641c335feb..000000000000
--- a/bridge/src/main/java/com/iluwatar/bridge/Mjollnir.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.iluwatar.bridge;
-
-/**
- *
- * Mjollnir
- *
- */
-public class Mjollnir extends FlyingMagicWeaponImpl {
-
- @Override
- public void wieldImp() {
- System.out.println("wielding Mjollnir");
- }
-
- @Override
- public void swingImp() {
- System.out.println("swinging Mjollnir");
- }
-
- @Override
- public void unwieldImp() {
- System.out.println("unwielding Mjollnir");
- }
-
- @Override
- public void flyImp() {
- System.out.println("Mjollnir hits the enemy in the air and returns back to the owner's hand");
- }
-}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java b/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java
new file mode 100644
index 000000000000..ed22174673d3
--- /dev/null
+++ b/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java
@@ -0,0 +1,47 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bridge;
+
+import lombok.extern.slf4j.Slf4j;
+
+/** SoulEatingEnchantment. */
+@Slf4j
+public class SoulEatingEnchantment implements Enchantment {
+
+ @Override
+ public void onActivate() {
+ LOGGER.info("The item spreads bloodlust.");
+ }
+
+ @Override
+ public void apply() {
+ LOGGER.info("The item eats the soul of enemies.");
+ }
+
+ @Override
+ public void onDeactivate() {
+ LOGGER.info("Bloodlust slowly disappears.");
+ }
+}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/SoulEatingMagicWeapon.java b/bridge/src/main/java/com/iluwatar/bridge/SoulEatingMagicWeapon.java
deleted file mode 100644
index 991719c316a1..000000000000
--- a/bridge/src/main/java/com/iluwatar/bridge/SoulEatingMagicWeapon.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.iluwatar.bridge;
-
-/**
- *
- * SoulEatingMagicWeapon
- *
- */
-public class SoulEatingMagicWeapon extends MagicWeapon {
-
- public SoulEatingMagicWeapon(SoulEatingMagicWeaponImpl imp) {
- super(imp);
- }
-
- @Override
- public SoulEatingMagicWeaponImpl getImp() {
- return (SoulEatingMagicWeaponImpl) imp;
- }
-
- @Override
- public void wield() {
- getImp().wieldImp();
- }
-
- @Override
- public void swing() {
- getImp().swingImp();
- }
-
- @Override
- public void unwield() {
- getImp().unwieldImp();
- }
-
- public void eatSoul() {
- getImp().eatSoulImp();
- }
-
-}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/SoulEatingMagicWeaponImpl.java b/bridge/src/main/java/com/iluwatar/bridge/SoulEatingMagicWeaponImpl.java
deleted file mode 100644
index ec2c3a9416b4..000000000000
--- a/bridge/src/main/java/com/iluwatar/bridge/SoulEatingMagicWeaponImpl.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.iluwatar.bridge;
-
-/**
- *
- * SoulEatingMagicWeaponImpl
- *
- */
-public abstract class SoulEatingMagicWeaponImpl extends MagicWeaponImpl {
-
- public abstract void eatSoulImp();
-
-}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/Stormbringer.java b/bridge/src/main/java/com/iluwatar/bridge/Stormbringer.java
deleted file mode 100644
index ebbe0c23fe84..000000000000
--- a/bridge/src/main/java/com/iluwatar/bridge/Stormbringer.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.iluwatar.bridge;
-
-/**
- *
- * Stormbringer
- *
- */
-public class Stormbringer extends SoulEatingMagicWeaponImpl {
-
- @Override
- public void wieldImp() {
- System.out.println("wielding Stormbringer");
- }
-
- @Override
- public void swingImp() {
- System.out.println("swinging Stormbringer");
- }
-
- @Override
- public void unwieldImp() {
- System.out.println("unwielding Stormbringer");
- }
-
- @Override
- public void eatSoulImp() {
- System.out.println("Stormbringer devours the enemy's soul");
- }
-}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/Sword.java b/bridge/src/main/java/com/iluwatar/bridge/Sword.java
new file mode 100644
index 000000000000..417bc9334046
--- /dev/null
+++ b/bridge/src/main/java/com/iluwatar/bridge/Sword.java
@@ -0,0 +1,59 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bridge;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/** Sword. */
+@Slf4j
+@AllArgsConstructor
+public class Sword implements Weapon {
+
+ private final Enchantment enchantment;
+
+ @Override
+ public void wield() {
+ LOGGER.info("The sword is wielded.");
+ enchantment.onActivate();
+ }
+
+ @Override
+ public void swing() {
+ LOGGER.info("The sword is swung.");
+ enchantment.apply();
+ }
+
+ @Override
+ public void unwield() {
+ LOGGER.info("The sword is unwielded.");
+ enchantment.onDeactivate();
+ }
+
+ @Override
+ public Enchantment getEnchantment() {
+ return enchantment;
+ }
+}
diff --git a/bridge/src/main/java/com/iluwatar/bridge/Weapon.java b/bridge/src/main/java/com/iluwatar/bridge/Weapon.java
new file mode 100644
index 000000000000..a9f0ab4bbf52
--- /dev/null
+++ b/bridge/src/main/java/com/iluwatar/bridge/Weapon.java
@@ -0,0 +1,37 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bridge;
+
+/** Weapon. */
+public interface Weapon {
+
+ void wield();
+
+ void swing();
+
+ void unwield();
+
+ Enchantment getEnchantment();
+}
diff --git a/bridge/src/test/java/com/iluwatar/bridge/AppTest.java b/bridge/src/test/java/com/iluwatar/bridge/AppTest.java
index 99faad43efa9..d1136fc90f41 100644
--- a/bridge/src/test/java/com/iluwatar/bridge/AppTest.java
+++ b/bridge/src/test/java/com/iluwatar/bridge/AppTest.java
@@ -1,19 +1,44 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bridge;
-import org.junit.Test;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-import com.iluwatar.bridge.App;
+import org.junit.jupiter.api.Test;
-/**
- *
- * Application test
- *
- */
-public class AppTest {
+/** Application test */
+class AppTest {
+ /**
+ * Issue: Add at least one assertion to this test case.
+ *
+ *
Solution: Inserted assertion to check whether the execution of the main method in {@link
+ * App} throws an exception.
+ */
@Test
- public void test() {
- String[] args = {};
- App.main(args);
+ void shouldExecuteApplicationWithoutException() {
+ assertDoesNotThrow(() -> 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
new file mode 100644
index 000000000000..d8853647cb84
--- /dev/null
+++ b/bridge/src/test/java/com/iluwatar/bridge/HammerTest.java
@@ -0,0 +1,44 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bridge;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import org.junit.jupiter.api.Test;
+
+/** Tests for hammer */
+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
+ void testHammer() {
+ final var hammer = spy(new Hammer(mock(FlyingEnchantment.class)));
+ testBasicWeaponActions(hammer);
+ }
+}
diff --git a/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java b/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java
new file mode 100644
index 000000000000..b021cd08d00c
--- /dev/null
+++ b/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java
@@ -0,0 +1,44 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bridge;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import org.junit.jupiter.api.Test;
+
+/** Tests for sword */
+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
+ void testSword() {
+ final var sword = spy(new Sword(mock(FlyingEnchantment.class)));
+ testBasicWeaponActions(sword);
+ }
+}
diff --git a/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java b/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java
new file mode 100644
index 000000000000..67648ea6391e
--- /dev/null
+++ b/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java
@@ -0,0 +1,56 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.bridge;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+/** Base class for weapon tests */
+abstract class WeaponTest {
+
+ /**
+ * Invoke the basic actions of the given weapon, and test if the underlying enchantment
+ * implementation is invoked
+ */
+ final void testBasicWeaponActions(final Weapon weapon) {
+ assertNotNull(weapon);
+ var enchantment = weapon.getEnchantment();
+ assertNotNull(enchantment);
+ assertNotNull(weapon.getEnchantment());
+
+ weapon.swing();
+ verify(enchantment).apply();
+ verifyNoMoreInteractions(enchantment);
+
+ weapon.wield();
+ verify(enchantment).onActivate();
+ verifyNoMoreInteractions(enchantment);
+
+ weapon.unwield();
+ verify(enchantment).onDeactivate();
+ verifyNoMoreInteractions(enchantment);
+ }
+}
diff --git a/builder/README.md b/builder/README.md
new file mode 100644
index 000000000000..cf6e3242dd51
--- /dev/null
+++ b/builder/README.md
@@ -0,0 +1,204 @@
+---
+title: "Builder Pattern in Java: Crafting Custom Objects with Clarity"
+shortTitle: Builder
+description: "Discover the Builder design pattern in Java, a powerful creational pattern that simplifies object construction. Learn how to separate the construction of a complex object from its representation with practical examples and use cases."
+category: Creational
+language: en
+tag:
+ - Gang of Four
+ - Instantiation
+ - Object composition
+---
+
+## Intent of Builder Design Pattern
+
+The Builder design pattern in Java, a fundamental creational pattern, allows for the step-by-step construction of complex objects. It separates the construction of a complex object from its representation so that the same construction process can create different representations.
+
+## Detailed Explanation of Builder Pattern with Real-World Examples
+
+Real-world example
+
+> The Java Builder pattern is particularly useful in scenarios where object creation involves numerous parameters.
+>
+> Imagine you are building a customizable sandwich at a deli. The Builder design pattern in this context would involve a SandwichBuilder that allows you to specify each component of the sandwich, such as the type of bread, meat, cheese, vegetables, and condiments. Instead of having to know how to construct the sandwich from scratch, you use the SandwichBuilder to add each desired component step-by-step, ensuring you get exactly the sandwich you want. This separation of construction from the final product representation ensures that the same construction process can yield different types of sandwiches based on the specified components.
+
+In plain words
+
+> Allows you to create different flavors of an object while avoiding constructor pollution. Useful when there could be several flavors of an object. Or when there are a lot of steps involved in creation of an object.
+
+Wikipedia says
+
+> The builder pattern is an object creation software design pattern with the intentions of finding a solution to the telescoping constructor antipattern.
+
+With that in mind, let's explain what the telescoping constructor antipattern is. At some point, we have all encountered a constructor like the one below:
+
+```java
+public Hero(Profession profession,String name,HairType hairType,HairColor hairColor,Armor armor,Weapon weapon){
+ // Value assignments
+}
+```
+
+As you can see, the number of constructor parameters can quickly become overwhelming, making it difficult to understand their arrangement. Additionally, this list of parameters might continue to grow if you decide to add more options in the future. This is known as the telescoping constructor antipattern.
+
+Sequence diagram
+
+
+
+## Programmatic Example of Builder Pattern in Java
+
+In this Java Builder pattern example, we construct different types of `Hero` objects with varying attributes.
+
+Imagine a character generator for a role-playing game. The simplest option is to let the computer generate the character for you. However, if you prefer to manually select character details such as profession, gender, hair color, etc., the character creation becomes a step-by-step process that concludes once all selections are made.
+
+A more sensible approach is to use the Builder pattern. First, let's consider the `Hero` that we want to create:
+
+```java
+public final class Hero {
+ private final Profession profession;
+ private final String name;
+ private final HairType hairType;
+ private final HairColor hairColor;
+ private final Armor armor;
+ private final Weapon weapon;
+
+ private Hero(Builder builder) {
+ this.profession = builder.profession;
+ this.name = builder.name;
+ this.hairColor = builder.hairColor;
+ this.hairType = builder.hairType;
+ this.weapon = builder.weapon;
+ this.armor = builder.armor;
+ }
+}
+```
+
+Then we have the `Builder`:
+
+```java
+ public static class Builder {
+ private final Profession profession;
+ private final String name;
+ private HairType hairType;
+ private HairColor hairColor;
+ private Armor armor;
+ private Weapon weapon;
+
+ public Builder(Profession profession, String name) {
+ if (profession == null || name == null) {
+ throw new IllegalArgumentException("profession and name can not be null");
+ }
+ this.profession = profession;
+ this.name = name;
+ }
+
+ public Builder withHairType(HairType hairType) {
+ this.hairType = hairType;
+ return this;
+ }
+
+ public Builder withHairColor(HairColor hairColor) {
+ this.hairColor = hairColor;
+ return this;
+ }
+
+ public Builder withArmor(Armor armor) {
+ this.armor = armor;
+ return this;
+ }
+
+ public Builder withWeapon(Weapon weapon) {
+ this.weapon = weapon;
+ return this;
+ }
+
+ public Hero build() {
+ return new Hero(this);
+ }
+}
+```
+
+Then it can be used as:
+
+```java
+ public static void main(String[] args) {
+
+ var mage = new Hero.Builder(Profession.MAGE, "Riobard")
+ .withHairColor(HairColor.BLACK)
+ .withWeapon(Weapon.DAGGER)
+ .build();
+ LOGGER.info(mage.toString());
+
+ 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());
+
+ var thief = new Hero.Builder(Profession.THIEF, "Desmond")
+ .withHairType(HairType.BALD)
+ .withWeapon(Weapon.BOW)
+ .build();
+ LOGGER.info(thief.toString());
+}
+```
+
+Program output:
+
+```
+16:28:06.058 [main] INFO com.iluwatar.builder.App -- This is a mage named Riobard with black hair and wielding a dagger.
+16:28:06.060 [main] INFO com.iluwatar.builder.App -- This is a warrior named Amberjill with blond long curly hair wearing chain mail and wielding a sword.
+16:28:06.060 [main] INFO com.iluwatar.builder.App -- This is a thief named Desmond with bald head and wielding a bow.
+```
+
+## When to Use the Builder Pattern in Java
+
+Use the Builder pattern when
+
+* The Builder pattern is ideal for Java applications requiring complex object creation.
+* The algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled
+* The construction process must allow different representations for the object that's constructed
+* It's particularly useful when a product requires a lot of steps to be created and when these steps need to be executed in a specific sequence
+
+## Builder Pattern Java Tutorials
+
+* [Builder Design Pattern in Java (DigitalOcean)](https://www.journaldev.com/1425/builder-design-pattern-in-java)
+* [Builder (Refactoring Guru)](https://refactoring.guru/design-patterns/builder)
+* [Exploring Joshua Bloch’s Builder design pattern in Java (Java Magazine)](https://blogs.oracle.com/javamagazine/post/exploring-joshua-blochs-builder-design-pattern-in-java)
+
+## Real-World Applications of Builder Pattern in Java
+
+* StringBuilder in Java for constructing strings.
+* java.lang.StringBuffer used to create mutable string objects.
+* Java.nio.ByteBuffer as well as similar buffers such as FloatBuffer, IntBuffer, and others
+* javax.swing.GroupLayout.Group#addComponent()
+* Various GUI builders in IDEs that construct UI components.
+* 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)
+
+## Benefits and Trade-offs of Builder Pattern
+
+Benefits:
+
+* More control over the construction process compared to other creational patterns
+* Supports constructing objects step-by-step, defer construction steps or run steps recursively
+* Can construct objects that require a complex assembly of sub-objects. The final product is detached from the parts that make it up, as well as their assembly process
+* Single Responsibility Principle. You can isolate complex construction code from the business logic of the product
+
+Trade-offs:
+
+* The overall complexity of the code can increase since the pattern requires creating multiple new classes
+* May increase memory usage due to the necessity of creating multiple builder objects
+
+## Related Java Design Patterns
+
+* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): Can be used in conjunction with Builder to build parts of a complex object.
+* [Prototype](https://java-design-patterns.com/patterns/prototype/): Builders often create objects from a prototype.
+* [Step Builder](https://java-design-patterns.com/patterns/step-builder/): It is a variation of the Builder pattern that generates a complex object using a step-by-step approach. The Step Builder pattern is a good choice when you need to build an object with a large number of optional parameters, and you want to avoid the telescoping constructor antipattern.
+
+## References and Credits
+
+* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
+* [Effective Java](https://amzn.to/4cGk2Jz)
+* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq)
+* [Refactoring to Patterns](https://amzn.to/3VOO4F5)
diff --git a/builder/etc/builder-sequence-diagram.png b/builder/etc/builder-sequence-diagram.png
new file mode 100644
index 000000000000..355556637870
Binary files /dev/null and b/builder/etc/builder-sequence-diagram.png differ
diff --git a/builder/etc/builder.png b/builder/etc/builder.png
deleted file mode 100644
index a0280ba53f7d..000000000000
Binary files a/builder/etc/builder.png and /dev/null differ
diff --git a/builder/etc/builder.ucls b/builder/etc/builder.ucls
deleted file mode 100644
index 06a83ced7f79..000000000000
--- a/builder/etc/builder.ucls
+++ /dev/null
@@ -1,162 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
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/etc/builder_1.png b/builder/etc/builder_1.png
deleted file mode 100644
index ec892c15f6c4..000000000000
Binary files a/builder/etc/builder_1.png and /dev/null differ
diff --git a/builder/index.md b/builder/index.md
deleted file mode 100644
index f350638d24d6..000000000000
--- a/builder/index.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-layout: pattern
-title: Builder
-folder: builder
-permalink: /patterns/builder/
-categories: Creational
-tags:
- - Java
- - Gang Of Four
----
-
-**Intent:** Separate the construction of a complex object from its
-representation so that the same construction process can create different
-representations.
-
-
-
-**Applicability:** Use the Builder pattern when
-
-* the algorithm for creating a complex object should be independent of the parts that make up the object and how they're assembled
-* the construction process must allow different representations for the object that's constructed
-
-**Real world examples:**
-
-* [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html)
-* [Apache Camel builders](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder)
-
-**Credits**
-
-* [Design Patterns: Elements of Reusable Object-Oriented Software](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)
diff --git a/builder/pom.xml b/builder/pom.xml
index f26494b2e717..3677c187d5e6 100644
--- a/builder/pom.xml
+++ b/builder/pom.xml
@@ -1,18 +1,70 @@
-
-
+
+
+
4.0.0
com.iluwatar
java-design-patterns
- 1.8.0-SNAPSHOT
+ 1.26.0-SNAPSHOT
builder
- junit
- junit
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ 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 b5c8fd7a9353..acec73a48ee1 100644
--- a/builder/src/main/java/com/iluwatar/builder/App.java
+++ b/builder/src/main/java/com/iluwatar/builder/App.java
@@ -1,55 +1,84 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.builder;
-import com.iluwatar.builder.Hero.HeroBuilder;
+import com.iluwatar.builder.Hero.Builder;
+import lombok.extern.slf4j.Slf4j;
/**
- *
* 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
+ * antipattern. The telescoping constructor antipattern 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 HeroBuilder} class. {@link HeroBuilder}
- * 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 HeroBuilder} 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.
*/
+@Slf4j
public class App {
/**
- * Program entry point
- *
+ * Program entry point.
+ *
* @param args command line args
*/
public static void main(String[] args) {
- Hero mage =
- new HeroBuilder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK)
- .withWeapon(Weapon.DAGGER).build();
- System.out.println(mage);
-
- Hero warrior =
- new HeroBuilder(Profession.WARRIOR, "Amberjill").withHairColor(HairColor.BLOND)
- .withHairType(HairType.LONG_CURLY).withArmor(Armor.CHAIN_MAIL).withWeapon(Weapon.SWORD)
+ var mage =
+ new Hero.Builder(Profession.MAGE, "Riobard")
+ .withHairColor(HairColor.BLACK)
+ .withWeapon(Weapon.DAGGER)
.build();
- System.out.println(warrior);
+ LOGGER.info(mage.toString());
- Hero thief =
- new HeroBuilder(Profession.THIEF, "Desmond").withHairType(HairType.BALD)
- .withWeapon(Weapon.BOW).build();
- System.out.println(thief);
+ 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());
+ 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 6b67b8f94602..1710f569af5e 100644
--- a/builder/src/main/java/com/iluwatar/builder/Armor.java
+++ b/builder/src/main/java/com/iluwatar/builder/Armor.java
@@ -1,19 +1,40 @@
-package com.iluwatar.builder;
-
-/**
- *
- * Armor enumeration
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.
*/
-public enum Armor {
+package com.iluwatar.builder;
- CLOTHES("clothes"), LEATHER("leather"), CHAIN_MAIL("chain mail"), PLATE_MAIL("plate mail");
+import lombok.AllArgsConstructor;
- private String title;
+/** Armor enumeration. */
+@AllArgsConstructor
+public enum Armor {
+ CLOTHES("clothes"),
+ LEATHER("leather"),
+ CHAIN_MAIL("chain mail"),
+ PLATE_MAIL("plate mail");
- Armor(String title) {
- this.title = title;
- }
+ private final String title;
@Override
public String toString() {
diff --git a/builder/src/main/java/com/iluwatar/builder/HairColor.java b/builder/src/main/java/com/iluwatar/builder/HairColor.java
index b99b3db63cd5..7f767c98d661 100644
--- a/builder/src/main/java/com/iluwatar/builder/HairColor.java
+++ b/builder/src/main/java/com/iluwatar/builder/HairColor.java
@@ -1,17 +1,39 @@
-package com.iluwatar.builder;
-
-/**
- *
- * HairColor enumeration
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.
*/
-public enum HairColor {
+package com.iluwatar.builder;
- WHITE, BLOND, RED, BROWN, BLACK;
+/** HairColor enumeration. */
+public enum HairColor {
+ WHITE,
+ BLOND,
+ RED,
+ BROWN,
+ BLACK;
@Override
public String toString() {
return name().toLowerCase();
}
-
}
diff --git a/builder/src/main/java/com/iluwatar/builder/HairType.java b/builder/src/main/java/com/iluwatar/builder/HairType.java
index 48eeac9504dc..7ac31d0fa03e 100644
--- a/builder/src/main/java/com/iluwatar/builder/HairType.java
+++ b/builder/src/main/java/com/iluwatar/builder/HairType.java
@@ -1,20 +1,41 @@
-package com.iluwatar.builder;
-
-/**
- *
- * HairType enumeration
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.
*/
-public enum HairType {
+package com.iluwatar.builder;
- BALD("bald"), SHORT("short"), CURLY("curly"), LONG_STRAIGHT("long straight"), LONG_CURLY(
- "long curly");
+import lombok.AllArgsConstructor;
- private String title;
+/** HairType enumeration. */
+@AllArgsConstructor
+public enum HairType {
+ BALD("bald"),
+ SHORT("short"),
+ CURLY("curly"),
+ LONG_STRAIGHT("long straight"),
+ LONG_CURLY("long curly");
- HairType(String title) {
- this.title = title;
- }
+ private final String title;
@Override
public String toString() {
diff --git a/builder/src/main/java/com/iluwatar/builder/Hero.java b/builder/src/main/java/com/iluwatar/builder/Hero.java
index 54be11ea3aaa..a87137e51fe0 100644
--- a/builder/src/main/java/com/iluwatar/builder/Hero.java
+++ b/builder/src/main/java/com/iluwatar/builder/Hero.java
@@ -1,90 +1,75 @@
-package com.iluwatar.builder;
-
-/**
- *
- * Hero, the class with many parameters.
- *
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.
*/
-public class Hero {
-
- private final Profession profession;
- private final String name;
- private final HairType hairType;
- private final HairColor hairColor;
- private final Armor armor;
- private final Weapon weapon;
-
- public Profession getProfession() {
- return profession;
- }
-
- public String getName() {
- return name;
- }
-
- public HairType getHairType() {
- return hairType;
- }
-
- public HairColor getHairColor() {
- return hairColor;
- }
-
- public Armor getArmor() {
- return armor;
- }
+package com.iluwatar.builder;
- public Weapon getWeapon() {
- return weapon;
+/** Hero,the record class. */
+public record Hero(
+ Profession profession,
+ String name,
+ HairType hairType,
+ HairColor hairColor,
+ Armor armor,
+ Weapon weapon) {
+
+ private Hero(Builder builder) {
+ this(
+ builder.profession,
+ builder.name,
+ builder.hairType,
+ builder.hairColor,
+ builder.armor,
+ builder.weapon);
}
@Override
public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("This is a ");
- sb.append(profession);
- sb.append(" named ");
- sb.append(name);
+ var sb = new StringBuilder();
+ sb.append("This is a ").append(profession).append(" named ").append(name);
if (hairColor != null || hairType != null) {
sb.append(" with ");
if (hairColor != null) {
- sb.append(hairColor);
- sb.append(" ");
+ sb.append(hairColor).append(' ');
}
if (hairType != null) {
- sb.append(hairType);
- sb.append(" ");
+ sb.append(hairType).append(' ');
}
sb.append(hairType != HairType.BALD ? "hair" : "head");
}
if (armor != null) {
- sb.append(" wearing ");
- sb.append(armor);
+ sb.append(" wearing ").append(armor);
}
if (weapon != null) {
- sb.append(" and wielding a ");
- sb.append(weapon);
+ sb.append(" and wielding a ").append(weapon);
}
- sb.append(".");
+ sb.append('.');
return sb.toString();
}
- private Hero(HeroBuilder builder) {
- this.profession = builder.profession;
- this.name = builder.name;
- this.hairColor = builder.hairColor;
- this.hairType = builder.hairType;
- this.weapon = builder.weapon;
- this.armor = builder.armor;
- }
-
- /**
- *
- * The builder class.
- *
- */
- public static class HeroBuilder {
+ /** The builder class. */
+ public static class Builder {
private final Profession profession;
private final String name;
@@ -93,7 +78,8 @@ public static class HeroBuilder {
private Armor armor;
private Weapon weapon;
- public HeroBuilder(Profession profession, String name) {
+ /** Constructor. */
+ public Builder(Profession profession, String name) {
if (profession == null || name == null) {
throw new IllegalArgumentException("profession and name can not be null");
}
@@ -101,22 +87,22 @@ public HeroBuilder(Profession profession, String name) {
this.name = name;
}
- public HeroBuilder withHairType(HairType hairType) {
+ public Builder withHairType(HairType hairType) {
this.hairType = hairType;
return this;
}
- public HeroBuilder withHairColor(HairColor hairColor) {
+ public Builder withHairColor(HairColor hairColor) {
this.hairColor = hairColor;
return this;
}
- public HeroBuilder withArmor(Armor armor) {
+ public Builder withArmor(Armor armor) {
this.armor = armor;
return this;
}
- public HeroBuilder withWeapon(Weapon weapon) {
+ public Builder withWeapon(Weapon weapon) {
this.weapon = weapon;
return this;
}
diff --git a/builder/src/main/java/com/iluwatar/builder/Profession.java b/builder/src/main/java/com/iluwatar/builder/Profession.java
index a157366b43d7..c1be949840f8 100644
--- a/builder/src/main/java/com/iluwatar/builder/Profession.java
+++ b/builder/src/main/java/com/iluwatar/builder/Profession.java
@@ -1,13 +1,35 @@
-package com.iluwatar.builder;
-
-/**
- *
- * Profession enumeration
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.
*/
-public enum Profession {
+package com.iluwatar.builder;
- WARRIOR, THIEF, MAGE, PRIEST;
+/** Profession enumeration. */
+public enum Profession {
+ WARRIOR,
+ THIEF,
+ MAGE,
+ PRIEST;
@Override
public String toString() {
diff --git a/builder/src/main/java/com/iluwatar/builder/Weapon.java b/builder/src/main/java/com/iluwatar/builder/Weapon.java
index 08f55c65ca8f..03a9565d0cbe 100644
--- a/builder/src/main/java/com/iluwatar/builder/Weapon.java
+++ b/builder/src/main/java/com/iluwatar/builder/Weapon.java
@@ -1,13 +1,36 @@
-package com.iluwatar.builder;
-
-/**
- *
- * Weapon enumeration
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.
*/
-public enum Weapon {
+package com.iluwatar.builder;
- DAGGER, SWORD, AXE, WARHAMMER, BOW;
+/** Weapon enumeration. */
+public enum Weapon {
+ DAGGER,
+ SWORD,
+ AXE,
+ WARHAMMER,
+ BOW;
@Override
public String toString() {
diff --git a/builder/src/test/java/com/iluwatar/builder/AppTest.java b/builder/src/test/java/com/iluwatar/builder/AppTest.java
index 857f49dc9a4d..367d3da1c1ac 100644
--- a/builder/src/test/java/com/iluwatar/builder/AppTest.java
+++ b/builder/src/test/java/com/iluwatar/builder/AppTest.java
@@ -1,19 +1,44 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.builder;
-import org.junit.Test;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-import com.iluwatar.builder.App;
+import org.junit.jupiter.api.Test;
-/**
- *
- * Application test
- *
- */
-public class AppTest {
+/** Application test */
+class AppTest {
+ /**
+ * Issue: Add at least one assertion to this test case.
+ *
+ *
Solution: Inserted assertion to check whether the execution of the main method in {@link
+ * App} throws an exception.
+ */
@Test
- public void test() {
- String[] args = {};
- App.main(args);
+ void shouldExecuteApplicationWithoutException() {
+ assertDoesNotThrow(() -> 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
new file mode 100644
index 000000000000..5f67a56aa999
--- /dev/null
+++ b/builder/src/test/java/com/iluwatar/builder/HeroTest.java
@@ -0,0 +1,70 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.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;
+
+/** HeroTest */
+class HeroTest {
+
+ /** Test if we get the expected exception when trying to create a hero without a profession */
+ @Test
+ void testMissingProfession() {
+ assertThrows(IllegalArgumentException.class, () -> new Hero.Builder(null, "Sir without a job"));
+ }
+
+ /** Test if we get the expected exception when trying to create a hero without a name */
+ @Test
+ void testMissingName() {
+ assertThrows(IllegalArgumentException.class, () -> new Hero.Builder(Profession.THIEF, null));
+ }
+
+ /** Test if the hero build by the builder has the correct attributes, as requested */
+ @Test
+ void testBuildHero() {
+ final String heroName = "Sir Lancelot";
+
+ final var hero =
+ new Hero.Builder(Profession.WARRIOR, heroName)
+ .withArmor(Armor.CHAIN_MAIL)
+ .withWeapon(Weapon.SWORD)
+ .withHairType(HairType.LONG_CURLY)
+ .withHairColor(HairColor.BLOND)
+ .build();
+
+ assertNotNull(hero);
+ assertNotNull(hero.toString());
+ assertEquals(Profession.WARRIOR, hero.profession());
+ assertEquals(heroName, hero.name());
+ assertEquals(Armor.CHAIN_MAIL, hero.armor());
+ assertEquals(Weapon.SWORD, hero.weapon());
+ assertEquals(HairType.LONG_CURLY, hero.hairType());
+ assertEquals(HairColor.BLOND, hero.hairColor());
+ }
+}
diff --git a/business-delegate/README.md b/business-delegate/README.md
new file mode 100644
index 000000000000..9f83e1a1748a
--- /dev/null
+++ b/business-delegate/README.md
@@ -0,0 +1,193 @@
+---
+title: "Business Delegate Pattern in Java: Simplifying Business Service Interaction"
+shortTitle: Business Delegate
+description: "Learn about the Business Delegate pattern in Java. This design pattern adds an abstraction layer between presentation and business tiers, ensuring loose coupling and easier service interaction. Includes examples and class diagrams."
+category: Structural
+language: en
+tag:
+ - Business
+ - Decoupling
+ - Delegation
+ - Enterprise patterns
+ - Layered architecture
+---
+
+## Also known as
+
+* Service Representative
+
+## Intent of Business Delegate Design Pattern
+
+The Business Delegate pattern is a structural design pattern in Java that adds an abstraction layer between the 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.
+
+## Detailed Explanation of Business Delegate Pattern with Real-World Examples
+
+Real-world example
+
+> In an Enterprise application using Java EE, the Business Delegate pattern helps manage interactions between different business services.
+>
+> Imagine a restaurant where the waitstaff serves as intermediaries between the customers and the kitchen. When a customer places an order, the waiter takes the order to the kitchen, relays any specific requests, and later brings the prepared food back to the customer. The waitstaff abstracts the complexity of the kitchen operations from the customers, allowing the chefs to focus solely on cooking without needing to interact directly with customers. This setup allows both the customer service (presentation tier) and the kitchen (business service) to operate independently and efficiently. The waitstaff acts as the Business Delegate, managing communication and ensuring smooth interactions between the two distinct areas.
+
+In Plain Words
+
+> Business Delegate adds an abstraction layer between the presentation and business tiers.
+
+Wikipedia says
+
+> Business Delegate is a Java EE design pattern. This pattern is directing to reduce the coupling in between business services and the connected presentation tier, and to hide the implementation details of services (including lookup and accessibility of EJB architecture). Business Delegates acts as an adaptor to invoke business objects from the presentation tier.
+
+Sequence diagram
+
+
+
+## Programmatic Example of Business Delegate Pattern in Java
+
+The following Java code demonstrates how to implement the Business Delegate pattern. This pattern is particularly useful in applications requiring loose coupling and efficient service interaction.
+
+A mobile phone application promises to stream any movie in existence to your device. It captures the user's search string and passes this on to the Business Delegate. The Business Delegate selects the most suitable video streaming service and plays the video from there.
+
+First, we have an abstraction for video streaming services and a couple of implementations.
+
+```java
+public interface VideoStreamingService {
+ void doProcessing();
+}
+
+@Slf4j
+public class NetflixService implements VideoStreamingService {
+ @Override
+ public void doProcessing() {
+ LOGGER.info("NetflixService is now processing");
+ }
+}
+
+@Slf4j
+public class YouTubeService implements VideoStreamingService {
+ @Override
+ public void doProcessing() {
+ LOGGER.info("YouTubeService is now processing");
+ }
+}
+```
+
+Then, we have a lookup service that decides which video streaming service to use.
+
+```java
+
+@Setter
+public class BusinessLookup {
+
+ private NetflixService netflixService;
+ private YouTubeService youTubeService;
+
+ public VideoStreamingService getBusinessService(String movie) {
+ if (movie.toLowerCase(Locale.ROOT).contains("die hard")) {
+ return netflixService;
+ } else {
+ return youTubeService;
+ }
+ }
+}
+```
+
+The Business Delegate uses a business lookup to route movie playback requests to a suitable video streaming service.
+
+```java
+
+@Setter
+public class BusinessDelegate {
+
+ private BusinessLookup lookupService;
+
+ public void playbackMovie(String movie) {
+ VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie);
+ videoStreamingService.doProcessing();
+ }
+}
+```
+
+The mobile client utilizes Business Delegate to call the business tier.
+
+```java
+public class MobileClient {
+
+ private final BusinessDelegate businessDelegate;
+
+ public MobileClient(BusinessDelegate businessDelegate) {
+ this.businessDelegate = businessDelegate;
+ }
+
+ public void playbackMovie(String movie) {
+ businessDelegate.playbackMovie(movie);
+ }
+}
+```
+
+Finally, we can demonstrate the complete example in action.
+
+```java
+public static void main(String[] args) {
+
+ // prepare the objects
+ var businessDelegate = new BusinessDelegate();
+ var businessLookup = new BusinessLookup();
+ businessLookup.setNetflixService(new NetflixService());
+ businessLookup.setYouTubeService(new YouTubeService());
+ businessDelegate.setLookupService(businessLookup);
+
+ // create the client and use the business delegate
+ var client = new MobileClient(businessDelegate);
+ client.playbackMovie("Die Hard 2");
+ client.playbackMovie("Maradona: The Greatest Ever");
+}
+```
+
+Here is the console output.
+
+```
+21:15:33.790 [main] INFO com.iluwatar.business.delegate.NetflixService - NetflixService is now processing
+21:15:33.794 [main] INFO com.iluwatar.business.delegate.YouTubeService - YouTubeService is now processing
+```
+
+## When to Use the Business Delegate Pattern in Java
+
+Use the Business Delegate pattern when
+
+* You need loose coupling between presentation and business tiers or need to abstract service lookups.
+* You want to orchestrate calls to multiple business services
+* You want to encapsulate service lookups and service calls
+* There is a need to abstract and encapsulate the communication between the client tier and business services
+
+## Business Delegate Pattern Java Tutorials
+
+* [Design Patterns - Business Delegate Pattern (TutorialsPoint)](https://www.tutorialspoint.com/design_pattern/business_delegate_pattern.htm)
+
+## Real-World Applications of Business Delegate Pattern in Java
+
+* Enterprise applications using Java EE (Java Platform, Enterprise Edition)
+* Applications requiring remote access to business services
+
+## Benefits and Trade-offs of Business Delegate Pattern
+
+Benefits:
+
+* Decoupling of Presentation and Business Tiers: Allows the client tier and business services to evolve independently.
+* Location Transparency: Clients remain unaffected by changes in the location or the instantiation of business services.
+* Reuse and Scalability: Business Delegate objects can be reused by multiple clients, and the pattern supports load balancing and scalability.
+
+Trade-offs:
+
+* Complexity: Introduces additional layers and abstractions, which may increase complexity.
+* Performance Overhead: The additional indirection may incur a slight performance penalty.
+
+## Related Java Design Patterns
+
+* [Service Locator](https://java-design-patterns.com/patterns/service-locator/): Business Delegate uses Service Locator to locate business services.
+* [Session Facade](https://java-design-patterns.com/patterns/session-facade/): Business Delegate may use Session Facade to provide a unified interface to a set of business services.
+* [Composite Entity](https://java-design-patterns.com/patterns/composite-entity/): Business Delegate may use Composite Entity to manage the state of business services.
+
+## References and Credits
+
+* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap)
+* [J2EE Design Patterns](https://amzn.to/4dpzgmx)
+* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR)
diff --git a/business-delegate/etc/business-delegate-sequence-diagram.png b/business-delegate/etc/business-delegate-sequence-diagram.png
new file mode 100644
index 000000000000..a6279e9d1888
Binary files /dev/null and b/business-delegate/etc/business-delegate-sequence-diagram.png differ
diff --git a/business-delegate/etc/business-delegate.png b/business-delegate/etc/business-delegate.png
deleted file mode 100644
index 928cf934645b..000000000000
Binary files a/business-delegate/etc/business-delegate.png and /dev/null differ
diff --git a/business-delegate/etc/business-delegate.ucls b/business-delegate/etc/business-delegate.ucls
deleted file mode 100644
index 668a6579eb47..000000000000
--- a/business-delegate/etc/business-delegate.ucls
+++ /dev/null
@@ -1,136 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/business-delegate/etc/business-delegate.urm.png b/business-delegate/etc/business-delegate.urm.png
new file mode 100644
index 000000000000..4dca6c263b99
Binary files /dev/null and b/business-delegate/etc/business-delegate.urm.png differ
diff --git a/business-delegate/etc/business-delegate.urm.puml b/business-delegate/etc/business-delegate.urm.puml
new file mode 100644
index 000000000000..407e3e12d1ea
--- /dev/null
+++ b/business-delegate/etc/business-delegate.urm.puml
@@ -0,0 +1,46 @@
+@startuml
+package com.iluwatar.business.delegate {
+ class App {
+ + App()
+ + main(args : String[]) {static}
+ }
+ class BusinessDelegate {
+ - lookupService : BusinessLookup
+ + BusinessDelegate()
+ + playbackMovie(movie : String)
+ + setLookupService(lookupService : BusinessLookup)
+ }
+ class BusinessLookup {
+ - netflixService : NetflixService
+ - youTubeService : YouTubeService
+ + BusinessLookup()
+ + getBusinessService(movie : String) : VideoStreamingService
+ + setNetflixService(netflixService : NetflixService)
+ + setYouTubeService(youTubeService : YouTubeService)
+ }
+ class MobileClient {
+ - businessDelegate : BusinessDelegate
+ + MobileClient(businessDelegate : BusinessDelegate)
+ + playbackMovie(movie : String)
+ }
+ class NetflixService {
+ - LOGGER : Logger {static}
+ + NetflixService()
+ + doProcessing()
+ }
+ interface VideoStreamingService {
+ + doProcessing() {abstract}
+ }
+ class YouTubeService {
+ - LOGGER : Logger {static}
+ + YouTubeService()
+ + doProcessing()
+ }
+}
+BusinessLookup --> "-netflixService" NetflixService
+BusinessLookup --> "-youTubeService" YouTubeService
+MobileClient --> "-businessDelegate" BusinessDelegate
+BusinessDelegate --> "-lookupService" BusinessLookup
+NetflixService ..|> VideoStreamingService
+YouTubeService ..|> VideoStreamingService
+@enduml
\ No newline at end of file
diff --git a/business-delegate/index.md b/business-delegate/index.md
deleted file mode 100644
index a55febaf96ac..000000000000
--- a/business-delegate/index.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-layout: pattern
-title: Business Delegate
-folder: business-delegate
-permalink: /patterns/business-delegate/
-categories: Business Tier
-tags: Java
----
-
-**Intent:** The Business Delegate pattern adds an abstraction layer between
-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.
-
-
-
-**Applicability:** Use the Business Delegate pattern when
-
-* you want loose coupling between presentation and business tiers
-* you want to orchestrate calls to multiple business services
-* you want to encapsulate service lookups and service calls
diff --git a/business-delegate/pom.xml b/business-delegate/pom.xml
index d07f2c0aef2a..a5bccb1529e7 100644
--- a/business-delegate/pom.xml
+++ b/business-delegate/pom.xml
@@ -1,24 +1,75 @@
-
-
- 4.0.0
-
- com.iluwatar
- java-design-patterns
- 1.8.0-SNAPSHOT
-
- business-delegate
-
-
- junit
- junit
- test
-
-
- org.mockito
- mockito-core
- test
-
-
+
+
+
+ 4.0.0
+
+ com.iluwatar
+ java-design-patterns
+ 1.26.0-SNAPSHOT
+
+ business-delegate
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.mockito
+ mockito-core
+ 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 371ec3ce4ff8..c23ed42caecd 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,3 +1,27 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.business.delegate;
/**
@@ -5,14 +29,14 @@
* 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.
+ *
+ *
In this example the client ({@link MobileClient}) utilizes a business delegate ( {@link
+ * BusinessDelegate}) to search for movies in video streaming services. The Business Delegate then
+ * selects the appropriate service and makes the service call.
*/
public class App {
@@ -23,18 +47,16 @@ public class App {
*/
public static void main(String[] args) {
- BusinessDelegate businessDelegate = new BusinessDelegate();
- BusinessLookup businessLookup = new BusinessLookup();
- businessLookup.setEjbService(new EjbService());
- businessLookup.setJmsService(new JmsService());
-
+ // prepare the objects
+ var businessDelegate = new BusinessDelegate();
+ var businessLookup = new BusinessLookup();
+ businessLookup.setNetflixService(new NetflixService());
+ businessLookup.setYouTubeService(new YouTubeService());
businessDelegate.setLookupService(businessLookup);
- businessDelegate.setServiceType(ServiceType.EJB);
-
- Client client = new Client(businessDelegate);
- client.doTask();
- businessDelegate.setServiceType(ServiceType.JMS);
- client.doTask();
+ // create the client and use the business delegate
+ var client = new MobileClient(businessDelegate);
+ client.playbackMovie("Die Hard 2");
+ client.playbackMovie("Maradona: The Greatest Ever");
}
}
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 a3c9a00e76ab..81920c857cd8 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,24 +1,39 @@
-package com.iluwatar.business.delegate;
-
-/**
- * BusinessDelegate separates the presentation and business tiers
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.
*/
-public class BusinessDelegate {
+package com.iluwatar.business.delegate;
- private BusinessLookup lookupService;
- private BusinessService businessService;
- private ServiceType serviceType;
+import lombok.Setter;
- public void setLookupService(BusinessLookup businessLookup) {
- this.lookupService = businessLookup;
- }
+/** BusinessDelegate separates the presentation and business tiers. */
+@Setter
+public class BusinessDelegate {
- public void setServiceType(ServiceType serviceType) {
- this.serviceType = serviceType;
- }
+ private BusinessLookup lookupService;
- public void doTask() {
- businessService = lookupService.getBusinessService(serviceType);
- businessService.doProcessing();
- }
+ public void playbackMovie(String movie) {
+ VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie);
+ videoStreamingService.doProcessing();
+ }
}
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 310f7f4339a9..81a1b2f18fd0 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,31 +1,51 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.business.delegate;
-/**
- * Class for performing service lookups.
- */
+import java.util.Locale;
+import lombok.Setter;
+
+/** Class for performing service lookups. */
+@Setter
public class BusinessLookup {
- private EjbService ejbService;
+ private NetflixService netflixService;
- private JmsService jmsService;
+ private YouTubeService youTubeService;
/**
- * @param serviceType Type of service instance to be returned.
+ * Gets service instance based on given movie search string.
+ *
+ * @param movie Search string for the movie.
* @return Service instance.
*/
- public BusinessService getBusinessService(ServiceType serviceType) {
- if (serviceType.equals(ServiceType.EJB)) {
- return ejbService;
+ public VideoStreamingService getBusinessService(String movie) {
+ if (movie.toLowerCase(Locale.ROOT).contains("die hard")) {
+ return netflixService;
} else {
- return jmsService;
+ return youTubeService;
}
}
-
- public void setJmsService(JmsService jmsService) {
- this.jmsService = jmsService;
- }
-
- public void setEjbService(EjbService ejbService) {
- this.ejbService = ejbService;
- }
}
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
deleted file mode 100644
index dfeaf883a1ca..000000000000
--- a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessService.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.iluwatar.business.delegate;
-
-/**
- *
- * Interface for service implementations
- *
- */
-public interface BusinessService {
-
- void doProcessing();
-}
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
deleted file mode 100644
index eab295cf29b5..000000000000
--- a/business-delegate/src/main/java/com/iluwatar/business/delegate/Client.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.iluwatar.business.delegate;
-
-/**
- *
- * Client utilizes BusinessDelegate to call the business tier
- *
- */
-public class Client {
-
- private BusinessDelegate businessDelegate;
-
- public Client(BusinessDelegate businessDelegate) {
- this.businessDelegate = businessDelegate;
- }
-
- public void doTask() {
- businessDelegate.doTask();
- }
-}
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
deleted file mode 100644
index f387449e2041..000000000000
--- a/business-delegate/src/main/java/com/iluwatar/business/delegate/EjbService.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.iluwatar.business.delegate;
-
-/**
- *
- * Service EJB implementation
- *
- */
-public class EjbService implements BusinessService {
-
- @Override
- public void doProcessing() {
- System.out.println("EjbService is now processing");
- }
-}
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
deleted file mode 100644
index fd74cf3c25c8..000000000000
--- a/business-delegate/src/main/java/com/iluwatar/business/delegate/JmsService.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.iluwatar.business.delegate;
-
-/**
- *
- * Service JMS implementation
- *
- */
-public class JmsService implements BusinessService {
-
- @Override
- public void doProcessing() {
- System.out.println("JmsService is now processing");
- }
-}
diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/MobileClient.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/MobileClient.java
new file mode 100644
index 000000000000..01b5b642735b
--- /dev/null
+++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/MobileClient.java
@@ -0,0 +1,39 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.business.delegate;
+
+/** MobileClient utilizes BusinessDelegate to call the business tier. */
+public class MobileClient {
+
+ private final BusinessDelegate businessDelegate;
+
+ public MobileClient(BusinessDelegate businessDelegate) {
+ this.businessDelegate = businessDelegate;
+ }
+
+ public void playbackMovie(String movie) {
+ businessDelegate.playbackMovie(movie);
+ }
+}
diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/NetflixService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/NetflixService.java
new file mode 100644
index 000000000000..696480e678f3
--- /dev/null
+++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/NetflixService.java
@@ -0,0 +1,37 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.business.delegate;
+
+import lombok.extern.slf4j.Slf4j;
+
+/** NetflixService implementation. */
+@Slf4j
+public class NetflixService implements VideoStreamingService {
+
+ @Override
+ public void doProcessing() {
+ LOGGER.info("NetflixService is now processing");
+ }
+}
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
deleted file mode 100644
index ac42d3b6c84c..000000000000
--- a/business-delegate/src/main/java/com/iluwatar/business/delegate/ServiceType.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.iluwatar.business.delegate;
-
-/**
- *
- * Enumeration for service types
- *
- */
-public enum ServiceType {
-
- EJB, JMS;
-}
diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/VideoStreamingService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/VideoStreamingService.java
new file mode 100644
index 000000000000..594b51850efb
--- /dev/null
+++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/VideoStreamingService.java
@@ -0,0 +1,31 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.business.delegate;
+
+/** Interface for video streaming service implementations. */
+public interface VideoStreamingService {
+
+ void doProcessing();
+}
diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/YouTubeService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/YouTubeService.java
new file mode 100644
index 000000000000..65c2e55ff6fa
--- /dev/null
+++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/YouTubeService.java
@@ -0,0 +1,37 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.business.delegate;
+
+import lombok.extern.slf4j.Slf4j;
+
+/** YouTubeService implementation. */
+@Slf4j
+public class YouTubeService implements VideoStreamingService {
+
+ @Override
+ public void doProcessing() {
+ LOGGER.info("YouTubeService is now processing");
+ }
+}
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
new file mode 100644
index 000000000000..5f862bf6e5ff
--- /dev/null
+++ b/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java
@@ -0,0 +1,45 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.business.delegate;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import org.junit.jupiter.api.Test;
+
+/** Tests that Business Delegate example runs without errors. */
+class AppTest {
+
+ /**
+ * Issue: Add at least one assertion to this test case.
+ *
+ *
Solution: Inserted assertion to check whether the execution of the main method in {@link
+ * App} throws an exception.
+ */
+ @Test
+ void shouldExecuteApplicationWithoutException() {
+
+ assertDoesNotThrow(() -> App.main(new String[] {}));
+ }
+}
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 ebacd6b78b99..4b8e0ee77d5c 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,29 +1,43 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.business.delegate;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * The Business Delegate pattern adds an abstraction layer between the presentation and business
- * 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.
- */
-public class BusinessDelegateTest {
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
- private EjbService ejbService;
+/** Tests for the {@link BusinessDelegate} */
+class BusinessDelegateTest {
- private JmsService jmsService;
+ private NetflixService netflixService;
- private BusinessLookup businessLookup;
+ private YouTubeService youTubeService;
private BusinessDelegate businessDelegate;
@@ -31,48 +45,42 @@ public class BusinessDelegateTest {
* This method sets up the instance variables of this test class. It is executed before the
* execution of every test.
*/
- @Before
- public void setup() {
- ejbService = spy(new EjbService());
- jmsService = spy(new JmsService());
+ @BeforeEach
+ void setup() {
+ netflixService = spy(new NetflixService());
+ youTubeService = spy(new YouTubeService());
- businessLookup = spy(new BusinessLookup());
- businessLookup.setEjbService(ejbService);
- businessLookup.setJmsService(jmsService);
+ BusinessLookup businessLookup = spy(new BusinessLookup());
+ businessLookup.setNetflixService(netflixService);
+ businessLookup.setYouTubeService(youTubeService);
businessDelegate = spy(new BusinessDelegate());
businessDelegate.setLookupService(businessLookup);
}
/**
- * In this example the client ({@link Client}) utilizes a business delegate (
- * {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate
+ * In this example the client ({@link MobileClient}) utilizes a business delegate ( {@link
+ * BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate
* service and makes the service call.
*/
@Test
- public void testBusinessDelegate() {
+ void testBusinessDelegate() {
// setup a client object
- Client client = new Client(businessDelegate);
-
- // set the service type
- businessDelegate.setServiceType(ServiceType.EJB);
+ var client = new MobileClient(businessDelegate);
// action
- client.doTask();
-
- // verifying that the businessDelegate was used by client during doTask() method.
- verify(businessDelegate).doTask();
- verify(ejbService).doProcessing();
+ client.playbackMovie("Die hard");
- // set the service type
- businessDelegate.setServiceType(ServiceType.JMS);
+ // verifying that the businessDelegate was used by client during playbackMovie() method.
+ verify(businessDelegate).playbackMovie(anyString());
+ verify(netflixService).doProcessing();
// action
- client.doTask();
+ client.playbackMovie("Maradona");
// verifying that the businessDelegate was used by client during doTask() method.
- verify(businessDelegate, times(2)).doTask();
- verify(jmsService).doProcessing();
+ verify(businessDelegate, times(2)).playbackMovie(anyString());
+ verify(youTubeService).doProcessing();
}
}
diff --git a/bytecode/README.md b/bytecode/README.md
new file mode 100644
index 000000000000..793eecffcc0e
--- /dev/null
+++ b/bytecode/README.md
@@ -0,0 +1,270 @@
+---
+title: "Bytecode Pattern in Java: Interpreting Instructions with Custom Virtual Machines"
+shortTitle: Bytecode
+description: "Explore the Bytecode design pattern in Java, including its implementation, real-world examples, and use cases for efficient virtual machine instruction handling."
+category: Behavioral
+language: en
+tag:
+ - Abstraction
+ - Code simplification
+ - Encapsulation
+ - Game programming
+ - Performance
+ - Runtime
+---
+
+## Intent of Bytecode Design Pattern
+
+The Bytecode design pattern in Java allows encoding behavior as instructions for a virtual machine, making it a powerful tool in game development and other applications.
+
+## Detailed Explanation of Bytecode Pattern with Real-World Examples
+
+Real-world example
+
+> An analogous real-world example of the Bytecode design pattern can be seen in the process of translating a book into multiple languages. Instead of directly translating the book from the original language into every other language, the book is first translated into a common intermediate language, like Esperanto. This intermediate version is easier to translate because it is simpler and more structured. Translators for each target language then translate from Esperanto into their specific languages. This approach ensures consistency, reduces errors, and simplifies the translation process, similar to how bytecode serves as an intermediate representation to optimize and facilitate the execution of high-level programming languages across different platforms.
+
+In plain words
+
+> Bytecode pattern enables behavior driven by data instead of code.
+
+[gameprogrammingpatterns.com](https://gameprogrammingpatterns.com/bytecode.html) documentation states:
+
+> 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.
+
+Sequence diagram
+
+
+
+## Programmatic Example of Bytecode Pattern in Java
+
+In this programmatic example, we show how the Bytecode pattern in Java can simplify the execution of complex virtual machine instructions through a well-defined set of operations. This real-world example demonstrates how the Bytecode design pattern in Java can streamline game programming by allowing wizards' behavior to be easily adjusted through bytecode instructions.
+
+A team is working on a new game where wizards battle against each other. The wizard behavior needs to be carefully adjusted and iterated hundreds of times through playtesting. It's not optimal to ask the programmer to make changes each time the game designer wants to vary the behavior, so the wizard behavior is implemented as a data-driven virtual machine.
+
+One of the most important game objects is the `Wizard` class.
+
+```java
+
+@AllArgsConstructor
+@Setter
+@Getter
+@Slf4j
+public class Wizard {
+
+ private int health;
+ private int agility;
+ private int wisdom;
+ private int numberOfPlayedSounds;
+ private int numberOfSpawnedParticles;
+
+ public void playSound() {
+ LOGGER.info("Playing sound");
+ numberOfPlayedSounds++;
+ }
+
+ public void spawnParticles() {
+ LOGGER.info("Spawning particles");
+ numberOfSpawnedParticles++;
+ }
+}
+```
+
+Next, we show the available instructions for our virtual machine. Each of the instructions has its own semantics on how it operates with the stack data. For example, the ADD instruction takes the top two items from the stack, adds them together and pushes the result to the stack.
+
+```java
+
+@AllArgsConstructor
+@Getter
+public enum Instruction {
+
+ LITERAL(1), // e.g. "LITERAL 0", push 0 to stack
+ SET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set health
+ SET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdom
+ SET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agility
+ PLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play sound
+ SPAWN_PARTICLES(6), // e.g. "SPAWN_PARTICLES", pop value as wizard number, call spawn particles
+ GET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's health
+ GET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agility
+ GET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdom
+ ADD(10), // e.g. "ADD", pop 2 values, push their sum
+ DIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division
+
+ // Other properties and methods...
+}
+```
+
+At the heart of our example is the `VirtualMachine` class. It takes instructions as input and executes them to provide the game object behavior.
+
+```java
+
+@Getter
+@Slf4j
+public class VirtualMachine {
+
+ private final Stack stack = new Stack<>();
+
+ private final Wizard[] wizards = new Wizard[2];
+
+ public VirtualMachine() {
+ wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),
+ 0, 0);
+ wizards[1] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),
+ 0, 0);
+ }
+
+ public VirtualMachine(Wizard wizard1, Wizard wizard2) {
+ wizards[0] = wizard1;
+ wizards[1] = wizard2;
+ }
+
+ 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];
+ // Push the next value to stack
+ 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");
+ }
+ LOGGER.info("Executed " + instruction.name() + ", Stack contains " + getStack());
+ }
+ }
+
+ public void setHealth(int wizard, int amount) {
+ wizards[wizard].setHealth(amount);
+ }
+
+ // Other properties and methods...
+}
+```
+
+Now we can show the full example utilizing the virtual machine.
+
+```java
+public static void main(String[] args) {
+
+ var vm = new VirtualMachine(
+ new Wizard(45, 7, 11, 0, 0),
+ new Wizard(36, 18, 8, 0, 0));
+
+ vm.execute(InstructionConverterUtil.convertToByteCode(LITERAL_0));
+ vm.execute(InstructionConverterUtil.convertToByteCode(LITERAL_0));
+ vm.execute(InstructionConverterUtil.convertToByteCode(String.format(HEALTH_PATTERN, "GET")));
+ vm.execute(InstructionConverterUtil.convertToByteCode(LITERAL_0));
+ vm.execute(InstructionConverterUtil.convertToByteCode(GET_AGILITY));
+ vm.execute(InstructionConverterUtil.convertToByteCode(LITERAL_0));
+ vm.execute(InstructionConverterUtil.convertToByteCode(GET_WISDOM));
+ vm.execute(InstructionConverterUtil.convertToByteCode(ADD));
+ vm.execute(InstructionConverterUtil.convertToByteCode(LITERAL_2));
+ vm.execute(InstructionConverterUtil.convertToByteCode(DIVIDE));
+ vm.execute(InstructionConverterUtil.convertToByteCode(ADD));
+ vm.execute(InstructionConverterUtil.convertToByteCode(String.format(HEALTH_PATTERN, "SET")));
+}
+```
+
+Here is the console output.
+
+```
+16:20:10.193 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0]
+16:20:10.196 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 0]
+16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_HEALTH, Stack contains [0, 45]
+16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 0]
+16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_AGILITY, Stack contains [0, 45, 7]
+16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 7, 0]
+16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_WISDOM, Stack contains [0, 45, 7, 11]
+16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 45, 18]
+16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 18, 2]
+16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed DIVIDE, Stack contains [0, 45, 9]
+16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 54]
+16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed SET_HEALTH, Stack contains []
+```
+
+Utilizing the Bytecode design pattern in Java can significantly enhance the flexibility and maintainability of your virtual machine-based applications.
+
+## When to Use the Bytecode Pattern in Java
+
+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.
+
+## Real-World Applications of Bytecode Pattern in Java
+
+* Java Virtual Machine (JVM) uses bytecode to allow Java programs to run on any device that has JVM installed
+* Python compiles its scripts to bytecode which is then interpreted by Python Virtual Machine
+* The .NET Framework uses a form of bytecode called Microsoft Intermediate Language (MSIL)
+
+## Benefits and Trade-offs of Bytecode Pattern
+
+Benefits:
+
+* Portability: Programs can run on any platform that has a compatible VM.
+* Security: The VM can enforce security checks on the bytecode.
+* Performance: JIT compilers can optimize bytecode at runtime, potentially improving performance over interpreted code.
+
+Trade-offs:
+
+* Overhead: Running bytecode typically involves more overhead than running native code, potentially affecting performance.
+* Complexity: Implementing and maintaining a VM adds complexity to the system.
+
+## Related Java Design Patterns
+
+* [Interpreter](https://java-design-patterns.com/patterns/interpreter/) is often used within the implementation of VMs to interpret bytecode instructions
+* [Command](https://java-design-patterns.com/patterns/command/): Bytecode instructions can be seen as commands executed by the VM.
+* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): VMs may use factory methods to instantiate operations or instructions defined in the bytecode.
+
+## References and Credits
+
+* [Game Programming Patterns](https://amzn.to/3K96fOn)
+* [Programming Language Pragmatics](https://amzn.to/49Tusnn)
+* [Bytecode (Game Programming Patterns)](http://gameprogrammingpatterns.com/bytecode.html)
diff --git a/bytecode/etc/bytecode-sequence-diagram.png b/bytecode/etc/bytecode-sequence-diagram.png
new file mode 100644
index 000000000000..c863a0e0b5cd
Binary files /dev/null and b/bytecode/etc/bytecode-sequence-diagram.png differ
diff --git a/bytecode/etc/bytecode.urm.png b/bytecode/etc/bytecode.urm.png
new file mode 100644
index 000000000000..51335fa0a4b4
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..224e909efa15
--- /dev/null
+++ b/bytecode/etc/bytecode.urm.puml
@@ -0,0 +1,73 @@
+@startuml
+package com.iluwatar.bytecode {
+ class App {
+ - LOGGER : Logger {static}
+ + App()
+ + 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}
+ - intValue : int
+ + getInstruction(value : int) : Instruction {static}
+ + getIntValue() : int
+ + valueOf(name : String) : Instruction {static}
+ + values() : Instruction[] {static}
+ }
+ class VirtualMachine {
+ - LOGGER : Logger {static}
+ - stack : Stack
+ - wizards : Wizard[]
+ + VirtualMachine()
+ + VirtualMachine(wizard1 : Wizard, wizard2 : Wizard)
+ + execute(bytecode : int[])
+ + getAgility(wizard : int) : int
+ + getHealth(wizard : int) : int
+ + getStack() : Stack
+ + getWisdom(wizard : int) : int
+ + getWizards() : Wizard[]
+ - randomInt(min : int, max : int) : int
+ + 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(health : int, agility : int, wisdom : int, numberOfPlayedSounds : int, numberOfSpawnedParticles : int)
+ + getAgility() : int
+ + getHealth() : int
+ + getNumberOfPlayedSounds() : int
+ + getNumberOfSpawnedParticles() : int
+ + getWisdom() : int
+ + playSound()
+ + setAgility(agility : int)
+ + setHealth(health : int)
+ + setNumberOfPlayedSounds(numberOfPlayedSounds : int)
+ + setNumberOfSpawnedParticles(numberOfSpawnedParticles : 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..2fb01fe2f914
--- /dev/null
+++ b/bytecode/pom.xml
@@ -0,0 +1,70 @@
+
+
+
+
+ java-design-patterns
+ com.iluwatar
+ 1.26.0-SNAPSHOT
+
+ 4.0.0
+ bytecode
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+
+
+ com.iluwatar.bytecode.App
+
+
+
+
+
+
+
+
+
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..9293b5876ef5
--- /dev/null
+++ b/bytecode/src/main/java/com/iluwatar/bytecode/App.java
@@ -0,0 +1,76 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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 lombok.extern.slf4j.Slf4j;
+
+/**
+ * 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.
+ */
+@Slf4j
+public class App {
+
+ private static final String LITERAL_0 = "LITERAL 0";
+ private static final String HEALTH_PATTERN = "%s_HEALTH";
+ private static final String GET_AGILITY = "GET_AGILITY";
+ private static final String GET_WISDOM = "GET_WISDOM";
+ private static final String ADD = "ADD";
+ private static final String LITERAL_2 = "LITERAL 2";
+ private static final String DIVIDE = "DIVIDE";
+
+ /**
+ * Main app method.
+ *
+ * @param args command line args
+ */
+ public static void main(String[] args) {
+
+ var vm = new VirtualMachine(new Wizard(45, 7, 11, 0, 0), new Wizard(36, 18, 8, 0, 0));
+
+ vm.execute(InstructionConverterUtil.convertToByteCode(LITERAL_0));
+ vm.execute(InstructionConverterUtil.convertToByteCode(LITERAL_0));
+ vm.execute(InstructionConverterUtil.convertToByteCode(String.format(HEALTH_PATTERN, "GET")));
+ vm.execute(InstructionConverterUtil.convertToByteCode(LITERAL_0));
+ vm.execute(InstructionConverterUtil.convertToByteCode(GET_AGILITY));
+ vm.execute(InstructionConverterUtil.convertToByteCode(LITERAL_0));
+ vm.execute(InstructionConverterUtil.convertToByteCode(GET_WISDOM));
+ vm.execute(InstructionConverterUtil.convertToByteCode(ADD));
+ vm.execute(InstructionConverterUtil.convertToByteCode(LITERAL_2));
+ vm.execute(InstructionConverterUtil.convertToByteCode(DIVIDE));
+ vm.execute(InstructionConverterUtil.convertToByteCode(ADD));
+ vm.execute(InstructionConverterUtil.convertToByteCode(String.format(HEALTH_PATTERN, "SET")));
+ }
+}
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..25330e73fd78
--- /dev/null
+++ b/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java
@@ -0,0 +1,62 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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 lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/** Representation of instructions understandable by virtual machine. */
+@AllArgsConstructor
+@Getter
+public enum Instruction {
+ LITERAL(1), // e.g. "LITERAL 0", push 0 to stack
+ SET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set health
+ SET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdom
+ SET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agility
+ PLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play sound
+ SPAWN_PARTICLES(6), // e.g. "SPAWN_PARTICLES", pop value as wizard number, call spawn particles
+ GET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's health
+ GET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agility
+ GET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdom
+ ADD(10), // e.g. "ADD", pop 2 values, push their sum
+ DIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division
+
+ private final int intValue;
+
+ /**
+ * 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..7f835d402f15
--- /dev/null
+++ b/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java
@@ -0,0 +1,147 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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;
+import java.util.concurrent.ThreadLocalRandom;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+/** Implementation of virtual machine. */
+@Getter
+@Slf4j
+public class VirtualMachine {
+
+ private final Stack stack = new Stack<>();
+
+ private final Wizard[] wizards = new Wizard[2];
+
+ /** No-args constructor. */
+ public VirtualMachine() {
+ wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32), 0, 0);
+ wizards[1] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32), 0, 0);
+ }
+
+ /** Constructor taking the wizards as arguments. */
+ public VirtualMachine(Wizard wizard1, Wizard wizard2) {
+ wizards[0] = wizard1;
+ wizards[1] = wizard2;
+ }
+
+ /**
+ * 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];
+ // Push the next value to stack
+ stack.push(value);
+ }
+ case SET_AGILITY -> {
+ var amount = stack.pop();
+ var wizard = stack.pop();
+ setAgility(wizard, amount);
+ }
+ case SET_WISDOM -> {
+ var amount = stack.pop();
+ var wizard = stack.pop();
+ setWisdom(wizard, amount);
+ }
+ case SET_HEALTH -> {
+ var amount = stack.pop();
+ var wizard = stack.pop();
+ setHealth(wizard, amount);
+ }
+ case GET_HEALTH -> {
+ var wizard = stack.pop();
+ stack.push(getHealth(wizard));
+ }
+ case GET_AGILITY -> {
+ var wizard = stack.pop();
+ stack.push(getAgility(wizard));
+ }
+ case GET_WISDOM -> {
+ var wizard = stack.pop();
+ stack.push(getWisdom(wizard));
+ }
+ case ADD -> {
+ var a = stack.pop();
+ var b = stack.pop();
+ stack.push(a + b);
+ }
+ case DIVIDE -> {
+ var a = stack.pop();
+ var b = stack.pop();
+ stack.push(b / a);
+ }
+ case PLAY_SOUND -> {
+ var wizard = stack.pop();
+ getWizards()[wizard].playSound();
+ }
+ case SPAWN_PARTICLES -> {
+ var wizard = stack.pop();
+ getWizards()[wizard].spawnParticles();
+ }
+ default -> {
+ throw new IllegalArgumentException("Invalid instruction value");
+ }
+ }
+ LOGGER.info("Executed " + instruction.name() + ", Stack contains " + getStack());
+ }
+ }
+
+ 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();
+ }
+
+ private int randomInt(int min, int max) {
+ return ThreadLocalRandom.current().nextInt(min, max + 1);
+ }
+}
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..8b8bfac9f98b
--- /dev/null
+++ b/bytecode/src/main/java/com/iluwatar/bytecode/Wizard.java
@@ -0,0 +1,57 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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 lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * This class represent game objects which properties can be changed by instructions interpreted by
+ * virtual machine.
+ */
+@AllArgsConstructor
+@Setter
+@Getter
+@Slf4j
+public class Wizard {
+
+ private int health;
+ private int agility;
+ private int wisdom;
+ private int numberOfPlayedSounds;
+ private int numberOfSpawnedParticles;
+
+ public void playSound() {
+ LOGGER.info("Playing sound");
+ numberOfPlayedSounds++;
+ }
+
+ public void spawnParticles() {
+ LOGGER.info("Spawning particles");
+ 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..d45a2aa55787
--- /dev/null
+++ b/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java
@@ -0,0 +1,75 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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..72d00eb34fb3
--- /dev/null
+++ b/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java
@@ -0,0 +1,44 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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 static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import org.junit.jupiter.api.Test;
+
+/** Application test */
+class AppTest {
+
+ /**
+ * Issue: Add at least one assertion to this test case.
+ *
+ * Solution: Inserted assertion to check whether the execution of the main method in {@link
+ * App} throws an exception.
+ */
+ @Test
+ void shouldExecuteApplicationWithoutException() {
+ assertDoesNotThrow(() -> 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..1d9a5539f51b
--- /dev/null
+++ b/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java
@@ -0,0 +1,154 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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 static com.iluwatar.bytecode.Instruction.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+/** Test for {@link VirtualMachine} */
+class VirtualMachineTest {
+
+ @Test
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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..9dadba1eaf21
--- /dev/null
+++ b/bytecode/src/test/java/com/iluwatar/bytecode/util/InstructionConverterUtilTest.java
@@ -0,0 +1,63 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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 org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/** Test for {@link InstructionConverterUtil} */
+class InstructionConverterUtilTest {
+
+ @Test
+ void testEmptyInstruction() {
+ var instruction = "";
+
+ var bytecode = InstructionConverterUtil.convertToByteCode(instruction);
+
+ Assertions.assertEquals(0, bytecode.length);
+ }
+
+ @Test
+ 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
new file mode 100644
index 000000000000..2e9c3761ef8d
--- /dev/null
+++ b/caching/README.md
@@ -0,0 +1,478 @@
+---
+title: "Caching Pattern in Java: Accelerating Data Access Speeds"
+shortTitle: Caching
+description: "Learn how to optimize performance with the Java Caching Design Pattern. Explore various caching strategies, real-world examples, and implementation techniques for efficient resource management."
+category: Performance optimization
+language: en
+tag:
+ - Caching
+ - Data access
+ - Performance
+ - Resource management
+---
+
+## Also known as
+
+* Cache
+* Temporary Storage
+
+## Intent of Caching Design Pattern
+
+The Java Caching Design Pattern is crucial for performance optimization and resource management. It involves various caching strategies such as write-through, read-through, and LRU cache to ensure efficient data access. The caching pattern avoids expensive re-acquisition of resources by not releasing them immediately after use. The resources retain their identity, are kept in some fast-access storage, and are re-used to avoid having to acquire them again.
+
+## Detailed Explanation of Caching Pattern with Real-World Examples
+
+Real-world example
+
+> A real-world example of the Caching Design Pattern in Java is a library's catalog system. By caching frequently searched book results, the system reduces database load and enhances performance. When patrons frequently search for popular books, the system can cache the results of these searches. Instead of querying the database every time a user searches for a popular book, the system quickly retrieves the results from the cache. This reduces the load on the database and provides faster response times for users, enhancing their overall experience. However, the system must also ensure that the cache is updated when new books are added or existing ones are checked out, to maintain accurate information.
+
+In plain words
+
+> Caching pattern keeps frequently needed data in fast-access storage to improve performance.
+
+Wikipedia says
+
+> In computing, a cache is a hardware or software component that stores data so that future requests for that data can be served faster; the data stored in a cache might be the result of an earlier computation or a copy of data stored elsewhere. A cache hit occurs when the requested data can be found in a cache, while a cache miss occurs when it cannot. Cache hits are served by reading data from the cache, which is faster than recomputing a result or reading from a slower data store; thus, the more requests that can be served from the cache, the faster the system performs.
+
+Flowchart
+
+
+
+## Programmatic Example of Caching Pattern in Java
+
+In this programmatic example, we demonstrate different Java caching strategies, including write-through, write-around, and write-behind, using a user account management system.
+
+A team is working on a website that provides new homes for abandoned cats. People can post their cats on the website after registering, but all the new posts require approval from one of the site moderators. The user accounts of the site moderators contain a specific flag and the data is stored in a MongoDB database. Checking for the moderator flag each time a post is viewed becomes expensive, and it's a good idea to utilize caching here.
+
+Let's first look at the data layer of our application. The interesting classes are `UserAccount` which is a simple Java object containing the user account details, and `DbManager` interface which handles reading and writing of these objects to/from database.
+
+```java
+
+@Data
+@AllArgsConstructor
+@ToString
+@EqualsAndHashCode
+public class UserAccount {
+ private String userId;
+ private String userName;
+ private String additionalInfo;
+}
+
+public interface DbManager {
+
+ void connect();
+
+ void disconnect();
+
+ UserAccount readFromDb(String userId);
+
+ UserAccount writeToDb(UserAccount userAccount);
+
+ UserAccount updateDb(UserAccount userAccount);
+
+ UserAccount upsertDb(UserAccount userAccount);
+}
+```
+
+In the example, we are demonstrating various different caching policies. The following caching strategies are implemented in Java: Write-through, Write-around, Write-behind, and Cache-aside. Each strategy offers unique benefits for improving performance and reducing load on the database.
+
+* Write-through writes data to the cache and DB in a single transaction
+* Write-around writes data immediately into the DB instead of the cache
+* Write-behind writes data into the cache initially whilst the data is only written into the DB when the cache is full
+* Cache-aside pushes the responsibility of keeping the data synchronized in both data sources to the application itself
+* Read-through strategy is also included in the aforementioned strategies, and it returns data from the cache to the caller if it exists, otherwise queries from DB and stores it into the cache for future use.
+
+The cache implementation in `LruCache` is a hash table accompanied by a doubly linked-list. The linked-list helps in capturing and maintaining the LRU data in the cache. When data is queried (from the cache), added (to the cache), or updated, the data is moved to the front of the list to depict itself as the most-recently-used data. The LRU data is always at the end of the list.
+
+```java
+
+@Slf4j
+public class LruCache {
+
+ static class Node {
+ String userId;
+ UserAccount userAccount;
+ Node previous;
+ Node next;
+
+ public Node(String userId, UserAccount userAccount) {
+ this.userId = userId;
+ this.userAccount = userAccount;
+ }
+ }
+
+ // Other properties and methods...
+
+ public LruCache(int capacity) {
+ this.capacity = capacity;
+ }
+
+ public UserAccount get(String userId) {
+ if (cache.containsKey(userId)) {
+ var node = cache.get(userId);
+ remove(node);
+ setHead(node);
+ return node.userAccount;
+ }
+ return null;
+ }
+
+ public void set(String userId, UserAccount userAccount) {
+ if (cache.containsKey(userId)) {
+ var old = cache.get(userId);
+ old.userAccount = userAccount;
+ remove(old);
+ setHead(old);
+ } else {
+ var newNode = new Node(userId, userAccount);
+ if (cache.size() >= capacity) {
+ LOGGER.info("# Cache is FULL! Removing {} from cache...", end.userId);
+ cache.remove(end.userId); // remove LRU data from cache.
+ remove(end);
+ setHead(newNode);
+ } else {
+ setHead(newNode);
+ }
+ cache.put(userId, newNode);
+ }
+ }
+
+ public boolean contains(String userId) {
+ return cache.containsKey(userId);
+ }
+
+ public void remove(Node node) { /* ... */ }
+
+ public void setHead(Node node) { /* ... */ }
+
+ public void invalidate(String userId) { /* ... */ }
+
+ public boolean isFull() { /* ... */ }
+
+ public UserAccount getLruData() { /* ... */ }
+
+ public void clear() { /* ... */ }
+
+ public List getCacheDataInListForm() { /* ... */ }
+
+ public void setCapacity(int newCapacity) { /* ... */ }
+}
+```
+
+The next layer we are going to look at is `CacheStore` which implements the different caching strategies.
+
+```java
+
+@Slf4j
+public class CacheStore {
+
+ private static final int CAPACITY = 3;
+ private static LruCache cache;
+ private final DbManager dbManager;
+
+ // Other properties and methods...
+
+ public UserAccount readThrough(final String userId) {
+ if (cache.contains(userId)) {
+ LOGGER.info("# Found in Cache!");
+ return cache.get(userId);
+ }
+ LOGGER.info("# Not found in cache! Go to DB!!");
+ UserAccount userAccount = dbManager.readFromDb(userId);
+ cache.set(userId, userAccount);
+ return userAccount;
+ }
+
+ public void writeThrough(final UserAccount userAccount) {
+ if (cache.contains(userAccount.getUserId())) {
+ dbManager.updateDb(userAccount);
+ } else {
+ dbManager.writeToDb(userAccount);
+ }
+ cache.set(userAccount.getUserId(), userAccount);
+ }
+
+ public void writeAround(final UserAccount userAccount) {
+ if (cache.contains(userAccount.getUserId())) {
+ dbManager.updateDb(userAccount);
+ // Cache data has been updated -- remove older
+ cache.invalidate(userAccount.getUserId());
+ // version from cache.
+ } else {
+ dbManager.writeToDb(userAccount);
+ }
+ }
+
+ public static void clearCache() {
+ if (cache != null) {
+ cache.clear();
+ }
+ }
+
+ public static void flushCache() {
+ LOGGER.info("# flushCache...");
+ Optional.ofNullable(cache)
+ .map(LruCache::getCacheDataInListForm)
+ .orElse(List.of())
+ .forEach(DbManager::updateDb);
+ }
+
+ // ... omitted the implementation of other caching strategies ...
+
+}
+```
+
+`AppManager` helps to bridge the gap in communication between the main class and the application's back-end. DB connection is initialized through this class. The chosen caching strategy/policy is also initialized here. Before the cache can be used, the size of the cache has to be set. Depending on the chosen caching policy, `AppManager` will call the appropriate function in the `CacheStore` class.
+
+```java
+
+@Slf4j
+public final class AppManager {
+
+ private static CachingPolicy cachingPolicy;
+ private final DbManager dbManager;
+ private final CacheStore cacheStore;
+
+ private AppManager() {
+ }
+
+ public void initDb() { /* ... */ }
+
+ public static void initCachingPolicy(CachingPolicy policy) { /* ... */ }
+
+ public static void initCacheCapacity(int capacity) { /* ... */ }
+
+ public UserAccount find(final String userId) {
+ LOGGER.info("Trying to find {} in cache", userId);
+ if (cachingPolicy == CachingPolicy.THROUGH
+ || cachingPolicy == CachingPolicy.AROUND) {
+ return cacheStore.readThrough(userId);
+ } else if (cachingPolicy == CachingPolicy.BEHIND) {
+ return cacheStore.readThroughWithWriteBackPolicy(userId);
+ } else if (cachingPolicy == CachingPolicy.ASIDE) {
+ return findAside(userId);
+ }
+ return null;
+ }
+
+ public void save(final UserAccount userAccount) {
+ LOGGER.info("Save record!");
+ if (cachingPolicy == CachingPolicy.THROUGH) {
+ cacheStore.writeThrough(userAccount);
+ } else if (cachingPolicy == CachingPolicy.AROUND) {
+ cacheStore.writeAround(userAccount);
+ } else if (cachingPolicy == CachingPolicy.BEHIND) {
+ cacheStore.writeBehind(userAccount);
+ } else if (cachingPolicy == CachingPolicy.ASIDE) {
+ saveAside(userAccount);
+ }
+ }
+
+ public static String printCacheContent() {
+ return CacheStore.print();
+ }
+
+ // Other properties and methods...
+}
+```
+
+Here is what we do in the main class of the application.
+
+```java
+
+@Slf4j
+public class App {
+
+ public static void main(final String[] args) {
+ boolean isDbMongo = isDbMongo(args);
+ if (isDbMongo) {
+ LOGGER.info("Using the Mongo database engine to run the application.");
+ } else {
+ LOGGER.info("Using the 'in Memory' database to run the application.");
+ }
+ App app = new App(isDbMongo);
+ app.useReadAndWriteThroughStrategy();
+ String splitLine = "==============================================";
+ LOGGER.info(splitLine);
+ app.useReadThroughAndWriteAroundStrategy();
+ LOGGER.info(splitLine);
+ app.useReadThroughAndWriteBehindStrategy();
+ LOGGER.info(splitLine);
+ app.useCacheAsideStategy();
+ LOGGER.info(splitLine);
+ }
+
+ public void useReadAndWriteThroughStrategy() {
+ LOGGER.info("# CachingPolicy.THROUGH");
+ appManager.initCachingPolicy(CachingPolicy.THROUGH);
+
+ var userAccount1 = new UserAccount("001", "John", "He is a boy.");
+
+ appManager.save(userAccount1);
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("001");
+ appManager.find("001");
+ }
+
+ public void useReadThroughAndWriteAroundStrategy() { /* ... */ }
+
+ public void useReadThroughAndWriteBehindStrategy() { /* ... */ }
+
+ public void useCacheAsideStrategy() { /* ... */ }
+}
+```
+
+The program output:
+
+```
+17:00:56.302 [main] INFO com.iluwatar.caching.App -- Using the 'in Memory' database to run the application.
+17:00:56.304 [main] INFO com.iluwatar.caching.App -- # CachingPolicy.THROUGH
+17:00:56.305 [main] INFO com.iluwatar.caching.AppManager -- Save record!
+17:00:56.308 [main] INFO com.iluwatar.caching.App --
+--CACHE CONTENT--
+UserAccount(userId=001, userName=John, additionalInfo=He is a boy.)
+----
+17:00:56.308 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 001 in cache
+17:00:56.309 [main] INFO com.iluwatar.caching.CacheStore -- # Found in Cache!
+17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 001 in cache
+17:00:56.309 [main] INFO com.iluwatar.caching.CacheStore -- # Found in Cache!
+17:00:56.309 [main] INFO com.iluwatar.caching.App -- ==============================================
+17:00:56.309 [main] INFO com.iluwatar.caching.App -- # CachingPolicy.AROUND
+17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Save record!
+17:00:56.309 [main] INFO com.iluwatar.caching.App --
+--CACHE CONTENT--
+----
+17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 002 in cache
+17:00:56.309 [main] INFO com.iluwatar.caching.CacheStore -- # Not found in cache! Go to DB!!
+17:00:56.309 [main] INFO com.iluwatar.caching.App --
+--CACHE CONTENT--
+UserAccount(userId=002, userName=Jane, additionalInfo=She is a girl.)
+----
+17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 002 in cache
+17:00:56.309 [main] INFO com.iluwatar.caching.CacheStore -- # Found in Cache!
+17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Save record!
+17:00:56.309 [main] INFO com.iluwatar.caching.LruCache -- # 002 has been updated! Removing older version from cache...
+17:00:56.309 [main] INFO com.iluwatar.caching.App --
+--CACHE CONTENT--
+----
+17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 002 in cache
+17:00:56.309 [main] INFO com.iluwatar.caching.CacheStore -- # Not found in cache! Go to DB!!
+17:00:56.309 [main] INFO com.iluwatar.caching.App --
+--CACHE CONTENT--
+UserAccount(userId=002, userName=Jane G., additionalInfo=She is a girl.)
+----
+17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 002 in cache
+17:00:56.309 [main] INFO com.iluwatar.caching.CacheStore -- # Found in Cache!
+17:00:56.309 [main] INFO com.iluwatar.caching.App -- ==============================================
+17:00:56.309 [main] INFO com.iluwatar.caching.App -- # CachingPolicy.BEHIND
+17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Save record!
+17:00:56.309 [main] INFO com.iluwatar.caching.AppManager -- Save record!
+17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Save record!
+17:00:56.310 [main] INFO com.iluwatar.caching.App --
+--CACHE CONTENT--
+UserAccount(userId=005, userName=Isaac, additionalInfo=He is allergic to mustard.)
+UserAccount(userId=004, userName=Rita, additionalInfo=She hates cats.)
+UserAccount(userId=003, userName=Adam, additionalInfo=He likes food.)
+----
+17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 003 in cache
+17:00:56.310 [main] INFO com.iluwatar.caching.CacheStore -- # Found in cache!
+17:00:56.310 [main] INFO com.iluwatar.caching.App --
+--CACHE CONTENT--
+UserAccount(userId=003, userName=Adam, additionalInfo=He likes food.)
+UserAccount(userId=005, userName=Isaac, additionalInfo=He is allergic to mustard.)
+UserAccount(userId=004, userName=Rita, additionalInfo=She hates cats.)
+----
+17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Save record!
+17:00:56.310 [main] INFO com.iluwatar.caching.CacheStore -- # Cache is FULL! Writing LRU data to DB...
+17:00:56.310 [main] INFO com.iluwatar.caching.LruCache -- # Cache is FULL! Removing 004 from cache...
+17:00:56.310 [main] INFO com.iluwatar.caching.App --
+--CACHE CONTENT--
+UserAccount(userId=006, userName=Yasha, additionalInfo=She is an only child.)
+UserAccount(userId=003, userName=Adam, additionalInfo=He likes food.)
+UserAccount(userId=005, userName=Isaac, additionalInfo=He is allergic to mustard.)
+----
+17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 004 in cache
+17:00:56.310 [main] INFO com.iluwatar.caching.CacheStore -- # Not found in Cache!
+17:00:56.310 [main] INFO com.iluwatar.caching.CacheStore -- # Cache is FULL! Writing LRU data to DB...
+17:00:56.310 [main] INFO com.iluwatar.caching.LruCache -- # Cache is FULL! Removing 005 from cache...
+17:00:56.310 [main] INFO com.iluwatar.caching.App --
+--CACHE CONTENT--
+UserAccount(userId=004, userName=Rita, additionalInfo=She hates cats.)
+UserAccount(userId=006, userName=Yasha, additionalInfo=She is an only child.)
+UserAccount(userId=003, userName=Adam, additionalInfo=He likes food.)
+----
+17:00:56.310 [main] INFO com.iluwatar.caching.App -- ==============================================
+17:00:56.310 [main] INFO com.iluwatar.caching.App -- # CachingPolicy.ASIDE
+17:00:56.310 [main] INFO com.iluwatar.caching.App --
+--CACHE CONTENT--
+----
+17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Save record!
+17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Save record!
+17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Save record!
+17:00:56.310 [main] INFO com.iluwatar.caching.App --
+--CACHE CONTENT--
+----
+17:00:56.310 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 003 in cache
+17:00:56.313 [main] INFO com.iluwatar.caching.App --
+--CACHE CONTENT--
+UserAccount(userId=003, userName=Adam, additionalInfo=He likes food.)
+----
+17:00:56.313 [main] INFO com.iluwatar.caching.AppManager -- Trying to find 004 in cache
+17:00:56.313 [main] INFO com.iluwatar.caching.App --
+--CACHE CONTENT--
+UserAccount(userId=004, userName=Rita, additionalInfo=She hates cats.)
+UserAccount(userId=003, userName=Adam, additionalInfo=He likes food.)
+----
+17:00:56.313 [main] INFO com.iluwatar.caching.App -- ==============================================
+17:00:56.314 [Thread-0] INFO com.iluwatar.caching.CacheStore -- # flushCache...
+```
+
+Implementing the Java Caching Design Pattern using various strategies like LRU cache and write-through caching significantly enhances application performance and scalability.
+
+## When to Use the Caching Pattern in Java
+
+Use the Caching pattern when
+
+* Repetitious acquisition, initialization, and release of the same resource cause unnecessary performance overhead
+* In scenarios where the cost of recomputing or re-fetching data is significantly higher than storing and retrieving it from cache
+* For read-heavy applications with relatively static data or data that changes infrequently
+
+## Real-World Applications of Caching Pattern in Java
+
+* Web page caching to reduce server load and improve response time
+* Database query caching to avoid repeated expensive SQL queries
+* Caching results of CPU-intensive computations
+* Content Delivery Networks (CDNs) for caching static resources like images, CSS, and JavaScript files closer to the end users
+
+## Benefits and Trade-offs of Caching Pattern
+
+Benefits:
+
+* Improved Performance: Significantly reduces data access latency, leading to faster application performance
+* Reduced Load: Decreases the load on the underlying data source, which can lead to cost savings and increased longevity of the resource
+* Scalability: Enhances the scalability of applications by efficiently handling increases in load without proportional increases in resource utilization
+
+Trade-Offs:
+
+* Complexity: Introduces complexity in terms of cache invalidation, consistency, and synchronization
+* Resource Utilization: Requires additional memory or storage resources to maintain the cache
+* Stale Data: There's a risk of serving outdated data if the cache is not properly invalidated or updated when the underlying data changes
+
+## Related Java Design Patterns
+
+* [Proxy](https://java-design-patterns.com/patterns/proxy/): Caching can be implemented using the Proxy pattern, where the proxy object intercepts requests and returns cached data if available
+* [Observer](https://java-design-patterns.com/patterns/observer/): Can be used to notify the cache when the underlying data changes, so that it can be updated or invalidated accordingly
+* [Decorator](https://java-design-patterns.com/patterns/decorator/): Can be used to add caching behavior to an existing object without modifying its code
+* [Strategy](https://java-design-patterns.com/patterns/strategy/): Different caching strategies can be implemented using the Strategy pattern, allowing the application to switch between them at runtime
+
+## References and Credits
+
+* [Effective Java](https://amzn.to/4cGk2Jz)
+* [High Performance Browser Networking](https://amzn.to/3TiNNY4)
+* [Java EE 8 High Performance](https://amzn.to/44T8vmH)
+* [Java Performance: In-Depth Advice for Tuning and Programming Java 8, 11, and Beyond](https://amzn.to/3yyD58W)
+* [Java Performance: The Definitive Guide: Getting the Most Out of Your Code](https://amzn.to/3Wu5neF)
+* [Patterns of Enterprise Application Architecture](https://amzn.to/3PMAHRZ)
+* [Scalable Internet Architectures](https://amzn.to/48V3ni9)
+* [Write-through, write-around, write-back: Cache explained (ComputerWeekly)](http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained)
+* [Cache-Aside Pattern (Microsoft)](https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside)
diff --git a/caching/docker-compose.yml b/caching/docker-compose.yml
new file mode 100644
index 000000000000..9643d91cf3b7
--- /dev/null
+++ b/caching/docker-compose.yml
@@ -0,0 +1,36 @@
+#
+# This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+#
+# The MIT License
+# Copyright © 2014-2022 Ilkka Seppälä
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to 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.
+#
+
+version: '3.7'
+services:
+ mongodb_container:
+ image: mongo:latest
+ environment:
+ MONGO_INITDB_ROOT_USERNAME: root
+ MONGO_INITDB_ROOT_PASSWORD: rootpassword
+ ports:
+ - '27017:27017'
+ volumes:
+ - ./mongo-data/:/data/db
diff --git a/caching/etc/caching-flowchart.png b/caching/etc/caching-flowchart.png
new file mode 100644
index 000000000000..8068392cd9bd
Binary files /dev/null and b/caching/etc/caching-flowchart.png differ
diff --git a/caching/etc/caching.png b/caching/etc/caching.png
index 6b3b2d0556c5..5be2dc0ea7f8 100644
Binary files a/caching/etc/caching.png and b/caching/etc/caching.png differ
diff --git a/caching/etc/caching.ucls b/caching/etc/caching.ucls
index 815a62ec99ce..a058277ea2fd 100644
--- a/caching/etc/caching.ucls
+++ b/caching/etc/caching.ucls
@@ -1,106 +1,90 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/caching/etc/caching.urm.puml b/caching/etc/caching.urm.puml
new file mode 100644
index 000000000000..f6f2e4732005
--- /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}
+ + useCacheAsideStrategy()
+ + 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
diff --git a/caching/index.md b/caching/index.md
deleted file mode 100644
index f79f13e42a6f..000000000000
--- a/caching/index.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-layout: pattern
-title: Caching
-folder: caching
-permalink: /patterns/caching/
-categories: Other
-tags:
- - Java
----
-
-**Intent:** 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.
-
-
-
-**Applicability:** Use the Caching pattern(s) when
-
-* Repetitious acquisition, initialization, and release of the same resource causes unnecessary performance overhead.
-
-**Credits**
-
-* [Write-through, write-around, write-back: Cache explained](http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained)
-* [Read-Through, Write-Through, Write-Behind, and Refresh-Ahead Caching](https://docs.oracle.com/cd/E15357_01/coh.360/e15723/cache_rtwtwbra.htm#COHDG5177)
diff --git a/caching/pom.xml b/caching/pom.xml
index e61b8ab8cbcc..3ffce74af493 100644
--- a/caching/pom.xml
+++ b/caching/pom.xml
@@ -1,33 +1,64 @@
-
-
+
+
+
4.0.0
com.iluwatar
java-design-patterns
- 1.8.0-SNAPSHOT
+ 1.26.0-SNAPSHOT
caching
- junit
- junit
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
test
- org.mongodb
- mongodb-driver
- 3.0.4
+ org.mockito
+ mockito-core
+ test
org.mongodb
- mongodb-driver-core
- 3.0.4
+ bson
org.mongodb
- bson
- 3.0.4
+ mongodb-driver-legacy
AppManager --> CacheStore/LRUCache/CachingPolicy --> DBManager
- *
+ * {@literal App --> AppManager --> CacheStore/LRUCache/CachingPolicy --> DBManager}
+ *
+ *
There are 2 ways to launch the application. - to use "in Memory" database. - to use the
+ * MongoDb as a database
+ *
+ *
To run the application with "in Memory" database, just launch it without parameters Example:
+ * 'java -jar app.jar'
+ *
+ *
To run the application with MongoDb you need to be installed the MongoDb in your system, or to
+ * launch it in the docker container. You may launch docker container from the root of current
+ * module with command: 'docker-compose up' Then you can start the application with parameter
+ * --mongo Example: 'java -jar app.jar --mongo'
*
* @see CacheStore
- * @See LRUCache
+ * @see LruCache
* @see CachingPolicy
- *
*/
+@Slf4j
public class App {
+ /** Constant parameter name to use mongoDB. */
+ private static final String USE_MONGO_DB = "--mongo";
+
+ /** Application manager. */
+ private final AppManager appManager;
/**
- * Program entry point
+ * Constructor of current App.
+ *
+ * @param isMongo boolean
+ */
+ public App(final boolean isMongo) {
+ DbManager dbManager = DbManagerFactory.initDb(isMongo);
+ appManager = new AppManager(dbManager);
+ appManager.initDb();
+ }
+
+ /**
+ * Program entry point.
*
* @param args command line args
*/
- public static void main(String[] args) {
- AppManager.initDB(false); // VirtualDB (instead of MongoDB) was used in running the JUnit tests
- // and the App class to avoid Maven compilation errors. Set flag to
- // true to run the tests with MongoDB (provided that MongoDB is
- // installed and socket connection is open).
- AppManager.initCacheCapacity(3);
- App app = new App();
+ public static void main(final String[] args) {
+ // VirtualDB (instead of MongoDB) was used in running the JUnit tests
+ // and the App class to avoid Maven compilation errors. Set flag to
+ // true to run the tests with MongoDB (provided that MongoDB is
+ // installed and socket connection is open).
+ boolean isDbMongo = isDbMongo(args);
+ if (isDbMongo) {
+ LOGGER.info("Using the Mongo database engine to run the application.");
+ } else {
+ LOGGER.info("Using the 'in Memory' database to run the application.");
+ }
+ App app = new App(isDbMongo);
app.useReadAndWriteThroughStrategy();
+ String splitLine = "==============================================";
+ LOGGER.info(splitLine);
app.useReadThroughAndWriteAroundStrategy();
+ LOGGER.info(splitLine);
app.useReadThroughAndWriteBehindStrategy();
+ LOGGER.info(splitLine);
+ app.useCacheAsideStrategy();
+ LOGGER.info(splitLine);
}
/**
- * Read-through and write-through
+ * Check the input parameters. if
+ *
+ * @param args input params
+ * @return true if there is "--mongo" parameter in arguments
*/
+ private static boolean isDbMongo(final String[] args) {
+ for (String arg : args) {
+ if (arg.equals(USE_MONGO_DB)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Read-through and write-through. */
public void useReadAndWriteThroughStrategy() {
- System.out.println("# CachingPolicy.THROUGH");
- AppManager.initCachingPolicy(CachingPolicy.THROUGH);
+ LOGGER.info("# CachingPolicy.THROUGH");
+ appManager.initCachingPolicy(CachingPolicy.THROUGH);
- UserAccount userAccount1 = new UserAccount("001", "John", "He is a boy.");
+ var userAccount1 = new UserAccount("001", "John", "He is a boy.");
- AppManager.save(userAccount1);
- System.out.println(AppManager.printCacheContent());
- userAccount1 = AppManager.find("001");
- userAccount1 = AppManager.find("001");
+ appManager.save(userAccount1);
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("001");
+ appManager.find("001");
}
- /**
- * Read-through and write-around
- */
+ /** Read-through and write-around. */
public void useReadThroughAndWriteAroundStrategy() {
- System.out.println("# CachingPolicy.AROUND");
- AppManager.initCachingPolicy(CachingPolicy.AROUND);
+ LOGGER.info("# CachingPolicy.AROUND");
+ appManager.initCachingPolicy(CachingPolicy.AROUND);
- UserAccount userAccount2 = new UserAccount("002", "Jane", "She is a girl.");
+ var userAccount2 = new UserAccount("002", "Jane", "She is a girl.");
- AppManager.save(userAccount2);
- System.out.println(AppManager.printCacheContent());
- userAccount2 = AppManager.find("002");
- System.out.println(AppManager.printCacheContent());
- userAccount2 = AppManager.find("002");
+ appManager.save(userAccount2);
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("002");
+ LOGGER.info(appManager.printCacheContent());
+ userAccount2 = appManager.find("002");
userAccount2.setUserName("Jane G.");
- AppManager.save(userAccount2);
- System.out.println(AppManager.printCacheContent());
- userAccount2 = AppManager.find("002");
- System.out.println(AppManager.printCacheContent());
- userAccount2 = AppManager.find("002");
+ appManager.save(userAccount2);
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("002");
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("002");
}
- /**
- * Read-through and write-behind
- */
+ /** Read-through and write-behind. */
public void useReadThroughAndWriteBehindStrategy() {
- System.out.println("# CachingPolicy.BEHIND");
- AppManager.initCachingPolicy(CachingPolicy.BEHIND);
-
- UserAccount userAccount3 = new UserAccount("003", "Adam", "He likes food.");
- UserAccount userAccount4 = new UserAccount("004", "Rita", "She hates cats.");
- UserAccount userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard.");
-
- AppManager.save(userAccount3);
- AppManager.save(userAccount4);
- AppManager.save(userAccount5);
- System.out.println(AppManager.printCacheContent());
- userAccount3 = AppManager.find("003");
- System.out.println(AppManager.printCacheContent());
+ LOGGER.info("# CachingPolicy.BEHIND");
+ appManager.initCachingPolicy(CachingPolicy.BEHIND);
+
+ var userAccount3 = new UserAccount("003", "Adam", "He likes food.");
+ var userAccount4 = new UserAccount("004", "Rita", "She hates cats.");
+ var userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard.");
+
+ appManager.save(userAccount3);
+ appManager.save(userAccount4);
+ appManager.save(userAccount5);
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("003");
+ LOGGER.info(appManager.printCacheContent());
UserAccount userAccount6 = new UserAccount("006", "Yasha", "She is an only child.");
- AppManager.save(userAccount6);
- System.out.println(AppManager.printCacheContent());
- userAccount4 = AppManager.find("004");
- System.out.println(AppManager.printCacheContent());
+ appManager.save(userAccount6);
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("004");
+ LOGGER.info(appManager.printCacheContent());
+ }
+
+ /** Cache-Aside. */
+ public void useCacheAsideStrategy() {
+ LOGGER.info("# CachingPolicy.ASIDE");
+ appManager.initCachingPolicy(CachingPolicy.ASIDE);
+ LOGGER.info(appManager.printCacheContent());
+
+ var userAccount3 = new UserAccount("003", "Adam", "He likes food.");
+ var userAccount4 = new UserAccount("004", "Rita", "She hates cats.");
+ var userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard.");
+ appManager.save(userAccount3);
+ appManager.save(userAccount4);
+ appManager.save(userAccount5);
+
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("003");
+ LOGGER.info(appManager.printCacheContent());
+ appManager.find("004");
+ LOGGER.info(appManager.printCacheContent());
}
}
diff --git a/caching/src/main/java/com/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java
index 08132e327880..c1d21fea33fe 100644
--- a/caching/src/main/java/com/iluwatar/caching/AppManager.java
+++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java
@@ -1,75 +1,152 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.caching;
-import java.text.ParseException;
+import com.iluwatar.caching.database.DbManager;
+import java.util.Optional;
+import lombok.extern.slf4j.Slf4j;
/**
- *
* AppManager helps to bridge the gap in communication between the main class and the application's
* back-end. DB connection is initialized through this class. The chosen caching strategy/policy is
* also initialized here. Before the cache can be used, the size of the cache has to be set.
* Depending on the chosen caching policy, AppManager will call the appropriate function in the
* CacheStore class.
- *
*/
+@Slf4j
public class AppManager {
+ /** Caching Policy. */
+ private CachingPolicy cachingPolicy;
+
+ /** Database Manager. */
+ private final DbManager dbManager;
- private static CachingPolicy cachingPolicy;
+ /** Cache Store. */
+ private final CacheStore cacheStore;
/**
+ * Constructor.
*
+ * @param newDbManager database manager
+ */
+ public AppManager(final DbManager newDbManager) {
+ this.dbManager = newDbManager;
+ this.cacheStore = new CacheStore(newDbManager);
+ }
+
+ /**
* Developer/Tester is able to choose whether the application should use MongoDB as its underlying
* data storage or a simple Java data structure to (temporarily) store the data/objects during
* runtime.
*/
- public static void initDB(boolean useMongoDB) {
- if (useMongoDB) {
- try {
- DBManager.connect();
- } catch (ParseException e) {
- e.printStackTrace();
- }
- } else {
- DBManager.createVirtualDB();
- }
+ public void initDb() {
+ dbManager.connect();
}
- public static void initCachingPolicy(CachingPolicy policy) {
+ /**
+ * Initialize caching policy.
+ *
+ * @param policy is a {@link CachingPolicy}
+ */
+ public void initCachingPolicy(final CachingPolicy policy) {
cachingPolicy = policy;
if (cachingPolicy == CachingPolicy.BEHIND) {
- Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
- @Override
- public void run() {
- CacheStore.flushCache();
- }
- }));
+ Runtime.getRuntime().addShutdownHook(new Thread(cacheStore::flushCache));
}
- CacheStore.clearCache();
- }
-
- public static void initCacheCapacity(int capacity) {
- CacheStore.initCapacity(capacity);
+ cacheStore.clearCache();
}
- public static UserAccount find(String userID) {
+ /**
+ * Find user account.
+ *
+ * @param userId String
+ * @return {@link UserAccount}
+ */
+ public UserAccount find(final String userId) {
+ LOGGER.info("Trying to find {} in cache", userId);
if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) {
- return CacheStore.readThrough(userID);
+ return cacheStore.readThrough(userId);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
- return CacheStore.readThroughWithWriteBackPolicy(userID);
+ return cacheStore.readThroughWithWriteBackPolicy(userId);
+ } else if (cachingPolicy == CachingPolicy.ASIDE) {
+ return findAside(userId);
}
return null;
}
- public static void save(UserAccount userAccount) {
+ /**
+ * Save user account.
+ *
+ * @param userAccount {@link UserAccount}
+ */
+ public void save(final UserAccount userAccount) {
+ LOGGER.info("Save record!");
if (cachingPolicy == CachingPolicy.THROUGH) {
- CacheStore.writeThrough(userAccount);
+ cacheStore.writeThrough(userAccount);
} else if (cachingPolicy == CachingPolicy.AROUND) {
- CacheStore.writeAround(userAccount);
+ cacheStore.writeAround(userAccount);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
- CacheStore.writeBehind(userAccount);
+ cacheStore.writeBehind(userAccount);
+ } else if (cachingPolicy == CachingPolicy.ASIDE) {
+ saveAside(userAccount);
}
}
- public static String printCacheContent() {
- return CacheStore.print();
+ /**
+ * Returns String.
+ *
+ * @return String
+ */
+ public String printCacheContent() {
+ return cacheStore.print();
+ }
+
+ /**
+ * Cache-Aside save user account helper.
+ *
+ * @param userAccount {@link UserAccount}
+ */
+ private void saveAside(final UserAccount userAccount) {
+ dbManager.updateDb(userAccount);
+ cacheStore.invalidate(userAccount.getUserId());
+ }
+
+ /**
+ * Cache-Aside find user account helper.
+ *
+ * @param userId String
+ * @return {@link UserAccount}
+ */
+ private UserAccount findAside(final String userId) {
+ return Optional.ofNullable(cacheStore.get(userId))
+ .or(
+ () -> {
+ Optional userAccount = Optional.ofNullable(dbManager.readFromDb(userId));
+ userAccount.ifPresent(account -> cacheStore.set(userId, account));
+ return userAccount;
+ })
+ .orElse(null);
}
}
diff --git a/caching/src/main/java/com/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java
index 2041ac14ffa8..b26c52b22159 100644
--- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java
+++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java
@@ -1,104 +1,210 @@
-package com.iluwatar.caching;
-
-import java.util.ArrayList;
-
-/**
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
- * The caching strategies are implemented in this class.
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
*
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to 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.caching;
+
+import com.iluwatar.caching.database.DbManager;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+
+/** The caching strategies are implemented in this class. */
+@Slf4j
public class CacheStore {
+ /** Cache capacity. */
+ private static final int CAPACITY = 3;
+
+ /** Lru cache see {@link LruCache}. */
+ private LruCache cache;
- static LRUCache cache = null;
+ /** DbManager. */
+ private final DbManager dbManager;
- public static void initCapacity(int capacity) {
- if (null == cache)
- cache = new LRUCache(capacity);
- else
+ /**
+ * Cache Store.
+ *
+ * @param dataBaseManager {@link DbManager}
+ */
+ public CacheStore(final DbManager dataBaseManager) {
+ this.dbManager = dataBaseManager;
+ initCapacity(CAPACITY);
+ }
+
+ /**
+ * Init cache capacity.
+ *
+ * @param capacity int
+ */
+ public void initCapacity(final int capacity) {
+ if (cache == null) {
+ cache = new LruCache(capacity);
+ } else {
cache.setCapacity(capacity);
+ }
}
- public static UserAccount readThrough(String userID) {
- if (cache.contains(userID)) {
- System.out.println("# Cache Hit!");
- return cache.get(userID);
+ /**
+ * Get user account using read-through cache.
+ *
+ * @param userId {@link String}
+ * @return {@link UserAccount}
+ */
+ public UserAccount readThrough(final String userId) {
+ if (cache.contains(userId)) {
+ LOGGER.info("# Found in Cache!");
+ return cache.get(userId);
}
- System.out.println("# Cache Miss!");
- UserAccount userAccount = DBManager.readFromDB(userID);
- cache.set(userID, userAccount);
+ LOGGER.info("# Not found in cache! Go to DB!!");
+ UserAccount userAccount = dbManager.readFromDb(userId);
+ cache.set(userId, userAccount);
return userAccount;
}
- public static void writeThrough(UserAccount userAccount) {
- if (cache.contains(userAccount.getUserID())) {
- DBManager.updateDB(userAccount);
+ /**
+ * Get user account using write-through cache.
+ *
+ * @param userAccount {@link UserAccount}
+ */
+ public void writeThrough(final UserAccount userAccount) {
+ if (cache.contains(userAccount.getUserId())) {
+ dbManager.updateDb(userAccount);
} else {
- DBManager.writeToDB(userAccount);
+ dbManager.writeToDb(userAccount);
}
- cache.set(userAccount.getUserID(), userAccount);
+ cache.set(userAccount.getUserId(), userAccount);
}
- public static void writeAround(UserAccount userAccount) {
- if (cache.contains(userAccount.getUserID())) {
- DBManager.updateDB(userAccount);
- cache.invalidate(userAccount.getUserID()); // Cache data has been updated -- remove older
- // version from cache.
+ /**
+ * Get user account using write-around cache.
+ *
+ * @param userAccount {@link UserAccount}
+ */
+ public void writeAround(final UserAccount userAccount) {
+ if (cache.contains(userAccount.getUserId())) {
+ dbManager.updateDb(userAccount);
+ // Cache data has been updated -- remove older
+ cache.invalidate(userAccount.getUserId());
+ // version from cache.
} else {
- DBManager.writeToDB(userAccount);
+ dbManager.writeToDb(userAccount);
}
}
- public static UserAccount readThroughWithWriteBackPolicy(String userID) {
- if (cache.contains(userID)) {
- System.out.println("# Cache Hit!");
- return cache.get(userID);
+ /**
+ * Get user account using read-through cache with write-back policy.
+ *
+ * @param userId {@link String}
+ * @return {@link UserAccount}
+ */
+ public UserAccount readThroughWithWriteBackPolicy(final String userId) {
+ if (cache.contains(userId)) {
+ LOGGER.info("# Found in cache!");
+ return cache.get(userId);
}
- System.out.println("# Cache Miss!");
- UserAccount userAccount = DBManager.readFromDB(userID);
+ LOGGER.info("# Not found in Cache!");
+ UserAccount userAccount = dbManager.readFromDb(userId);
if (cache.isFull()) {
- System.out.println("# Cache is FULL! Writing LRU data to DB...");
- UserAccount toBeWrittenToDB = cache.getLRUData();
- DBManager.upsertDB(toBeWrittenToDB);
+ LOGGER.info("# Cache is FULL! Writing LRU data to DB...");
+ UserAccount toBeWrittenToDb = cache.getLruData();
+ dbManager.upsertDb(toBeWrittenToDb);
}
- cache.set(userID, userAccount);
+ cache.set(userId, userAccount);
return userAccount;
}
- public static void writeBehind(UserAccount userAccount) {
- if (cache.isFull() && !cache.contains(userAccount.getUserID())) {
- System.out.println("# Cache is FULL! Writing LRU data to DB...");
- UserAccount toBeWrittenToDB = cache.getLRUData();
- DBManager.upsertDB(toBeWrittenToDB);
+ /**
+ * Set user account.
+ *
+ * @param userAccount {@link UserAccount}
+ */
+ public void writeBehind(final UserAccount userAccount) {
+ if (cache.isFull() && !cache.contains(userAccount.getUserId())) {
+ LOGGER.info("# Cache is FULL! Writing LRU data to DB...");
+ UserAccount toBeWrittenToDb = cache.getLruData();
+ dbManager.upsertDb(toBeWrittenToDb);
}
- cache.set(userAccount.getUserID(), userAccount);
+ cache.set(userAccount.getUserId(), userAccount);
}
- public static void clearCache() {
- if (null != cache)
+ /** Clears cache. */
+ public void clearCache() {
+ if (cache != null) {
cache.clear();
+ }
+ }
+
+ /** Writes remaining content in the cache into the DB. */
+ public void flushCache() {
+ LOGGER.info("# flushCache...");
+ Optional.ofNullable(cache)
+ .map(LruCache::getCacheDataInListForm)
+ .orElse(List.of())
+ .forEach(dbManager::updateDb);
+ dbManager.disconnect();
}
/**
- * Writes remaining content in the cache into the DB.
+ * Print user accounts.
+ *
+ * @return {@link String}
*/
- public static void flushCache() {
- System.out.println("# flushCache...");
- if (null == cache)
- return;
- ArrayList listOfUserAccounts = cache.getCacheDataInListForm();
- for (UserAccount userAccount : listOfUserAccounts) {
- DBManager.upsertDB(userAccount);
- }
+ public String print() {
+ return Optional.ofNullable(cache)
+ .map(LruCache::getCacheDataInListForm)
+ .orElse(List.of())
+ .stream()
+ .map(userAccount -> userAccount.toString() + "\n")
+ .collect(Collectors.joining("", "\n--CACHE CONTENT--\n", "----"));
}
- public static String print() {
- ArrayList listOfUserAccounts = cache.getCacheDataInListForm();
- StringBuilder sb = new StringBuilder();
- sb.append("\n--CACHE CONTENT--\n");
- for (UserAccount userAccount : listOfUserAccounts) {
- sb.append(userAccount.toString() + "\n");
- }
- sb.append("----\n");
- return sb.toString();
+ /**
+ * Delegate to backing cache store.
+ *
+ * @param userId {@link String}
+ * @return {@link UserAccount}
+ */
+ public UserAccount get(final String userId) {
+ return cache.get(userId);
+ }
+
+ /**
+ * Delegate to backing cache store.
+ *
+ * @param userId {@link String}
+ * @param userAccount {@link UserAccount}
+ */
+ public void set(final String userId, final UserAccount userAccount) {
+ cache.set(userId, userAccount);
+ }
+
+ /**
+ * Delegate to backing cache store.
+ *
+ * @param userId {@link String}
+ */
+ public void invalidate(final String userId) {
+ cache.invalidate(userId);
}
}
diff --git a/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java
index 314cfaa36b61..0ec07ced7b8f 100644
--- a/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java
+++ b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java
@@ -1,20 +1,45 @@
-package com.iluwatar.caching;
-
-/**
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
*
- * Enum class containing the three caching strategies implemented in the pattern.
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
*
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 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.
*/
-public enum CachingPolicy {
- THROUGH("through"), AROUND("around"), BEHIND("behind");
+package com.iluwatar.caching;
- private String policy;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
- private CachingPolicy(String policy) {
- this.policy = policy;
- }
+/** Enum class containing the four caching strategies implemented in the pattern. */
+@AllArgsConstructor
+@Getter
+public enum CachingPolicy {
+ /** Through. */
+ THROUGH("through"),
+ /** AROUND. */
+ AROUND("around"),
+ /** BEHIND. */
+ BEHIND("behind"),
+ /** ASIDE. */
+ ASIDE("aside");
- public String getPolicy() {
- return policy;
- }
+ /** Policy value. */
+ private final String policy;
}
diff --git a/caching/src/main/java/com/iluwatar/caching/DBManager.java b/caching/src/main/java/com/iluwatar/caching/DBManager.java
deleted file mode 100644
index 07a5daeac76c..000000000000
--- a/caching/src/main/java/com/iluwatar/caching/DBManager.java
+++ /dev/null
@@ -1,123 +0,0 @@
-package com.iluwatar.caching;
-
-import java.text.ParseException;
-import java.util.HashMap;
-
-import org.bson.Document;
-
-import com.mongodb.MongoClient;
-import com.mongodb.client.FindIterable;
-import com.mongodb.client.MongoDatabase;
-import com.mongodb.client.model.UpdateOptions;
-
-/**
- *
- * DBManager handles the communication with the underlying data store i.e. Database. It contains the
- * implemented methods for querying, inserting, and updating data. MongoDB was used as the database
- * for the application.
- *
- * Developer/Tester is able to choose whether the application should use MongoDB as its underlying
- * data storage (connect()) or a simple Java data structure to (temporarily) store the data/objects
- * during runtime (createVirtualDB()).
- *
- */
-public class DBManager {
-
- private static MongoClient mongoClient;
- private static MongoDatabase db;
- private static boolean useMongoDB;
-
- private static HashMap virtualDB;
-
- public static void createVirtualDB() {
- useMongoDB = false;
- virtualDB = new HashMap();
- }
-
- public static void connect() throws ParseException {
- useMongoDB = true;
- mongoClient = new MongoClient();
- db = mongoClient.getDatabase("test");
- }
-
- public static UserAccount readFromDB(String userID) {
- if (!useMongoDB) {
- if (virtualDB.containsKey(userID))
- return virtualDB.get(userID);
- return null;
- }
- if (null == db) {
- try {
- connect();
- } catch (ParseException e) {
- e.printStackTrace();
- }
- }
- FindIterable iterable =
- db.getCollection("user_accounts").find(new Document("userID", userID));
- if (iterable == null)
- return null;
- Document doc = iterable.first();
- UserAccount userAccount =
- new UserAccount(userID, doc.getString("userName"), doc.getString("additionalInfo"));
- return userAccount;
- }
-
- public static void writeToDB(UserAccount userAccount) {
- if (!useMongoDB) {
- virtualDB.put(userAccount.getUserID(), userAccount);
- return;
- }
- if (null == db) {
- try {
- connect();
- } catch (ParseException e) {
- e.printStackTrace();
- }
- }
- db.getCollection("user_accounts").insertOne(
- new Document("userID", userAccount.getUserID()).append("userName",
- userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo()));
- }
-
- public static void updateDB(UserAccount userAccount) {
- if (!useMongoDB) {
- virtualDB.put(userAccount.getUserID(), userAccount);
- return;
- }
- if (null == db) {
- try {
- connect();
- } catch (ParseException e) {
- e.printStackTrace();
- }
- }
- db.getCollection("user_accounts").updateOne(
- new Document("userID", userAccount.getUserID()),
- new Document("$set", new Document("userName", userAccount.getUserName()).append(
- "additionalInfo", userAccount.getAdditionalInfo())));
- }
-
- /**
- *
- * Insert data into DB if it does not exist. Else, update it.
- */
- public static void upsertDB(UserAccount userAccount) {
- if (!useMongoDB) {
- virtualDB.put(userAccount.getUserID(), userAccount);
- return;
- }
- if (null == db) {
- try {
- connect();
- } catch (ParseException e) {
- e.printStackTrace();
- }
- }
- db.getCollection("user_accounts").updateOne(
- new Document("userID", userAccount.getUserID()),
- new Document("$set", new Document("userID", userAccount.getUserID()).append("userName",
- userAccount.getUserName()).append("additionalInfo", userAccount.getAdditionalInfo())),
- new UpdateOptions().upsert(true));
- }
-}
diff --git a/caching/src/main/java/com/iluwatar/caching/LRUCache.java b/caching/src/main/java/com/iluwatar/caching/LRUCache.java
deleted file mode 100644
index 872f9725655c..000000000000
--- a/caching/src/main/java/com/iluwatar/caching/LRUCache.java
+++ /dev/null
@@ -1,146 +0,0 @@
-package com.iluwatar.caching;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- *
- * Data structure/implementation of the application's cache. The data structure consists of a hash
- * table attached with a doubly linked-list. The linked-list helps in capturing and maintaining the
- * LRU data in the cache. When a data is queried (from the cache), added (to the cache), or updated,
- * the data is moved to the front of the list to depict itself as the most-recently-used data. The
- * LRU data is always at the end of the list.
- *
- */
-public class LRUCache {
-
- class Node {
- String userID;
- UserAccount userAccount;
- Node previous;
- Node next;
-
- public Node(String userID, UserAccount userAccount) {
- this.userID = userID;
- this.userAccount = userAccount;
- }
- }
-
- int capacity;
- HashMap cache = new HashMap();
- Node head = null;
- Node end = null;
-
- public LRUCache(int capacity) {
- this.capacity = capacity;
- }
-
- public UserAccount get(String userID) {
- if (cache.containsKey(userID)) {
- Node node = cache.get(userID);
- remove(node);
- setHead(node);
- return node.userAccount;
- }
- return null;
- }
-
- /**
- *
- * Remove node from linked list.
- */
- public void remove(Node node) {
- if (node.previous != null) {
- node.previous.next = node.next;
- } else {
- head = node.next;
- }
- if (node.next != null) {
- node.next.previous = node.previous;
- } else {
- end = node.previous;
- }
- }
-
- /**
- *
- * Move node to the front of the list.
- */
- public void setHead(Node node) {
- node.next = head;
- node.previous = null;
- if (head != null)
- head.previous = node;
- head = node;
- if (end == null)
- end = head;
- }
-
- public void set(String userID, UserAccount userAccount) {
- if (cache.containsKey(userID)) {
- Node old = cache.get(userID);
- old.userAccount = userAccount;
- remove(old);
- setHead(old);
- } else {
- Node newNode = new Node(userID, userAccount);
- if (cache.size() >= capacity) {
- System.out.println("# Cache is FULL! Removing " + end.userID + " from cache...");
- cache.remove(end.userID); // remove LRU data from cache.
- remove(end);
- setHead(newNode);
- } else {
- setHead(newNode);
- }
- cache.put(userID, newNode);
- }
- }
-
- public boolean contains(String userID) {
- return cache.containsKey(userID);
- }
-
- public void invalidate(String userID) {
- System.out.println("# " + userID + " has been updated! Removing older version from cache...");
- Node toBeRemoved = cache.get(userID);
- remove(toBeRemoved);
- cache.remove(userID);
- }
-
- public boolean isFull() {
- return cache.size() >= capacity;
- }
-
- public UserAccount getLRUData() {
- return end.userAccount;
- }
-
- public void clear() {
- head = null;
- end = null;
- cache.clear();
- }
-
- /**
- *
- * Returns cache data in list form.
- */
- public ArrayList getCacheDataInListForm() {
- ArrayList