From 04ed1f20c5c5690b6d62ba161dff052b85575fc3 Mon Sep 17 00:00:00 2001 From: u6996742 Date: Thu, 27 Oct 2022 11:48:50 +1100 Subject: [PATCH] #88 Created Facet pattern example and wrote most of the README. --- facet/README.md | 178 ++++++++++++++++++ facet/etc/proxy.urm.png | Bin 0 -> 28797 bytes facet/etc/proxy.urm.puml | 32 ++++ facet/pom.xml | 67 +++++++ .../src/main/java/com/iluwatar/facet/App.java | 53 ++++++ .../main/java/com/iluwatar/facet/Attack.java | 5 + .../main/java/com/iluwatar/facet/Knight.java | 27 +++ .../com/iluwatar/facet/dragon/Dragon.java | 38 ++++ .../iluwatar/facet/dragon/DragonFacet.java | 24 +++ .../test/java/com/iluwatar/facet/AppTest.java | 40 ++++ .../com/iluwatar/facet/IvoryTowerTest.java | 71 +++++++ .../java/com/iluwatar/facet/WizardTest.java | 42 +++++ .../iluwatar/facet/WizardTowerProxyTest.java | 71 +++++++ .../facet/utils/InMemoryAppender.java | 63 +++++++ 14 files changed, 711 insertions(+) create mode 100644 facet/README.md create mode 100644 facet/etc/proxy.urm.png create mode 100644 facet/etc/proxy.urm.puml create mode 100644 facet/pom.xml create mode 100644 facet/src/main/java/com/iluwatar/facet/App.java create mode 100644 facet/src/main/java/com/iluwatar/facet/Attack.java create mode 100644 facet/src/main/java/com/iluwatar/facet/Knight.java create mode 100644 facet/src/main/java/com/iluwatar/facet/dragon/Dragon.java create mode 100644 facet/src/main/java/com/iluwatar/facet/dragon/DragonFacet.java create mode 100644 facet/src/test/java/com/iluwatar/facet/AppTest.java create mode 100644 facet/src/test/java/com/iluwatar/facet/IvoryTowerTest.java create mode 100644 facet/src/test/java/com/iluwatar/facet/WizardTest.java create mode 100644 facet/src/test/java/com/iluwatar/facet/WizardTowerProxyTest.java create mode 100644 facet/src/test/java/com/iluwatar/facet/utils/InMemoryAppender.java diff --git a/facet/README.md b/facet/README.md new file mode 100644 index 000000000000..74126ba482b4 --- /dev/null +++ b/facet/README.md @@ -0,0 +1,178 @@ +--- +title: Facet +category: Structural +language: en +tags: + - Decoupling +--- + +## Also known as + +Attenuation + +## Intent + +Provide a interface to a powerful object in a restricted way, either by restricting parameters +or only allowing calls to a subset of object's functions. + +## Explanation + +Real-world example + +> Imagine a knight fighting a dragon. The knight can only attempt to attack the dragon by trying +> different attacks. Here, the facet stops the knight from directly changing the health of the +> dragon, as well as only allowing certain attacks to affect the dragon. + +In plain words + +> Using the facet pattern, a class represents strictly limited access to another class. + +C2 Wiki says that the intent is + +> Restrict an interface to obtain a smaller interface that provides less authority. Usually the +> smaller interface has only a subset of the methods, or allows only a subset of parameter values. + +**Programmatic Example** + +Taking our knight versus dragon example from above. Firstly we have the `Knight` class and the +`Attack` enum containing attacks that can belong to knights. + +```java +@Slf4j +public class Knight { + private final String name; + private Attack attack; + private DragonFacet dragonFacet; + + public Knight (String name, Attack attack, DragonFacet dragonFacet) { + this.name = name; + this.attack = attack; + this.dragonFacet = dragonFacet; + } + + public void attackDragon() { + int oldHealth = dragonFacet.getHealth(); + dragonFacet.receiveAttack(attack); + if(oldHealth == dragonFacet.getHealth()){ + LOGGER.info("{}: Darn it! {} did nothing.", name, attack); + } else { + LOGGER.info("{}: Huzzah! {} hurt that dastardly dragon.", name, attack); + } + } +} + +public enum Attack { + ARROW, WATER_PISTOL, SWORD, FLAME_THROWER +} +``` + +Next are the `Dragon` class and the `DragonFacet` class. These belong to the same package, which is +different to the package containing `Knight`. This means that when there is no access level modifier to a variable or method, such as for `setHealth()`, nothing outside the package will have access to them. So, if the facet doesn't allow it then there is no way to access them. + +Note that, according to C2 Wiki, + +> Facets should be implemented in such a way that if methods are added to the original interface, +> they are not added by default to the facet. The methods to be included in a facet should have to +> be explicitly indicated (although this could be by metadata or a naming convention). + +In this case, the methods are marked with `f_`. + +This is the simple `Dragon` class. + +```java +public class Dragon { + private int health; + + public Dragon(int health) { + this.health = health; + } + + int f_getHealth() { + return health; + } + + void setHealth(int health) { + this.health = health; + } + + void f_receiveAttack(Attack attack) { + switch(attack) { + case ARROW: + health -= 10; + break; + case WATER_PISTOL: + health -= 15; + break; + default: + health -= 5; + } + } +} +``` + +Then we have the `DragonFacet` to add control to `Dragon`. + +```java +@Slf4j +public class DragonFacet { + private Dragon dragon; + + public DragonFacet(Dragon dragon) { + this.dragon = dragon; + } + + public int getHealth() { + return dragon.f_getHealth(); + } + + public void receiveAttack(Attack attack) { + if(attack == Attack.WATER_PISTOL || attack == Attack.ARROW) { + dragon.f_receiveAttack(attack); + } + } +} +``` + +Note that `DragonFacet` only provides access to two of the three methods in `Dragon` +(the ones marked with `f_`). Also, `receiveAttack` makes a check for valid parameters. + +And here is the dragon-fighting scenario. + +```java +var facet = new DragonFacet(new Dragon(100)); +var sirJim = new Knight("Sir Jim", Attack.WATER_PISTOL, facet); +sirJim.attackDragon(); +var sirLuke = new Knight("Sir Luke", Attack.FLAME_THROWER, facet); +sirLuke.attackDragon(); +``` + +Program output: + +``` +Sir Jim: Huzzah! WATER_PISTOL hurt that dastardly dragon. +Sir Luke: Darn it! FLAME_THROWER did nothing. +``` + +## Class diagram + +![alt text](./etc/proxy.urm.png "Facet pattern class diagram") + +## Applicability + +Facet is applicable when the client should have restricted access to an object. It is +a special type of proxy, but no extra functionality may be provided; that is, the facet +should only provide a subset of the object's functionality to a client. + +This is often a security pattern, used in order to satisfy the Principle of Least +Authority. For example, if an object should be read-only, then the client should +be provided with a read-only facet. + + +## Related patterns + +* [Proxy](https://java-design-patterns.com/patterns/proxy/) + +## Credits + +* [Facet Pattern](http://wiki.c2.com/?FacetPattern) +* [Capability Theory Glossary](http://www.cap-lore.com/CapTheory/Glossary.html) diff --git a/facet/etc/proxy.urm.png b/facet/etc/proxy.urm.png new file mode 100644 index 0000000000000000000000000000000000000000..a0c94fc7c71722efadfc63ebe641121e87bfeb3a GIT binary patch literal 28797 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 literal 0 HcmV?d00001 diff --git a/facet/etc/proxy.urm.puml b/facet/etc/proxy.urm.puml new file mode 100644 index 000000000000..a6e73c66aa93 --- /dev/null +++ b/facet/etc/proxy.urm.puml @@ -0,0 +1,32 @@ +@startuml +package com.iluwatar.facet { + class App { + + App() + + main(args : String[]) {static} + } + class IvoryTower { + - LOGGER : Logger {static} + + IvoryTower() + + enter(wizard : Wizard) + } + class Wizard { + - name : String + + Wizard(name : String) + + toString() : String + } + interface WizardTower { + + enter(Wizard) {abstract} + } + class WizardTowerProxy { + - LOGGER : Logger {static} + - NUM_WIZARDS_ALLOWED : int {static} + - numWizards : int + - tower : WizardTower + + WizardTowerProxy(tower : WizardTower) + + enter(wizard : Wizard) + } +} +WizardTowerProxy --> "-tower" WizardTower +IvoryTower ..|> WizardTower +WizardTowerProxy ..|> WizardTower +@enduml \ No newline at end of file diff --git a/facet/pom.xml b/facet/pom.xml new file mode 100644 index 000000000000..cc87378383b7 --- /dev/null +++ b/facet/pom.xml @@ -0,0 +1,67 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + proxy + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.facet.App + + + + + + + + + diff --git a/facet/src/main/java/com/iluwatar/facet/App.java b/facet/src/main/java/com/iluwatar/facet/App.java new file mode 100644 index 000000000000..9a6957e361cf --- /dev/null +++ b/facet/src/main/java/com/iluwatar/facet/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.facet; + +import com.iluwatar.facet.dragon.*; + +/** + * A facet is a class functioning as an interface to something else which provides less functionality. + * It is a wrapper often used for security purposes, so that the Principle of Least Authority is + * observed. It is technically a special case of proxy. + * + *

The Facet design pattern allows you to provide an interface to other objects by creating a + * wrapper class as the facet. The wrapper class, which is the facet, must only provide access to + * certain functions and under certain parameters, but never add functionality. + * + *

In this example the facet ({@link DragonFacet}) controls access to the actual object ( + * {@link Dragon}). + */ +public class App { + + /** + * Program entry point. + */ + public static void main(String[] args) { + var facet = new DragonFacet(new Dragon(100)); + var sirJim = new Knight("Sir Jim", Attack.WATER_PISTOL, facet); + sirJim.attackDragon(); + var sirLuke = new Knight("Sir Luke", Attack.FLAME_THROWER, facet); + sirLuke.attackDragon(); + } +} diff --git a/facet/src/main/java/com/iluwatar/facet/Attack.java b/facet/src/main/java/com/iluwatar/facet/Attack.java new file mode 100644 index 000000000000..48a662d49bce --- /dev/null +++ b/facet/src/main/java/com/iluwatar/facet/Attack.java @@ -0,0 +1,5 @@ +package com.iluwatar.facet; + +public enum Attack { + ARROW, WATER_PISTOL, SWORD, FLAME_THROWER +} diff --git a/facet/src/main/java/com/iluwatar/facet/Knight.java b/facet/src/main/java/com/iluwatar/facet/Knight.java new file mode 100644 index 000000000000..1c529f3449d4 --- /dev/null +++ b/facet/src/main/java/com/iluwatar/facet/Knight.java @@ -0,0 +1,27 @@ +package com.iluwatar.facet; + +import com.iluwatar.facet.dragon.DragonFacet; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class Knight { + private final String name; + private Attack attack; + private DragonFacet dragonFacet; + + public Knight (String name, Attack attack, DragonFacet dragonFacet) { + this.name = name; + this.attack = attack; + this.dragonFacet = dragonFacet; + } + + public void attackDragon() { + int oldHealth = dragonFacet.getHealth(); + dragonFacet.receiveAttack(attack); + if(oldHealth == dragonFacet.getHealth()){ + LOGGER.info("{}: Darn it! {} did nothing.", name, attack); + } else { + LOGGER.info("{}: Huzzah! {} hurt that dastardly dragon.", name, attack); + } + } +} diff --git a/facet/src/main/java/com/iluwatar/facet/dragon/Dragon.java b/facet/src/main/java/com/iluwatar/facet/dragon/Dragon.java new file mode 100644 index 000000000000..3a1ecbd66a78 --- /dev/null +++ b/facet/src/main/java/com/iluwatar/facet/dragon/Dragon.java @@ -0,0 +1,38 @@ +package com.iluwatar.facet.dragon; + +import com.iluwatar.facet.Attack; + +/** + * Dragon object needs to be protected, since the other objects shouldn't be + * allowed to edit its health. + * + * + */ +public class Dragon { + private int health; + + public Dragon(int health) { + this.health = health; + } + + int f_getHealth() { + return health; + } + + void setHealth(int health) { + this.health = health; + } + + void f_receiveAttack(Attack attack) { + switch(attack) { + case ARROW: + health -= 10; + break; + case WATER_PISTOL: + health -= 15; + break; + default: + health -= 5; + } + } +} diff --git a/facet/src/main/java/com/iluwatar/facet/dragon/DragonFacet.java b/facet/src/main/java/com/iluwatar/facet/dragon/DragonFacet.java new file mode 100644 index 000000000000..3d15a92afba4 --- /dev/null +++ b/facet/src/main/java/com/iluwatar/facet/dragon/DragonFacet.java @@ -0,0 +1,24 @@ +package com.iluwatar.facet.dragon; + +import com.iluwatar.facet.Attack; +import com.iluwatar.facet.dragon.Dragon; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class DragonFacet { + private Dragon dragon; + + public DragonFacet(Dragon dragon) { + this.dragon = dragon; + } + + public int getHealth() { + return dragon.f_getHealth(); + } + + public void receiveAttack(Attack attack) { + if(attack == Attack.WATER_PISTOL || attack == Attack.ARROW) { + dragon.f_receiveAttack(attack); + } + } +} diff --git a/facet/src/test/java/com/iluwatar/facet/AppTest.java b/facet/src/test/java/com/iluwatar/facet/AppTest.java new file mode 100644 index 000000000000..c14802d0b2d7 --- /dev/null +++ b/facet/src/test/java/com/iluwatar/facet/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.facet; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Application test + */ +class AppTest { + + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[]{})); + } +} diff --git a/facet/src/test/java/com/iluwatar/facet/IvoryTowerTest.java b/facet/src/test/java/com/iluwatar/facet/IvoryTowerTest.java new file mode 100644 index 000000000000..85f58df3d26c --- /dev/null +++ b/facet/src/test/java/com/iluwatar/facet/IvoryTowerTest.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.facet; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.iluwatar.facet.utils.InMemoryAppender; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link IvoryTower} + */ +class IvoryTowerTest { + + private InMemoryAppender appender; + + @BeforeEach + public void setUp() { + appender = new InMemoryAppender(IvoryTower.class); + } + + @AfterEach + public void tearDown() { + appender.stop(); + } + + @Test + void testEnter() { + final var wizards = List.of( + new Wizard("Gandalf"), + new Wizard("Dumbledore"), + new Wizard("Oz"), + new Wizard("Merlin") + ); + + var tower = new IvoryTower(); + wizards.forEach(tower::enter); + + assertTrue(appender.logContains("Gandalf enters the tower.")); + assertTrue(appender.logContains("Dumbledore enters the tower.")); + assertTrue(appender.logContains("Oz enters the tower.")); + assertTrue(appender.logContains("Merlin enters the tower.")); + assertEquals(4, appender.getLogSize()); + } +} diff --git a/facet/src/test/java/com/iluwatar/facet/WizardTest.java b/facet/src/test/java/com/iluwatar/facet/WizardTest.java new file mode 100644 index 000000000000..77af9fc44070 --- /dev/null +++ b/facet/src/test/java/com/iluwatar/facet/WizardTest.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.facet; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link Wizard} + */ +class WizardTest { + + @Test + void testToString() { + List.of("Gandalf", "Dumbledore", "Oz", "Merlin") + .forEach(name -> assertEquals(name, new Wizard(name).toString())); + } +} \ No newline at end of file diff --git a/facet/src/test/java/com/iluwatar/facet/WizardTowerProxyTest.java b/facet/src/test/java/com/iluwatar/facet/WizardTowerProxyTest.java new file mode 100644 index 000000000000..6b6a72be42dd --- /dev/null +++ b/facet/src/test/java/com/iluwatar/facet/WizardTowerProxyTest.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.facet; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.iluwatar.facet.utils.InMemoryAppender; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link WizardTowerProxy} + */ +class WizardTowerProxyTest { + + private InMemoryAppender appender; + + @BeforeEach + public void setUp() { + appender = new InMemoryAppender(); + } + + @AfterEach + public void tearDown() { + appender.stop(); + } + + @Test + void testEnter() { + final var wizards = List.of( + new Wizard("Gandalf"), + new Wizard("Dumbledore"), + new Wizard("Oz"), + new Wizard("Merlin") + ); + + final var proxy = new WizardTowerProxy(new IvoryTower()); + wizards.forEach(proxy::enter); + + assertTrue(appender.logContains("Gandalf enters the tower.")); + assertTrue(appender.logContains("Dumbledore enters the tower.")); + assertTrue(appender.logContains("Oz enters the tower.")); + assertTrue(appender.logContains("Merlin is not allowed to enter!")); + assertEquals(4, appender.getLogSize()); + } +} diff --git a/facet/src/test/java/com/iluwatar/facet/utils/InMemoryAppender.java b/facet/src/test/java/com/iluwatar/facet/utils/InMemoryAppender.java new file mode 100644 index 000000000000..5f122bcb39cd --- /dev/null +++ b/facet/src/test/java/com/iluwatar/facet/utils/InMemoryAppender.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.facet.utils; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import java.util.LinkedList; +import java.util.List; +import org.slf4j.LoggerFactory; + + +/** + * InMemory Log Appender Util. + */ +public class InMemoryAppender extends AppenderBase { + private final List log = new LinkedList<>(); + + public InMemoryAppender(Class clazz) { + ((Logger) LoggerFactory.getLogger(clazz)).addAppender(this); + start(); + } + + public InMemoryAppender() { + ((Logger) LoggerFactory.getLogger("root")).addAppender(this); + start(); + } + + @Override + protected void append(ILoggingEvent eventObject) { + log.add(eventObject); + } + + public boolean logContains(String message) { + return log.stream().map(ILoggingEvent::getFormattedMessage).anyMatch(message::equals); + } + + public int getLogSize() { + return log.size(); + } +}