deps: Refactor dependencies (#3224)

* remove spring dep
move junit, logging, mockito under dep mgmt

* upgrade anti-corruption-layer deps

* async method invocation

* balking, bloc

* bridge to bytecode

* caching

* callback - cqrs

* component - health check

* hexagonal - metadata mapping

* rest of the patterns

* remove checkstyle, take spotless into use
This commit is contained in:
Ilkka Seppälä authored and GitHub committed 2025-03-29 19:34:27 +02:00
1 parent 371439aeaa
commit 0ca162a55c
1863 files changed
+14403 -17632

No files matched your search

+8
View File
@@ -34,6 +34,14 @@
</parent> </parent>
<artifactId>abstract-document</artifactId> <artifactId>abstract-document</artifactId>
<dependencies> <dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
@@ -31,9 +31,7 @@ import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /** Abstract implementation of Document interface. */
* Abstract implementation of Document interface.
*/
public abstract class AbstractDocument implements Document { public abstract class AbstractDocument implements Document {
private final Map<String, Object> documentProperties; private final Map<String, Object> documentProperties;
@@ -57,12 +55,12 @@ public abstract class AbstractDocument implements Document {
@Override @Override
public <T> Stream<T> children(String key, Function<Map<String, Object>, T> childConstructor) { public <T> Stream<T> children(String key, Function<Map<String, Object>, T> childConstructor) {
return Stream.ofNullable(get(key)) return Stream.ofNullable(get(key))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.map(el -> (List<Map<String, Object>>) el) .map(el -> (List<Map<String, Object>>) el)
.findAny() .findAny()
.stream() .stream()
.flatMap(Collection::stream) .flatMap(Collection::stream)
.map(childConstructor); .map(childConstructor);
} }
@Override @Override
@@ -100,5 +98,4 @@ public abstract class AbstractDocument implements Document {
builder.append("]"); builder.append("]");
return builder.toString(); return builder.toString();
} }
} }
@@ -49,20 +49,26 @@ public class App {
public static void main(String[] args) { public static void main(String[] args) {
LOGGER.info("Constructing parts and car"); LOGGER.info("Constructing parts and car");
var wheelProperties = Map.of( var wheelProperties =
Property.TYPE.toString(), "wheel", Map.of(
Property.MODEL.toString(), "15C", Property.TYPE.toString(), "wheel",
Property.PRICE.toString(), 100L); Property.MODEL.toString(), "15C",
Property.PRICE.toString(), 100L);
var doorProperties = Map.of( var doorProperties =
Property.TYPE.toString(), "door", Map.of(
Property.MODEL.toString(), "Lambo", Property.TYPE.toString(), "door",
Property.PRICE.toString(), 300L); Property.MODEL.toString(), "Lambo",
Property.PRICE.toString(), 300L);
var carProperties = Map.of( var carProperties =
Property.MODEL.toString(), "300SL", Map.of(
Property.PRICE.toString(), 10000L, Property.MODEL.toString(),
Property.PARTS.toString(), List.of(wheelProperties, doorProperties)); "300SL",
Property.PRICE.toString(),
10000L,
Property.PARTS.toString(),
List.of(wheelProperties, doorProperties));
var car = new Car(carProperties); var car = new Car(carProperties);
@@ -70,10 +76,13 @@ public class App {
LOGGER.info("-> model: {}", car.getModel().orElseThrow()); LOGGER.info("-> model: {}", car.getModel().orElseThrow());
LOGGER.info("-> price: {}", car.getPrice().orElseThrow()); LOGGER.info("-> price: {}", car.getPrice().orElseThrow());
LOGGER.info("-> parts: "); LOGGER.info("-> parts: ");
car.getParts().forEach(p -> LOGGER.info("\t{}/{}/{}", car.getParts()
p.getType().orElse(null), .forEach(
p.getModel().orElse(null), p ->
p.getPrice().orElse(null)) LOGGER.info(
); "\t{}/{}/{}",
p.getType().orElse(null),
p.getModel().orElse(null),
p.getPrice().orElse(null)));
} }
} }
@@ -28,15 +28,13 @@ import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /** Document interface. */
* Document interface.
*/
public interface Document { public interface Document {
/** /**
* Puts the value related to the key. * Puts the value related to the key.
* *
* @param key element key * @param key element key
* @param value element value * @param value element value
* @return Void * @return Void
*/ */
@@ -53,7 +51,7 @@ public interface Document {
/** /**
* Gets the stream of child documents. * Gets the stream of child documents.
* *
* @param key element key * @param key element key
* @param constructor constructor of child class * @param constructor constructor of child class
* @return child documents * @return child documents
*/ */
@@ -27,13 +27,10 @@ package com.iluwatar.abstractdocument.domain;
import com.iluwatar.abstractdocument.AbstractDocument; import com.iluwatar.abstractdocument.AbstractDocument;
import java.util.Map; import java.util.Map;
/** /** Car entity. */
* Car entity.
*/
public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts { public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts {
public Car(Map<String, Object> properties) { public Car(Map<String, Object> properties) {
super(properties); super(properties);
} }
} }
@@ -28,13 +28,10 @@ import com.iluwatar.abstractdocument.Document;
import com.iluwatar.abstractdocument.domain.enums.Property; import com.iluwatar.abstractdocument.domain.enums.Property;
import java.util.Optional; import java.util.Optional;
/** /** HasModel trait for static access to 'model' property. */
* HasModel trait for static access to 'model' property.
*/
public interface HasModel extends Document { public interface HasModel extends Document {
default Optional<String> getModel() { default Optional<String> getModel() {
return Optional.ofNullable((String) get(Property.MODEL.toString())); return Optional.ofNullable((String) get(Property.MODEL.toString()));
} }
} }
@@ -28,13 +28,10 @@ import com.iluwatar.abstractdocument.Document;
import com.iluwatar.abstractdocument.domain.enums.Property; import com.iluwatar.abstractdocument.domain.enums.Property;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /** HasParts trait for static access to 'parts' property. */
* HasParts trait for static access to 'parts' property.
*/
public interface HasParts extends Document { public interface HasParts extends Document {
default Stream<Part> getParts() { default Stream<Part> getParts() {
return children(Property.PARTS.toString(), Part::new); return children(Property.PARTS.toString(), Part::new);
} }
} }
@@ -28,13 +28,10 @@ import com.iluwatar.abstractdocument.Document;
import com.iluwatar.abstractdocument.domain.enums.Property; import com.iluwatar.abstractdocument.domain.enums.Property;
import java.util.Optional; import java.util.Optional;
/** /** HasPrice trait for static access to 'price' property. */
* HasPrice trait for static access to 'price' property.
*/
public interface HasPrice extends Document { public interface HasPrice extends Document {
default Optional<Number> getPrice() { default Optional<Number> getPrice() {
return Optional.ofNullable((Number) get(Property.PRICE.toString())); return Optional.ofNullable((Number) get(Property.PRICE.toString()));
} }
} }
@@ -28,13 +28,10 @@ import com.iluwatar.abstractdocument.Document;
import com.iluwatar.abstractdocument.domain.enums.Property; import com.iluwatar.abstractdocument.domain.enums.Property;
import java.util.Optional; import java.util.Optional;
/** /** HasType trait for static access to 'type' property. */
* HasType trait for static access to 'type' property.
*/
public interface HasType extends Document { public interface HasType extends Document {
default Optional<String> getType() { default Optional<String> getType() {
return Optional.ofNullable((String) get(Property.TYPE.toString())); return Optional.ofNullable((String) get(Property.TYPE.toString()));
} }
} }
@@ -27,13 +27,10 @@ package com.iluwatar.abstractdocument.domain;
import com.iluwatar.abstractdocument.AbstractDocument; import com.iluwatar.abstractdocument.AbstractDocument;
import java.util.Map; import java.util.Map;
/** /** Part entity. */
* Part entity.
*/
public class Part extends AbstractDocument implements HasType, HasModel, HasPrice { public class Part extends AbstractDocument implements HasType, HasModel, HasPrice {
public Part(Map<String, Object> properties) { public Part(Map<String, Object> properties) {
super(properties); super(properties);
} }
} }
@@ -24,10 +24,10 @@
*/ */
package com.iluwatar.abstractdocument.domain.enums; package com.iluwatar.abstractdocument.domain.enums;
/** /** Enum To Describe Property type. */
* Enum To Describe Property type.
*/
public enum Property { public enum Property {
PARTS,
PARTS, TYPE, PRICE, MODEL TYPE,
PRICE,
MODEL
} }
@@ -24,16 +24,14 @@
*/ */
package com.iluwatar.abstractdocument; package com.iluwatar.abstractdocument;
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*; /** AbstractDocument test class */
/**
* AbstractDocument test class
*/
class AbstractDocumentTest { class AbstractDocumentTest {
private static final String KEY = "key"; private static final String KEY = "key";
@@ -82,13 +80,16 @@ class AbstractDocumentTest {
@Test @Test
void shouldHandleExceptionDuringConstruction() { void shouldHandleExceptionDuringConstruction() {
Map<String, Object> invalidProperties = null; // Invalid properties, causing NullPointerException Map<String, Object> invalidProperties =
null; // Invalid properties, causing NullPointerException
// Throw null pointer exception // Throw null pointer exception
assertThrows(NullPointerException.class, () -> { assertThrows(
// Attempt to construct a document with invalid properties NullPointerException.class,
new DocumentImplementation(invalidProperties); () -> {
}); // Attempt to construct a document with invalid properties
new DocumentImplementation(invalidProperties);
});
} }
@Test @Test
@@ -97,11 +98,11 @@ class AbstractDocumentTest {
DocumentImplementation nestedDocument = new DocumentImplementation(new HashMap<>()); DocumentImplementation nestedDocument = new DocumentImplementation(new HashMap<>());
nestedDocument.put("nestedKey", "nestedValue"); nestedDocument.put("nestedKey", "nestedValue");
document.put("nested", nestedDocument); document.put("nested", nestedDocument);
// Retrieving the nested document // Retrieving the nested document
DocumentImplementation retrievedNestedDocument = (DocumentImplementation) document.get("nested"); DocumentImplementation retrievedNestedDocument =
(DocumentImplementation) document.get("nested");
assertNotNull(retrievedNestedDocument); assertNotNull(retrievedNestedDocument);
assertEquals("nestedValue", retrievedNestedDocument.get("nestedKey")); assertEquals("nestedValue", retrievedNestedDocument.get("nestedKey"));
@@ -24,25 +24,21 @@
*/ */
package com.iluwatar.abstractdocument; package com.iluwatar.abstractdocument;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** import org.junit.jupiter.api.Test;
* Simple App test
*/ /** Simple App test */
class AppTest { class AppTest {
/** /**
* Issue: Add at least one assertion to this test case. * 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} * <p>Solution: Inserted assertion to check whether the execution of the main method in {@link
* throws an exception. * App} throws an exception.
*/ */
@Test @Test
void shouldExecuteAppWithoutException() { void shouldExecuteAppWithoutException() {
assertDoesNotThrow(() -> App.main(null)); assertDoesNotThrow(() -> App.main(null));
} }
} }
@@ -24,18 +24,16 @@
*/ */
package com.iluwatar.abstractdocument; package com.iluwatar.abstractdocument;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.iluwatar.abstractdocument.domain.Car; import com.iluwatar.abstractdocument.domain.Car;
import com.iluwatar.abstractdocument.domain.Part; import com.iluwatar.abstractdocument.domain.Part;
import com.iluwatar.abstractdocument.domain.enums.Property; import com.iluwatar.abstractdocument.domain.enums.Property;
import org.junit.jupiter.api.Test;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; /** Test for Part and Car */
/**
* Test for Part and Car
*/
class DomainTest { class DomainTest {
private static final String TEST_PART_TYPE = "test-part-type"; private static final String TEST_PART_TYPE = "test-part-type";
@@ -47,11 +45,11 @@ class DomainTest {
@Test @Test
void shouldConstructPart() { void shouldConstructPart() {
var partProperties = Map.of( var partProperties =
Property.TYPE.toString(), TEST_PART_TYPE, Map.of(
Property.MODEL.toString(), TEST_PART_MODEL, Property.TYPE.toString(), TEST_PART_TYPE,
Property.PRICE.toString(), (Object) TEST_PART_PRICE Property.MODEL.toString(), TEST_PART_MODEL,
); Property.PRICE.toString(), (Object) TEST_PART_PRICE);
var part = new Part(partProperties); var part = new Part(partProperties);
assertEquals(TEST_PART_TYPE, part.getType().orElseThrow()); assertEquals(TEST_PART_TYPE, part.getType().orElseThrow());
assertEquals(TEST_PART_MODEL, part.getModel().orElseThrow()); assertEquals(TEST_PART_MODEL, part.getModel().orElseThrow());
@@ -60,15 +58,14 @@ class DomainTest {
@Test @Test
void shouldConstructCar() { void shouldConstructCar() {
var carProperties = Map.of( var carProperties =
Property.MODEL.toString(), TEST_CAR_MODEL, Map.of(
Property.PRICE.toString(), TEST_CAR_PRICE, Property.MODEL.toString(), TEST_CAR_MODEL,
Property.PARTS.toString(), List.of(Map.of(), Map.of()) Property.PRICE.toString(), TEST_CAR_PRICE,
); Property.PARTS.toString(), List.of(Map.of(), Map.of()));
var car = new Car(carProperties); var car = new Car(carProperties);
assertEquals(TEST_CAR_MODEL, car.getModel().orElseThrow()); assertEquals(TEST_CAR_MODEL, car.getModel().orElseThrow());
assertEquals(TEST_CAR_PRICE, car.getPrice().orElseThrow()); assertEquals(TEST_CAR_PRICE, car.getPrice().orElseThrow());
assertEquals(2, car.getParts().count()); assertEquals(2, car.getParts().count());
} }
} }
+8
View File
@@ -34,6 +34,14 @@
</parent> </parent>
<artifactId>abstract-factory</artifactId> <artifactId>abstract-factory</artifactId>
<dependencies> <dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
@@ -74,6 +74,7 @@ public class App implements Runnable {
/** /**
* Creates kingdom. * Creates kingdom.
*
* @param kingdomType type of Kingdom * @param kingdomType type of Kingdom
*/ */
public void createKingdom(final Kingdom.FactoryMaker.KingdomType kingdomType) { public void createKingdom(final Kingdom.FactoryMaker.KingdomType kingdomType) {
@@ -82,4 +83,4 @@ public class App implements Runnable {
kingdom.setCastle(kingdomFactory.createCastle()); kingdom.setCastle(kingdomFactory.createCastle());
kingdom.setArmy(kingdomFactory.createArmy()); kingdom.setArmy(kingdomFactory.createArmy());
} }
} }
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
/** /** Army interface. */
* Army interface.
*/
public interface Army { public interface Army {
String getDescription(); String getDescription();
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
/** /** Castle interface. */
* Castle interface.
*/
public interface Castle { public interface Castle {
String getDescription(); String getDescription();
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
/** /** ElfArmy. */
* ElfArmy.
*/
public class ElfArmy implements Army { public class ElfArmy implements Army {
static final String DESCRIPTION = "This is the elven army!"; static final String DESCRIPTION = "This is the elven army!";
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
/** /** ElfCastle. */
* ElfCastle.
*/
public class ElfCastle implements Castle { public class ElfCastle implements Castle {
static final String DESCRIPTION = "This is the elven castle!"; static final String DESCRIPTION = "This is the elven castle!";
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
/** /** ElfKing. */
* ElfKing.
*/
public class ElfKing implements King { public class ElfKing implements King {
static final String DESCRIPTION = "This is the elven king!"; static final String DESCRIPTION = "This is the elven king!";
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
/** /** ElfKingdomFactory concrete factory. */
* ElfKingdomFactory concrete factory.
*/
public class ElfKingdomFactory implements KingdomFactory { public class ElfKingdomFactory implements KingdomFactory {
@Override @Override
@@ -43,5 +41,4 @@ public class ElfKingdomFactory implements KingdomFactory {
public Army createArmy() { public Army createArmy() {
return new ElfArmy(); return new ElfArmy();
} }
} }
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
/** /** King interface. */
* King interface.
*/
public interface King { public interface King {
String getDescription(); String getDescription();
@@ -27,9 +27,7 @@ package com.iluwatar.abstractfactory;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
/** /** Helper class to manufacture {@link KingdomFactory} beans. */
* Helper class to manufacture {@link KingdomFactory} beans.
*/
@Getter @Getter
@Setter @Setter
public class Kingdom { public class Kingdom {
@@ -38,21 +36,16 @@ public class Kingdom {
private Castle castle; private Castle castle;
private Army army; private Army army;
/** /** The factory of kingdom factories. */
* The factory of kingdom factories.
*/
public static class FactoryMaker { public static class FactoryMaker {
/** /** Enumeration for the different types of Kingdoms. */
* Enumeration for the different types of Kingdoms.
*/
public enum KingdomType { public enum KingdomType {
ELF, ORC ELF,
ORC
} }
/** /** The factory method to create KingdomFactory concrete objects. */
* The factory method to create KingdomFactory concrete objects.
*/
public static KingdomFactory makeFactory(KingdomType type) { public static KingdomFactory makeFactory(KingdomType type) {
return switch (type) { return switch (type) {
case ELF -> new ElfKingdomFactory(); case ELF -> new ElfKingdomFactory();
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
/** /** KingdomFactory factory interface. */
* KingdomFactory factory interface.
*/
public interface KingdomFactory { public interface KingdomFactory {
Castle createCastle(); Castle createCastle();
@@ -34,5 +32,4 @@ public interface KingdomFactory {
King createKing(); King createKing();
Army createArmy(); Army createArmy();
} }
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
/** /** OrcArmy. */
* OrcArmy.
*/
public class OrcArmy implements Army { public class OrcArmy implements Army {
static final String DESCRIPTION = "This is the orc army!"; static final String DESCRIPTION = "This is the orc army!";
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
/** /** OrcCastle. */
* OrcCastle.
*/
public class OrcCastle implements Castle { public class OrcCastle implements Castle {
static final String DESCRIPTION = "This is the orc castle!"; static final String DESCRIPTION = "This is the orc castle!";
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
/** /** OrcKing. */
* OrcKing.
*/
public class OrcKing implements King { public class OrcKing implements King {
static final String DESCRIPTION = "This is the orc king!"; static final String DESCRIPTION = "This is the orc king!";
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
/** /** OrcKingdomFactory concrete factory. */
* OrcKingdomFactory concrete factory.
*/
public class OrcKingdomFactory implements KingdomFactory { public class OrcKingdomFactory implements KingdomFactory {
@Override @Override
@@ -24,14 +24,12 @@
*/ */
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
/** import org.junit.jupiter.api.Test;
* Tests for abstract factory.
*/ /** Tests for abstract factory. */
class AbstractFactoryTest { class AbstractFactoryTest {
private final App app = new App(); private final App app = new App();
@@ -24,18 +24,16 @@
*/ */
package com.iluwatar.abstractfactory; package com.iluwatar.abstractfactory;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** import org.junit.jupiter.api.Test;
* Check whether the execution of the main method in {@link App} throws an exception.
*/ /** Check whether the execution of the main method in {@link App} throws an exception. */
class AppTest { class AppTest {
@Test @Test
void shouldExecuteApplicationWithoutException() { void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{})); assertDoesNotThrow(() -> App.main(new String[] {}));
} }
} }
+8
View File
@@ -34,6 +34,14 @@
</parent> </parent>
<artifactId>active-object</artifactId> <artifactId>active-object</artifactId>
<dependencies> <dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
@@ -29,86 +29,87 @@ import java.util.concurrent.LinkedBlockingQueue;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /** ActiveCreature class is the base of the active object example. */
* ActiveCreature class is the base of the active object example.
*
*/
public abstract class ActiveCreature { public abstract class ActiveCreature {
private static final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName()); private static final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName());
private BlockingQueue<Runnable> requests; private BlockingQueue<Runnable> requests;
private String name; private String name;
private Thread thread; // Thread of execution. private Thread thread; // Thread of execution.
private int status; // status of the thread of execution. private int status; // status of the thread of execution.
/** /** Constructor and initialization. */
* Constructor and initialization.
*/
protected ActiveCreature(String name) { protected ActiveCreature(String name) {
this.name = name; this.name = name;
this.status = 0; this.status = 0;
this.requests = new LinkedBlockingQueue<>(); this.requests = new LinkedBlockingQueue<>();
thread = new Thread(() -> { thread =
boolean infinite = true; new Thread(
while (infinite) { () -> {
try { boolean infinite = true;
requests.take().run(); while (infinite) {
} catch (InterruptedException e) { try {
if (this.status != 0) { requests.take().run();
logger.error("Thread was interrupted. --> {}", e.getMessage()); } catch (InterruptedException e) {
} if (this.status != 0) {
infinite = false; logger.error("Thread was interrupted. --> {}", e.getMessage());
Thread.currentThread().interrupt(); }
} infinite = false;
} Thread.currentThread().interrupt();
}); }
}
});
thread.start(); thread.start();
} }
/** /**
* Eats the porridge. * Eats the porridge.
*
* @throws InterruptedException due to firing a new Runnable. * @throws InterruptedException due to firing a new Runnable.
*/ */
public void eat() throws InterruptedException { public void eat() throws InterruptedException {
requests.put(() -> { requests.put(
logger.info("{} is eating!", name()); () -> {
logger.info("{} has finished eating!", name()); logger.info("{} is eating!", name());
}); logger.info("{} has finished eating!", name());
});
} }
/** /**
* Roam the wastelands. * Roam the wastelands.
*
* @throws InterruptedException due to firing a new Runnable. * @throws InterruptedException due to firing a new Runnable.
*/ */
public void roam() throws InterruptedException { public void roam() throws InterruptedException {
requests.put(() -> requests.put(() -> logger.info("{} has started to roam in the wastelands.", name()));
logger.info("{} has started to roam in the wastelands.", name())
);
} }
/** /**
* Returns the name of the creature. * Returns the name of the creature.
*
* @return the name of the creature. * @return the name of the creature.
*/ */
public String name() { public String name() {
return this.name; return this.name;
} }
/** /**
* Kills the thread of execution. * Kills the thread of execution.
*
* @param status of the thread of execution. 0 == OK, the rest is logging an error. * @param status of the thread of execution. 0 == OK, the rest is logging an error.
*/ */
public void kill(int status) { public void kill(int status) {
this.status = status; this.status = status;
this.thread.interrupt(); this.thread.interrupt();
} }
/** /**
* Returns the status of the thread of execution. * Returns the status of the thread of execution.
*
* @return the status of the thread of execution. * @return the status of the thread of execution.
*/ */
public int getStatus() { public int getStatus() {
@@ -30,17 +30,17 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* The Active Object pattern helps to solve synchronization difficulties without using * The Active Object pattern helps to solve synchronization difficulties without using
* 'synchronized' methods. The active object will contain a thread-safe data structure * 'synchronized' methods. The active object will contain a thread-safe data structure (such as
* (such as BlockingQueue) and use to synchronize method calls by moving the logic of the method * BlockingQueue) and use to synchronize method calls by moving the logic of the method into an
* into an invocator(usually a Runnable) and store it in the DSA. * invocator(usually a Runnable) and store it in the DSA.
* *
* <p>In this example, we fire 20 threads to modify a value in the target class. * <p>In this example, we fire 20 threads to modify a value in the target class.
*/ */
public class App implements Runnable { public class App implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(App.class.getName()); private static final Logger logger = LoggerFactory.getLogger(App.class.getName());
private static final int NUM_CREATURES = 3; private static final int NUM_CREATURES = 3;
/** /**
@@ -48,11 +48,11 @@ public class App implements Runnable {
* *
* @param args command line arguments. * @param args command line arguments.
*/ */
public static void main(String[] args) { public static void main(String[] args) {
var app = new App(); var app = new App();
app.run(); app.run();
} }
@Override @Override
public void run() { public void run() {
List<ActiveCreature> creatures = new ArrayList<>(); List<ActiveCreature> creatures = new ArrayList<>();
@@ -24,14 +24,10 @@
*/ */
package com.iluwatar.activeobject; package com.iluwatar.activeobject;
/** /** An implementation of the ActiveCreature class. */
* An implementation of the ActiveCreature class.
*
*/
public class Orc extends ActiveCreature { public class Orc extends ActiveCreature {
public Orc(String name) { public Orc(String name) {
super(name); super(name);
} }
} }
@@ -27,17 +27,16 @@ package com.iluwatar.activeobject;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test; 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);
}
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);
}
} }
@@ -28,11 +28,10 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
class AppTest { class AppTest {
@Test @Test
void shouldExecuteApplicationWithoutException() { void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{})); assertDoesNotThrow(() -> App.main(new String[] {}));
} }
} }
+8
View File
@@ -34,6 +34,14 @@
</parent> </parent>
<artifactId>acyclic-visitor</artifactId> <artifactId>acyclic-visitor</artifactId>
<dependencies> <dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
@@ -28,6 +28,4 @@ package com.iluwatar.acyclicvisitor;
* All ModemVisitor interface extends all visitor interfaces. This interface provides ease of use * All ModemVisitor interface extends all visitor interfaces. This interface provides ease of use
* when a visitor needs to visit all modem types. * when a visitor needs to visit all modem types.
*/ */
public interface AllModemVisitor extends ZoomVisitor, HayesVisitor { public interface AllModemVisitor extends ZoomVisitor, HayesVisitor {}
}
@@ -37,9 +37,7 @@ package com.iluwatar.acyclicvisitor;
*/ */
public class App { public class App {
/** /** Program's entry point. */
* Program's entry point.
*/
public static void main(String[] args) { public static void main(String[] args) {
var conUnix = new ConfigureForUnixVisitor(); var conUnix = new ConfigureForUnixVisitor();
var conDos = new ConfigureForDosVisitor(); var conDos = new ConfigureForDosVisitor();
@@ -50,6 +48,6 @@ public class App {
hayes.accept(conDos); // Hayes modem with Dos configurator hayes.accept(conDos); // Hayes modem with Dos configurator
zoom.accept(conDos); // Zoom modem with Dos configurator zoom.accept(conDos); // Zoom modem with Dos configurator
hayes.accept(conUnix); // Hayes modem with Unix configurator hayes.accept(conUnix); // Hayes modem with Unix configurator
zoom.accept(conUnix); // Zoom modem with Unix configurator zoom.accept(conUnix); // Zoom modem with Unix configurator
} }
} }
@@ -27,8 +27,7 @@ package com.iluwatar.acyclicvisitor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /**
* ConfigureForDosVisitor class implements both zoom's and hayes' visit method for Dos * ConfigureForDosVisitor class implements both zoom's and hayes' visit method for Dos manufacturer.
* manufacturer.
*/ */
@Slf4j @Slf4j
public class ConfigureForDosVisitor implements AllModemVisitor { public class ConfigureForDosVisitor implements AllModemVisitor {
@@ -37,4 +37,4 @@ public class ConfigureForUnixVisitor implements ZoomVisitor {
public void visit(Zoom zoom) { public void visit(Zoom zoom) {
LOGGER.info(zoom + " used with Unix configurator."); LOGGER.info(zoom + " used with Unix configurator.");
} }
} }
@@ -26,15 +26,11 @@ package com.iluwatar.acyclicvisitor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /** Hayes class implements its accept method. */
* Hayes class implements its accept method.
*/
@Slf4j @Slf4j
public class Hayes implements Modem { public class Hayes implements Modem {
/** /** Accepts all visitors but honors only HayesVisitor. */
* Accepts all visitors but honors only HayesVisitor.
*/
@Override @Override
public void accept(ModemVisitor modemVisitor) { public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof HayesVisitor) { if (modemVisitor instanceof HayesVisitor) {
@@ -42,12 +38,9 @@ public class Hayes implements Modem {
} else { } else {
LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem"); LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem");
} }
} }
/** /** Hayes' modem's toString method. */
* Hayes' modem's toString method.
*/
@Override @Override
public String toString() { public String toString() {
return "Hayes modem"; return "Hayes modem";
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.acyclicvisitor; package com.iluwatar.acyclicvisitor;
/** /** HayesVisitor interface. */
* HayesVisitor interface.
*/
public interface HayesVisitor extends ModemVisitor { public interface HayesVisitor extends ModemVisitor {
void visit(Hayes hayes); void visit(Hayes hayes);
} }
@@ -24,10 +24,7 @@
*/ */
package com.iluwatar.acyclicvisitor; package com.iluwatar.acyclicvisitor;
/** /** //Modem abstract class. converted to an interface */
* //Modem abstract class.
* converted to an interface
*/
public interface Modem { public interface Modem {
void accept(ModemVisitor modemVisitor); void accept(ModemVisitor modemVisitor);
} }
@@ -26,15 +26,11 @@ package com.iluwatar.acyclicvisitor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /** Zoom class implements its accept method. */
* Zoom class implements its accept method.
*/
@Slf4j @Slf4j
public class Zoom implements Modem { public class Zoom implements Modem {
/** /** Accepts all visitors but honors only ZoomVisitor. */
* Accepts all visitors but honors only ZoomVisitor.
*/
@Override @Override
public void accept(ModemVisitor modemVisitor) { public void accept(ModemVisitor modemVisitor) {
if (modemVisitor instanceof ZoomVisitor) { if (modemVisitor instanceof ZoomVisitor) {
@@ -44,9 +40,7 @@ public class Zoom implements Modem {
} }
} }
/** /** Zoom modem's toString method. */
* Zoom modem's toString method.
*/
@Override @Override
public String toString() { public String toString() {
return "Zoom modem"; return "Zoom modem";
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.acyclicvisitor; package com.iluwatar.acyclicvisitor;
/** /** ZoomVisitor interface. */
* ZoomVisitor interface.
*/
public interface ZoomVisitor extends ModemVisitor { public interface ZoomVisitor extends ModemVisitor {
void visit(Zoom zoom); void visit(Zoom zoom);
} }
@@ -24,25 +24,22 @@
*/ */
package com.iluwatar.acyclicvisitor; package com.iluwatar.acyclicvisitor;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** import org.junit.jupiter.api.Test;
* Tests that the Acyclic Visitor example runs without errors.
*/ /** Tests that the Acyclic Visitor example runs without errors. */
class AppTest { class AppTest {
/** /**
* Issue: Add at least one assertion to this test case. * 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} * <p>Solution: Inserted assertion to check whether the execution of the main method in {@link
* throws an exception. * App} throws an exception.
*/ */
@Test @Test
void shouldExecuteApplicationWithoutException() { void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{})); assertDoesNotThrow(() -> App.main(new String[] {}));
} }
} }
@@ -24,14 +24,12 @@
*/ */
package com.iluwatar.acyclicvisitor; package com.iluwatar.acyclicvisitor;
import org.junit.jupiter.api.Test;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
/** import org.junit.jupiter.api.Test;
* Hayes test class
*/ /** Hayes test class */
class HayesTest { class HayesTest {
@Test @Test
@@ -24,16 +24,13 @@
*/ */
package com.iluwatar.acyclicvisitor; package com.iluwatar.acyclicvisitor;
import org.junit.jupiter.api.Test;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
/** import org.junit.jupiter.api.Test;
* Zoom test class
*/ /** Zoom test class */
class ZoomTest { class ZoomTest {
@Test @Test
+8
View File
@@ -34,6 +34,14 @@
</parent> </parent>
<artifactId>adapter</artifactId> <artifactId>adapter</artifactId>
<dependencies> <dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
@@ -37,16 +37,15 @@ package com.iluwatar.adapter;
* <p>The Adapter ({@link FishingBoatAdapter}) converts the interface of the adaptee class ({@link * <p>The Adapter ({@link FishingBoatAdapter}) converts the interface of the adaptee class ({@link
* FishingBoat}) into a suitable one expected by the client ({@link RowingBoat}). * FishingBoat}) into a suitable one expected by the client ({@link RowingBoat}).
* *
* <p>The story of this implementation is this. <br> Pirates are coming! we need a {@link * <p>The story of this implementation is this. <br>
* RowingBoat} to flee! We have a {@link FishingBoat} and our captain. We have no time to make up a * Pirates are coming! we need a {@link RowingBoat} to flee! We have a {@link FishingBoat} and our
* new ship! we need to reuse this {@link FishingBoat}. The captain needs a rowing boat which he can * captain. We have no time to make up a new ship! we need to reuse this {@link FishingBoat}. The
* operate. The spec is in {@link RowingBoat}. We will use the Adapter pattern to reuse {@link * captain needs a rowing boat which he can operate. The spec is in {@link RowingBoat}. We will use
* FishingBoat}. * the Adapter pattern to reuse {@link FishingBoat}.
*/ */
public final class App { public final class App {
private App() { private App() {}
}
/** /**
* Program entry point. * Program entry point.
@@ -29,7 +29,8 @@ import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
/** /**
* The Captain uses {@link RowingBoat} to sail. <br> This is the client in the pattern. * The Captain uses {@link RowingBoat} to sail. <br>
* This is the client in the pattern.
*/ */
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@@ -41,5 +42,4 @@ public final class Captain {
void row() { void row() {
rowingBoat.row(); rowingBoat.row();
} }
} }
@@ -36,5 +36,4 @@ final class FishingBoat {
void sail() { void sail() {
LOGGER.info("The fishing boat is sailing"); LOGGER.info("The fishing boat is sailing");
} }
} }
@@ -25,10 +25,10 @@
package com.iluwatar.adapter; package com.iluwatar.adapter;
/** /**
* The interface expected by the client.<br> A rowing boat is rowed to move. * The interface expected by the client.<br>
* A rowing boat is rowed to move.
*/ */
public interface RowingBoat { public interface RowingBoat {
void row(); void row();
} }
@@ -24,17 +24,15 @@
*/ */
package com.iluwatar.adapter; package com.iluwatar.adapter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
/** import java.util.HashMap;
* Tests for the adapter pattern. import java.util.Map;
*/ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Tests for the adapter pattern. */
class AdapterPatternTest { class AdapterPatternTest {
private Map<String, Object> beans; private Map<String, Object> beans;
@@ -43,9 +41,7 @@ class AdapterPatternTest {
private static final String ROWING_BEAN = "captain"; private static final String ROWING_BEAN = "captain";
/** /** This method runs before the test execution and sets the bean objects in the beans Map. */
* This method runs before the test execution and sets the bean objects in the beans Map.
*/
@BeforeEach @BeforeEach
void setup() { void setup() {
beans = new HashMap<>(); beans = new HashMap<>();
@@ -24,23 +24,17 @@
*/ */
package com.iluwatar.adapter; package com.iluwatar.adapter;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** import org.junit.jupiter.api.Test;
* Tests that Adapter example runs without errors.
*/ /** Tests that Adapter example runs without errors. */
class AppTest { class AppTest {
/** /** Check whether the execution of the main method in {@link App} throws an exception. */
* Check whether the execution of the main method in {@link App}
* throws an exception.
*/
@Test @Test
void shouldExecuteApplicationWithoutException() { void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{})); assertDoesNotThrow(() -> App.main(new String[] {}));
} }
} }
+8
View File
@@ -34,6 +34,14 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>ambassador</artifactId> <artifactId>ambassador</artifactId>
<dependencies> <dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
@@ -28,8 +28,8 @@ package com.iluwatar.ambassador;
* The ambassador pattern creates a helper service that sends network requests on behalf of a * 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. * client. It is often used in cloud-based applications to offload features of a remote service.
* *
* <p>An ambassador service can be thought of as an out-of-process proxy that is co-located with * <p>An ambassador service can be thought of as an out-of-process proxy that is co-located with the
* the client. Similar to the proxy design pattern, the ambassador service provides an interface for * 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 * another remote service. In addition to the interface, the ambassador provides extra functionality
* and features, specifically offloaded common connectivity tasks. This usually consists of * and features, specifically offloaded common connectivity tasks. This usually consists of
* monitoring, logging, routing, security etc. This is extremely useful in legacy applications where * monitoring, logging, routing, security etc. This is extremely useful in legacy applications where
@@ -37,14 +37,11 @@ package com.iluwatar.ambassador;
* capabilities. * capabilities.
* *
* <p>In this example, we will the ({@link ServiceAmbassador}) class represents the ambassador while * <p>In this example, we will the ({@link ServiceAmbassador}) class represents the ambassador while
* the * the ({@link RemoteService}) class represents a remote application.
* ({@link RemoteService}) class represents a remote application.
*/ */
public class App { public class App {
/** /** Entry point. */
* Entry point.
*/
public static void main(String[] args) { public static void main(String[] args) {
var host1 = new Client(); var host1 = new Client();
var host2 = new Client(); var host2 = new Client();
@@ -26,9 +26,7 @@ package com.iluwatar.ambassador;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /** A simple Client. */
* A simple Client.
*/
@Slf4j @Slf4j
public class Client { public class Client {
@@ -29,9 +29,7 @@ import static java.lang.Thread.sleep;
import com.iluwatar.ambassador.util.RandomProvider; import com.iluwatar.ambassador.util.RandomProvider;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /** A remote legacy application represented by a Singleton implementation. */
* A remote legacy application represented by a Singleton implementation.
*/
@Slf4j @Slf4j
public class RemoteService implements RemoteServiceInterface { public class RemoteService implements RemoteServiceInterface {
private static final int THRESHOLD = 200; private static final int THRESHOLD = 200;
@@ -49,9 +47,7 @@ public class RemoteService implements RemoteServiceInterface {
this(Math::random); this(Math::random);
} }
/** /** This constructor is used for testing purposes only. */
* This constructor is used for testing purposes only.
*/
RemoteService(RandomProvider randomProvider) { RemoteService(RandomProvider randomProvider) {
this.randomProvider = randomProvider; this.randomProvider = randomProvider;
} }
@@ -75,7 +71,8 @@ public class RemoteService implements RemoteServiceInterface {
LOGGER.error("Thread sleep state interrupted", e); LOGGER.error("Thread sleep state interrupted", e);
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
return waitTime <= THRESHOLD ? value * 10 return waitTime <= THRESHOLD
? value * 10
: RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue(); : RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue();
} }
} }
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.ambassador; package com.iluwatar.ambassador;
/** /** Interface shared by ({@link RemoteService}) and ({@link ServiceAmbassador}). */
* Interface shared by ({@link RemoteService}) and ({@link ServiceAmbassador}).
*/
interface RemoteServiceInterface { interface RemoteServiceInterface {
long doRemoteFunction(int value); long doRemoteFunction(int value);
@@ -29,17 +29,14 @@ import lombok.Getter;
/** /**
* Holds information regarding the status of the Remote Service. * Holds information regarding the status of the Remote Service.
* *
* <p> This Enum replaces the integer value previously * <p>This Enum replaces the integer value previously stored in {@link RemoteServiceInterface} as
* stored in {@link RemoteServiceInterface} as SonarCloud was identifying * SonarCloud was identifying it as an issue. All test cases have been checked after changes,
* it as an issue. All test cases have been checked after changes, * without failures.
* without failures. </p>
*/ */
public enum RemoteServiceStatus { public enum RemoteServiceStatus {
FAILURE(-1); FAILURE(-1);
@Getter @Getter private final long remoteServiceStatusValue;
private final long remoteServiceStatusValue;
RemoteServiceStatus(long remoteServiceStatusValue) { RemoteServiceStatus(long remoteServiceStatusValue) {
this.remoteServiceStatusValue = remoteServiceStatusValue; this.remoteServiceStatusValue = remoteServiceStatusValue;
@@ -40,8 +40,7 @@ public class ServiceAmbassador implements RemoteServiceInterface {
private static final int RETRIES = 3; private static final int RETRIES = 3;
private static final int DELAY_MS = 3000; private static final int DELAY_MS = 3000;
ServiceAmbassador() { ServiceAmbassador() {}
}
@Override @Override
public long doRemoteFunction(int value) { public long doRemoteFunction(int value) {
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.ambassador.util; package com.iluwatar.ambassador.util;
/** /** An interface for randomness. Useful for testing purposes. */
* An interface for randomness. Useful for testing purposes.
*/
public interface RandomProvider { public interface RandomProvider {
double random(); double random();
} }
@@ -24,25 +24,22 @@
*/ */
package com.iluwatar.ambassador; package com.iluwatar.ambassador;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** import org.junit.jupiter.api.Test;
* Application test
*/ /** Application test */
class AppTest { class AppTest {
/** /**
* Issue: Add at least one assertion to this test case. * 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} * <p>Solution: Inserted assertion to check whether the execution of the main method in {@link
* throws an exception. * App} throws an exception.
*/ */
@Test @Test
void shouldExecuteApplicationWithoutException() { void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{})); assertDoesNotThrow(() -> App.main(new String[] {}));
} }
} }
@@ -24,13 +24,11 @@
*/ */
package com.iluwatar.ambassador; package com.iluwatar.ambassador;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
/** import org.junit.jupiter.api.Test;
* Test for {@link Client}
*/ /** Test for {@link Client} */
class ClientTest { class ClientTest {
@Test @Test
@@ -38,6 +36,7 @@ class ClientTest {
Client client = new Client(); Client client = new Client();
var result = client.useService(10); var result = client.useService(10);
assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue()); assertTrue(
result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue());
} }
} }
@@ -29,9 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import com.iluwatar.ambassador.util.RandomProvider; import com.iluwatar.ambassador.util.RandomProvider;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
/** /** Test for {@link RemoteService} */
* Test for {@link RemoteService}
*/
class RemoteServiceTest { class RemoteServiceTest {
@Test @Test
@@ -24,18 +24,17 @@
*/ */
package com.iluwatar.ambassador; package com.iluwatar.ambassador;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
/** import org.junit.jupiter.api.Test;
* Test for {@link ServiceAmbassador}
*/ /** Test for {@link ServiceAmbassador} */
class ServiceAmbassadorTest { class ServiceAmbassadorTest {
@Test @Test
void test() { void test() {
long result = new ServiceAmbassador().doRemoteFunction(10); long result = new ServiceAmbassador().doRemoteFunction(10);
assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue()); assertTrue(
result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue());
} }
} }
+2 -2
View File
@@ -45,8 +45,8 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit</artifactId> <artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -28,9 +28,8 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
/** /**
* This layer translates communications between the two systems, * This layer translates communications between the two systems, allowing one system to remain
* allowing one system to remain unchanged while the other can avoid compromising * unchanged while the other can avoid compromising its design and technological approach.
* its design and technological approach.
*/ */
@SpringBootApplication @SpringBootApplication
public class App { public class App {
@@ -23,30 +23,26 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
/** /**
* Context and problem * Context and problem Most applications rely on other systems for some data or functionality. For
* Most applications rely on other systems for some data or functionality. * example, when a legacy application is migrated to a modern system, it may still need existing
* For example, when a legacy application is migrated to a modern system, * legacy resources. New features must be able to call the legacy system. This is especially true of
* it may still need existing legacy resources. New features must be able to call the legacy system. * gradual migrations, where different features of a larger application are moved to a modern system
* This is especially true of gradual migrations, * over time.
* where different features of a larger application are moved to a modern system over time.
* *
* <p>Often these legacy systems suffer from quality issues such as convoluted data schemas * <p>Often these legacy systems suffer from quality issues such as convoluted data schemas or
* or obsolete APIs. * obsolete APIs. The features and technologies used in legacy systems can vary widely from more
* The features and technologies used in legacy systems can vary widely from more modern systems. * modern systems. To interoperate with the legacy system, the new application may need to support
* To interoperate with the legacy system, * outdated infrastructure, protocols, data models, APIs, or other features that you wouldn't
* the new application may need to support outdated infrastructure, protocols, data models, APIs, * otherwise put into a modern application.
* or other features that you wouldn't otherwise put into a modern application.
* *
* <p>Maintaining access between new and legacy systems can force the new system to adhere to * <p>Maintaining access between new and legacy systems can force the new system to adhere to at
* at least some of the legacy system's APIs or other semantics. * least some of the legacy system's APIs or other semantics. When these legacy features have
* When these legacy features have quality issues, supporting them "corrupts" what might * quality issues, supporting them "corrupts" what might otherwise be a cleanly designed modern
* otherwise be a cleanly designed modern application. * application. Similar issues can arise with any external system that your development team doesn't
* Similar issues can arise with any external system that your development team doesn't control, * control, not just legacy systems.
* not just legacy systems.
* *
* <p>Solution Isolate the different subsystems by placing an anti-corruption layer between them. * <p>Solution Isolate the different subsystems by placing an anti-corruption layer between them.
* This layer translates communications between the two systems, * This layer translates communications between the two systems, allowing one system to remain
* allowing one system to remain unchanged while the other can avoid compromising * unchanged while the other can avoid compromising its design and technological approach.
* its design and technological approach.
*/ */
package com.iluwatar.corruption; package com.iluwatar.corruption;
@@ -33,36 +33,34 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** /**
* The class represents an anti-corruption layer. * The class represents an anti-corruption layer. The main purpose of the class is to provide a
* The main purpose of the class is to provide a layer between the modern and legacy systems. * layer between the modern and legacy systems. The class is responsible for converting the data
* The class is responsible for converting the data from one system to another * from one system to another decoupling the systems to each other
* decoupling the systems to each other
* *
* <p>It allows using one system a domain model of the other system * <p>It allows using one system a domain model of the other system without changing the domain
* without changing the domain model of the system. * model of the system.
*/ */
@Service @Service
public class AntiCorruptionLayer { public class AntiCorruptionLayer {
@Autowired @Autowired private LegacyShop legacyShop;
private LegacyShop legacyShop;
/** /**
* The method converts the order from the legacy system to the modern system. * The method converts the order from the legacy system to the modern system.
*
* @param id the id of the order * @param id the id of the order
* @return the order in the modern system * @return the order in the modern system
*/ */
public Optional<ModernOrder> findOrderInLegacySystem(String id) { public Optional<ModernOrder> findOrderInLegacySystem(String id) {
return legacyShop.findOrder(id).map(o -> return legacyShop
new ModernOrder( .findOrder(id)
o.getId(), .map(
new Customer(o.getCustomer()), o ->
new Shipment(o.getItem(), o.getQty(), o.getPrice()), new ModernOrder(
"" o.getId(),
) new Customer(o.getCustomer()),
); new Shipment(o.getItem(), o.getQty(), o.getPrice()),
""));
} }
} }
@@ -29,6 +29,7 @@ import java.util.Optional;
/** /**
* The class represents a data store for the modern system. * The class represents a data store for the modern system.
*
* @param <V> the type of the value stored in the data store * @param <V> the type of the value stored in the data store
*/ */
public abstract class DataStore<V> { public abstract class DataStore<V> {
@@ -44,6 +45,5 @@ public abstract class DataStore<V> {
public Optional<V> put(String key, V value) { public Optional<V> put(String key, V value) {
return Optional.ofNullable(inner.put(key, value)); return Optional.ofNullable(inner.put(key, value));
} }
} }
@@ -24,9 +24,7 @@
*/ */
package com.iluwatar.corruption.system; package com.iluwatar.corruption.system;
/** /** The class represents a general exception for the shop. */
* The class represents a general exception for the shop.
*/
public class ShopException extends Exception { public class ShopException extends Exception {
public ShopException(String message) { public ShopException(String message) {
super(message); super(message);
@@ -41,9 +39,12 @@ public class ShopException extends Exception {
* @throws ShopException the exception * @throws ShopException the exception
*/ */
public static ShopException throwIncorrectData(String lhs, String rhs) throws ShopException { public static ShopException throwIncorrectData(String lhs, String rhs) throws ShopException {
throw new ShopException("The order is already placed but has an incorrect data:\n" throw new ShopException(
+ "Incoming order: " + lhs + "\n" "The order is already placed but has an incorrect data:\n"
+ "Existing order: " + rhs); + "Incoming order: "
+ lhs
+ "\n"
+ "Existing order: "
+ rhs);
} }
} }
@@ -28,8 +28,8 @@ import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
/** /**
* The class represents an order in the legacy system. * The class represents an order in the legacy system. The class is used by the legacy system to
* The class is used by the legacy system to store the data. * store the data.
*/ */
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@@ -28,10 +28,8 @@ import com.iluwatar.corruption.system.DataStore;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** /**
* The class represents a data store for the legacy system. * The class represents a data store for the legacy system. The class is used by the legacy system
* The class is used by the legacy system to store the data. * to store the data.
*/ */
@Service @Service
public class LegacyStore extends DataStore<LegacyOrder> { public class LegacyStore extends DataStore<LegacyOrder> {}
}
@@ -27,9 +27,7 @@ package com.iluwatar.corruption.system.modern;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
/** /** The class represents a customer in the modern system. */
* The class represents a customer in the modern system.
*/
@Data @Data
@AllArgsConstructor @AllArgsConstructor
public class Customer { public class Customer {
@@ -27,9 +27,7 @@ package com.iluwatar.corruption.system.modern;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
/** /** The class represents an order in the modern system. */
* The class represents an order in the modern system.
*/
@Data @Data
@AllArgsConstructor @AllArgsConstructor
public class ModernOrder { public class ModernOrder {
@@ -39,6 +37,4 @@ public class ModernOrder {
private Shipment shipment; private Shipment shipment;
private String extra; private String extra;
} }
@@ -31,20 +31,18 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** /**
* The class represents a modern shop system. * The class represents a modern shop system. The main purpose of the class is to place orders and
* The main purpose of the class is to place orders and find orders. * find orders.
*/ */
@Service @Service
public class ModernShop { public class ModernShop {
@Autowired @Autowired private ModernStore store;
private ModernStore store;
@Autowired @Autowired private AntiCorruptionLayer acl;
private AntiCorruptionLayer acl;
/** /**
* Places the order in the modern system. * Places the order in the modern system. If the order is already present in the legacy system,
* If the order is already present in the legacy system, then no need to place it again. * then no need to place it again.
*/ */
public void placeOrder(ModernOrder order) throws ShopException { public void placeOrder(ModernOrder order) throws ShopException {
@@ -62,9 +60,7 @@ public class ModernShop {
} }
} }
/** /** Finds the order in the modern system. */
* Finds the order in the modern system.
*/
public Optional<ModernOrder> findOrder(String orderId) { public Optional<ModernOrder> findOrder(String orderId) {
return store.get(orderId); return store.get(orderId);
} }
@@ -27,10 +27,6 @@ package com.iluwatar.corruption.system.modern;
import com.iluwatar.corruption.system.DataStore; import com.iluwatar.corruption.system.DataStore;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** /** The class represents a data store for the modern system. */
* The class represents a data store for the modern system.
*/
@Service @Service
public class ModernStore extends DataStore<ModernOrder> { public class ModernStore extends DataStore<ModernOrder> {}
}
@@ -28,8 +28,8 @@ import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
/** /**
* The class represents a shipment in the modern system. * The class represents a shipment in the modern system. The class is used by the modern system to
* The class is used by the modern system to store the data. * store the data.
*/ */
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@@ -24,88 +24,73 @@
*/ */
package com.iluwatar.corruption.system; 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.LegacyOrder;
import com.iluwatar.corruption.system.legacy.LegacyShop; import com.iluwatar.corruption.system.legacy.LegacyShop;
import com.iluwatar.corruption.system.modern.Customer; import com.iluwatar.corruption.system.modern.Customer;
import com.iluwatar.corruption.system.modern.ModernOrder; import com.iluwatar.corruption.system.modern.ModernOrder;
import com.iluwatar.corruption.system.modern.ModernShop; import com.iluwatar.corruption.system.modern.ModernShop;
import com.iluwatar.corruption.system.modern.Shipment; import com.iluwatar.corruption.system.modern.Shipment;
import org.junit.Test; import java.util.Optional;
import org.junit.runner.RunWith; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.Optional; @ExtendWith(SpringExtension.class)
import static org.junit.jupiter.api.Assertions.*;
@RunWith(SpringRunner.class)
@SpringBootTest @SpringBootTest
public class AntiCorruptionLayerTest { public class AntiCorruptionLayerTest {
@Autowired @Autowired private LegacyShop legacyShop;
private LegacyShop legacyShop;
@Autowired @Autowired private ModernShop modernShop;
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<LegacyOrder> legacyOrderWithIdOne = legacyShop.findOrder("1");
assertEquals(Optional.of(legacyOrder), legacyOrderWithIdOne);
/** // a new order (or maybe just the same order) appears in the modern shop
* Test the anti-corruption layer. ModernOrder modernOrder =
* Main intention is to demonstrate how the anti-corruption layer works. new ModernOrder("1", new Customer("addr1"), new Shipment("item1", 1, 1), "");
* <p> // the system places it, but it checks if there is an order with the same id in the legacy shop.
* The 2 shops (modern and legacy) should operate independently and in the same time synchronize the data. modernShop.placeOrder(modernOrder);
* To avoid corrupting the domain models of the 2 shops, we use an anti-corruption layer
* that transforms one model to another under the hood.
*
*/
@Test
public void antiCorruptionLayerTest() throws ShopException {
// a new order comes to the legacy shop. Optional<ModernOrder> modernOrderWithIdOne = modernShop.findOrder("1");
LegacyOrder legacyOrder = new LegacyOrder("1", "addr1", "item1", 1, 1); // there is no new order placed since there is already an order with the same id in the legacy
// place the order in the legacy shop. // shop.
legacyShop.placeOrder(legacyOrder); assertTrue(modernOrderWithIdOne.isEmpty());
// the order is placed as usual since there is no other orders with the id in the both systems. }
Optional<LegacyOrder> 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), ""); * Test the anti-corruption layer when a conflict occurs between systems. This test ensures that
* an exception is thrown when conflicting orders are placed.
// the system places it, but it checks if there is an order with the same id in the legacy shop. */
modernShop.placeOrder(modernOrder); @Test
public void antiCorruptionLayerWithExTest() throws ShopException {
Optional<ModernOrder> modernOrderWithIdOne = modernShop.findOrder("1"); // a new order comes to the legacy shop.
// there is no new order placed since there is already an order with the same id in the legacy shop. LegacyOrder legacyOrder = new LegacyOrder("1", "addr1", "item1", 1, 1);
assertTrue(modernOrderWithIdOne.isEmpty()); // 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<LegacyOrder> legacyOrderWithIdOne = legacyShop.findOrder("1");
* Test the anti-corruption layer. assertEquals(Optional.of(legacyOrder), legacyOrderWithIdOne);
* Main intention is to demonstrate how the anti-corruption layer works. // a new order but with the same id and different data appears in the modern shop
* <p> ModernOrder modernOrder =
* This test tests the anti-corruption layer from the rule the orders should be the same in the both systems. 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));
@Test(expected = ShopException.class) }
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<LegacyOrder> 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.
modernShop.placeOrder(modernOrder);
}
}
@@ -35,12 +35,12 @@ public class Cash {
private int amount; private int amount;
//plus // plus
void plus(int addend) { void plus(int addend) {
amount += addend; amount += addend;
} }
//minus // minus
boolean minus(int subtrahend) { boolean minus(int subtrahend) {
if (amount >= subtrahend) { if (amount >= subtrahend) {
amount -= subtrahend; amount -= subtrahend;
@@ -50,7 +50,7 @@ public class Cash {
} }
} }
//count // count
int count() { int count() {
return amount; return amount;
} }
@@ -35,8 +35,11 @@ import org.junit.jupiter.api.Test;
* tests, so they're easier to read, maintain and enhance. * tests, so they're easier to read, maintain and enhance.
* *
* <p>It breaks tests down into three clear and distinct steps: * <p>It breaks tests down into three clear and distinct steps:
*
* <p>1. Arrange: Perform the setup and initialization required for the test. * <p>1. Arrange: Perform the setup and initialization required for the test.
*
* <p>2. Act: Take action(s) required for the test. * <p>2. Act: Take action(s) required for the test.
*
* <p>3. Assert: Verify the outcome(s) of the test. * <p>3. Assert: Verify the outcome(s) of the test.
* *
* <p>This pattern has several significant benefits. It creates a clear separation between a test's * <p>This pattern has several significant benefits. It creates a clear separation between a test's
@@ -48,53 +51,52 @@ import org.junit.jupiter.api.Test;
* clearly about the three steps your test will perform. But it makes tests more natural to write at * 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. * the same time since you already have an outline.
* *
* <p>In ({@link CashAAATest}) we have four test methods. Each of them has only one reason to * <p>In ({@link CashAAATest}) we have four test methods. Each of them has only one reason to change
* change and one reason to fail. In a large and complicated code base, tests that honor the single * and one reason to fail. In a large and complicated code base, tests that honor the single
* responsibility principle are much easier to troubleshoot. * responsibility principle are much easier to troubleshoot.
*/ */
class CashAAATest { class CashAAATest {
@Test @Test
void testPlus() { void testPlus() {
//Arrange // Arrange
var cash = new Cash(3); var cash = new Cash(3);
//Act // Act
cash.plus(4); cash.plus(4);
//Assert // Assert
assertEquals(7, cash.count()); assertEquals(7, cash.count());
} }
@Test @Test
void testMinus() { void testMinus() {
//Arrange // Arrange
var cash = new Cash(8); var cash = new Cash(8);
//Act // Act
var result = cash.minus(5); var result = cash.minus(5);
//Assert // Assert
assertTrue(result); assertTrue(result);
assertEquals(3, cash.count()); assertEquals(3, cash.count());
} }
@Test @Test
void testInsufficientMinus() { void testInsufficientMinus() {
//Arrange // Arrange
var cash = new Cash(1); var cash = new Cash(1);
//Act // Act
var result = cash.minus(6); var result = cash.minus(6);
//Assert // Assert
assertFalse(result); assertFalse(result);
assertEquals(1, cash.count()); assertEquals(1, cash.count());
} }
@Test @Test
void testUpdate() { void testUpdate() {
//Arrange // Arrange
var cash = new Cash(5); var cash = new Cash(5);
//Act // Act
cash.plus(6); cash.plus(6);
var result = cash.minus(3); var result = cash.minus(3);
//Assert // Assert
assertTrue(result); assertTrue(result);
assertEquals(8, cash.count()); assertEquals(8, cash.count());
} }
@@ -37,23 +37,22 @@ import org.junit.jupiter.api.Test;
* single responsibility principle. If this test method failed after a small code change, it might * single responsibility principle. If this test method failed after a small code change, it might
* take some digging to discover why. * take some digging to discover why.
*/ */
class CashAntiAAATest { class CashAntiAAATest {
@Test @Test
void testCash() { void testCash() {
//initialize // initialize
var cash = new Cash(3); var cash = new Cash(3);
//test plus // test plus
cash.plus(4); cash.plus(4);
assertEquals(7, cash.count()); assertEquals(7, cash.count());
//test minus // test minus
cash = new Cash(8); cash = new Cash(8);
assertTrue(cash.minus(5)); assertTrue(cash.minus(5));
assertEquals(3, cash.count()); assertEquals(3, cash.count());
assertFalse(cash.minus(6)); assertFalse(cash.minus(6));
assertEquals(3, cash.count()); assertEquals(3, cash.count());
//test update // test update
cash.plus(5); cash.plus(5);
assertTrue(cash.minus(5)); assertTrue(cash.minus(5));
assertEquals(3, cash.count()); assertEquals(3, cash.count());
+8
View File
@@ -34,6 +34,14 @@
</parent> </parent>
<artifactId>async-method-invocation</artifactId> <artifactId>async-method-invocation</artifactId>
<dependencies> <dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
@@ -30,15 +30,15 @@ import lombok.extern.slf4j.Slf4j;
/** /**
* In this example, we are launching space rockets and deploying lunar rovers. * In this example, we are launching space rockets and deploying lunar rovers.
* *
* <p>The application demonstrates the async method invocation pattern. The key parts of the * <p>The application demonstrates the async method invocation pattern. The key parts of the pattern
* pattern are <code>AsyncResult</code> which is an intermediate container for an asynchronously * are <code>AsyncResult</code> which is an intermediate container for an asynchronously evaluated
* evaluated value, <code>AsyncCallback</code> which can be provided to be executed on task * value, <code>AsyncCallback</code> which can be provided to be executed on task completion and
* completion and <code>AsyncExecutor</code> that manages the execution of the async tasks. * <code>AsyncExecutor</code> that manages the execution of the async tasks.
* *
* <p>The main method shows example flow of async invocations. The main thread starts multiple * <p>The main method shows example flow of async invocations. The main thread starts multiple tasks
* tasks with variable durations and then continues its own work. When the main thread has done it's * with variable durations and then continues its own work. When the main thread has done it's job
* job it collects the results of the async tasks. Two of the tasks are handled with callbacks, * it collects the results of the async tasks. Two of the tasks are handled with callbacks, meaning
* meaning the callbacks are executed immediately when the tasks complete. * the callbacks are executed immediately when the tasks complete.
* *
* <p>Noteworthy difference of thread usage between the async results and callbacks is that the * <p>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 * async results are collected in the main thread but the callbacks are executed within the worker
@@ -62,10 +62,7 @@ public class App {
private static final String ROCKET_LAUNCH_LOG_PATTERN = "Space rocket <%s> launched successfully"; private static final String ROCKET_LAUNCH_LOG_PATTERN = "Space rocket <%s> launched successfully";
/** /** Program entry point. */
* Program entry point.
*/
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
// construct a new executor that will run async tasks // construct a new executor that will run async tasks
var executor = new ThreadAsyncExecutor(); var executor = new ThreadAsyncExecutor();
@@ -74,8 +71,8 @@ public class App {
final var asyncResult1 = executor.startProcess(lazyval(10, 500)); final var asyncResult1 = executor.startProcess(lazyval(10, 500));
final var asyncResult2 = executor.startProcess(lazyval("test", 300)); final var asyncResult2 = executor.startProcess(lazyval("test", 300));
final var asyncResult3 = executor.startProcess(lazyval(50L, 700)); final var asyncResult3 = executor.startProcess(lazyval(50L, 700));
final var asyncResult4 = executor.startProcess(lazyval(20, 400), final var asyncResult4 =
callback("Deploying lunar rover")); executor.startProcess(lazyval(20, 400), callback("Deploying lunar rover"));
final var asyncResult5 = final var asyncResult5 =
executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover")); executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover"));
@@ -99,7 +96,7 @@ public class App {
/** /**
* Creates a callable that lazily evaluates to given value with artificial delay. * Creates a callable that lazily evaluates to given value with artificial delay.
* *
* @param value value to evaluate * @param value value to evaluate
* @param delayMillis artificial delay in milliseconds * @param delayMillis artificial delay in milliseconds
* @return new callable for lazy evaluation * @return new callable for lazy evaluation
*/ */
@@ -27,9 +27,7 @@ package com.iluwatar.async.method.invocation;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
/** /** AsyncExecutor interface. */
* AsyncExecutor interface.
*/
public interface AsyncExecutor { public interface AsyncExecutor {
/** /**
@@ -44,7 +42,7 @@ public interface AsyncExecutor {
* Starts processing of an async task. Returns immediately with async result. Executes callback * Starts processing of an async task. Returns immediately with async result. Executes callback
* when the task is completed. * when the task is completed.
* *
* @param task task to be executed asynchronously * @param task task to be executed asynchronously
* @param callback callback to be executed on task completion * @param callback callback to be executed on task completion
* @return async result for the task * @return async result for the task
*/ */
@@ -56,7 +54,7 @@ public interface AsyncExecutor {
* *
* @param asyncResult async result of a task * @param asyncResult async result of a task
* @return evaluated value of the completed task * @return evaluated value of the completed task
* @throws ExecutionException if execution has failed, containing the root cause * @throws ExecutionException if execution has failed, containing the root cause
* @throws InterruptedException if the execution is interrupted * @throws InterruptedException if the execution is interrupted
*/ */
<T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException; <T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException;
@@ -44,7 +44,7 @@ public interface AsyncResult<T> {
* Gets the value of completed async task. * Gets the value of completed async task.
* *
* @return evaluated value or throws ExecutionException if execution has failed * @return evaluated value or throws ExecutionException if execution has failed
* @throws ExecutionException if execution has failed, containing the root cause * @throws ExecutionException if execution has failed, containing the root cause
* @throws IllegalStateException if execution is not completed * @throws IllegalStateException if execution is not completed
*/ */
T getValue() throws ExecutionException; T getValue() throws ExecutionException;
@@ -24,19 +24,14 @@
*/ */
package com.iluwatar.async.method.invocation; package com.iluwatar.async.method.invocation;
import java.util.Optional;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger; 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 { public class ThreadAsyncExecutor implements AsyncExecutor {
/** /** Index for thread naming. */
* Index for thread naming.
*/
private final AtomicInteger idx = new AtomicInteger(0); private final AtomicInteger idx = new AtomicInteger(0);
@Override @Override
@@ -47,19 +42,22 @@ public class ThreadAsyncExecutor implements AsyncExecutor {
@Override @Override
public <T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback) { public <T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback) {
var result = new CompletableResult<>(callback); var result = new CompletableResult<>(callback);
new Thread(() -> { new Thread(
try { () -> {
result.setValue(task.call()); try {
} catch (Exception ex) { result.setValue(task.call());
result.setException(ex); } catch (Exception ex) {
} result.setException(ex);
}, "executor-" + idx.incrementAndGet()).start(); }
},
"executor-" + idx.incrementAndGet())
.start();
return result; return result;
} }
@Override @Override
public <T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, public <T> T endProcess(AsyncResult<T> asyncResult)
InterruptedException { throws ExecutionException, InterruptedException {
if (!asyncResult.isCompleted()) { if (!asyncResult.isCompleted()) {
asyncResult.await(); asyncResult.await();
} }
@@ -24,26 +24,22 @@
*/ */
package com.iluwatar.async.method.invocation; package com.iluwatar.async.method.invocation;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/** import org.junit.jupiter.api.Test;
* Application test
*/ /** Application test */
class AppTest { class AppTest {
/** /**
* Issue: Add at least one assertion to this test case. * 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} * <p>Solution: Inserted assertion to check whether the execution of the main method in {@link
* throws an exception. * App} throws an exception.
*/ */
@Test @Test
void shouldExecuteApplicationWithoutException() { void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{})); assertDoesNotThrow(() -> App.main(new String[] {}));
} }
} }
@@ -33,7 +33,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.mockito.internal.verification.VerificationModeFactory.times; import static org.mockito.internal.verification.VerificationModeFactory.times;
import java.util.Optional;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@@ -43,49 +42,43 @@ import org.mockito.Captor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
/** /** ThreadAsyncExecutorTest */
* ThreadAsyncExecutorTest
*
*/
class ThreadAsyncExecutorTest { class ThreadAsyncExecutorTest {
@Captor @Captor private ArgumentCaptor<Exception> exceptionCaptor;
private ArgumentCaptor<Exception> exceptionCaptor;
@Mock @Mock private Callable<Object> task;
private Callable<Object> task;
@Mock @Mock private AsyncCallback<Object> callback;
private AsyncCallback<Object> callback;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
MockitoAnnotations.openMocks(this); MockitoAnnotations.openMocks(this);
} }
/** /** Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)} */
* Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)}
*/
@Test @Test
void testSuccessfulTaskWithoutCallback() { void testSuccessfulTaskWithoutCallback() {
assertTimeout(ofMillis(3000), () -> { assertTimeout(
// Instantiate a new executor and start a new 'null' task ... ofMillis(3000),
final var executor = new ThreadAsyncExecutor(); () -> {
// Instantiate a new executor and start a new 'null' task ...
final var executor = new ThreadAsyncExecutor();
final var result = new Object(); final var result = new Object();
when(task.call()).thenReturn(result); when(task.call()).thenReturn(result);
final var asyncResult = executor.startProcess(task); final var asyncResult = executor.startProcess(task);
assertNotNull(asyncResult); assertNotNull(asyncResult);
asyncResult.await(); // Prevent timing issues, and wait until the result is available asyncResult.await(); // Prevent timing issues, and wait until the result is available
assertTrue(asyncResult.isCompleted()); assertTrue(asyncResult.isCompleted());
// Our task should only execute once ... // Our task should only execute once ...
verify(task, times(1)).call(); verify(task, times(1)).call();
// ... and the result should be exactly the same object // ... and the result should be exactly the same object
assertSame(result, asyncResult.getValue()); assertSame(result, asyncResult.getValue());
}); });
} }
/** /**
@@ -94,28 +87,30 @@ class ThreadAsyncExecutorTest {
*/ */
@Test @Test
void testSuccessfulTaskWithCallback() { void testSuccessfulTaskWithCallback() {
assertTimeout(ofMillis(3000), () -> { assertTimeout(
// Instantiate a new executor and start a new 'null' task ... ofMillis(3000),
final var executor = new ThreadAsyncExecutor(); () -> {
// Instantiate a new executor and start a new 'null' task ...
final var executor = new ThreadAsyncExecutor();
final var result = new Object(); final var result = new Object();
when(task.call()).thenReturn(result); when(task.call()).thenReturn(result);
final var asyncResult = executor.startProcess(task, callback); final var asyncResult = executor.startProcess(task, callback);
assertNotNull(asyncResult); assertNotNull(asyncResult);
asyncResult.await(); // Prevent timing issues, and wait until the result is available asyncResult.await(); // Prevent timing issues, and wait until the result is available
assertTrue(asyncResult.isCompleted()); assertTrue(asyncResult.isCompleted());
// Our task should only execute once ... // Our task should only execute once ...
verify(task, times(1)).call(); verify(task, times(1)).call();
// ... same for the callback, we expect our object // ... same for the callback, we expect our object
verify(callback, times(1)).onComplete(eq(result)); verify(callback, times(1)).onComplete(eq(result));
verify(callback, times(0)).onError(exceptionCaptor.capture()); verify(callback, times(0)).onError(exceptionCaptor.capture());
// ... and the result should be exactly the same object // ... and the result should be exactly the same object
assertSame(result, asyncResult.getValue()); assertSame(result, asyncResult.getValue());
}); });
} }
/** /**
@@ -124,38 +119,43 @@ class ThreadAsyncExecutorTest {
*/ */
@Test @Test
void testLongRunningTaskWithoutCallback() { void testLongRunningTaskWithoutCallback() {
assertTimeout(ofMillis(5000), () -> { assertTimeout(
// Instantiate a new executor and start a new 'null' task ... ofMillis(5000),
final var executor = new ThreadAsyncExecutor(); () -> {
// Instantiate a new executor and start a new 'null' task ...
final var executor = new ThreadAsyncExecutor();
final var result = new Object(); final var result = new Object();
when(task.call()).thenAnswer(i -> { when(task.call())
Thread.sleep(1500); .thenAnswer(
return result; i -> {
}); Thread.sleep(1500);
return result;
});
final var asyncResult = executor.startProcess(task); final var asyncResult = executor.startProcess(task);
assertNotNull(asyncResult); assertNotNull(asyncResult);
assertFalse(asyncResult.isCompleted()); assertFalse(asyncResult.isCompleted());
try { try {
asyncResult.getValue(); asyncResult.getValue();
fail("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task"); fail(
} catch (IllegalStateException e) { "Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task");
assertNotNull(e.getMessage()); } catch (IllegalStateException e) {
} assertNotNull(e.getMessage());
}
// Our task should only execute once, but it can take a while ... // Our task should only execute once, but it can take a while ...
verify(task, timeout(3000).times(1)).call(); verify(task, timeout(3000).times(1)).call();
// Prevent timing issues, and wait until the result is available // Prevent timing issues, and wait until the result is available
asyncResult.await(); asyncResult.await();
assertTrue(asyncResult.isCompleted()); assertTrue(asyncResult.isCompleted());
verifyNoMoreInteractions(task); verifyNoMoreInteractions(task);
// ... and the result should be exactly the same object // ... and the result should be exactly the same object
assertSame(result, asyncResult.getValue()); assertSame(result, asyncResult.getValue());
}); });
} }
/** /**
@@ -164,42 +164,47 @@ class ThreadAsyncExecutorTest {
*/ */
@Test @Test
void testLongRunningTaskWithCallback() { void testLongRunningTaskWithCallback() {
assertTimeout(ofMillis(5000), () -> { assertTimeout(
// Instantiate a new executor and start a new 'null' task ... ofMillis(5000),
final var executor = new ThreadAsyncExecutor(); () -> {
// Instantiate a new executor and start a new 'null' task ...
final var executor = new ThreadAsyncExecutor();
final var result = new Object(); final var result = new Object();
when(task.call()).thenAnswer(i -> { when(task.call())
Thread.sleep(1500); .thenAnswer(
return result; i -> {
}); Thread.sleep(1500);
return result;
});
final var asyncResult = executor.startProcess(task, callback); final var asyncResult = executor.startProcess(task, callback);
assertNotNull(asyncResult); assertNotNull(asyncResult);
assertFalse(asyncResult.isCompleted()); assertFalse(asyncResult.isCompleted());
verifyNoMoreInteractions(callback); verifyNoMoreInteractions(callback);
try { try {
asyncResult.getValue(); asyncResult.getValue();
fail("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task"); fail(
} catch (IllegalStateException e) { "Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task");
assertNotNull(e.getMessage()); } catch (IllegalStateException e) {
} assertNotNull(e.getMessage());
}
// Our task should only execute once, but it can take a while ... // Our task should only execute once, but it can take a while ...
verify(task, timeout(3000).times(1)).call(); verify(task, timeout(3000).times(1)).call();
verify(callback, timeout(3000).times(1)).onComplete(eq(result)); verify(callback, timeout(3000).times(1)).onComplete(eq(result));
verify(callback, times(0)).onError(isA(Exception.class)); verify(callback, times(0)).onError(isA(Exception.class));
// Prevent timing issues, and wait until the result is available // Prevent timing issues, and wait until the result is available
asyncResult.await(); asyncResult.await();
assertTrue(asyncResult.isCompleted()); assertTrue(asyncResult.isCompleted());
verifyNoMoreInteractions(task, callback); verifyNoMoreInteractions(task, callback);
// ... and the result should be exactly the same object // ... and the result should be exactly the same object
assertSame(result, asyncResult.getValue()); assertSame(result, asyncResult.getValue());
}); });
} }
/** /**
@@ -209,35 +214,40 @@ class ThreadAsyncExecutorTest {
*/ */
@Test @Test
void testEndProcess() { void testEndProcess() {
assertTimeout(ofMillis(5000), () -> { assertTimeout(
// Instantiate a new executor and start a new 'null' task ... ofMillis(5000),
final var executor = new ThreadAsyncExecutor(); () -> {
// Instantiate a new executor and start a new 'null' task ...
final var executor = new ThreadAsyncExecutor();
final var result = new Object(); final var result = new Object();
when(task.call()).thenAnswer(i -> { when(task.call())
Thread.sleep(1500); .thenAnswer(
return result; i -> {
}); Thread.sleep(1500);
return result;
});
final var asyncResult = executor.startProcess(task); final var asyncResult = executor.startProcess(task);
assertNotNull(asyncResult); assertNotNull(asyncResult);
assertFalse(asyncResult.isCompleted()); assertFalse(asyncResult.isCompleted());
try { try {
asyncResult.getValue(); asyncResult.getValue();
fail("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task"); fail(
} catch (IllegalStateException e) { "Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task");
assertNotNull(e.getMessage()); } catch (IllegalStateException e) {
} assertNotNull(e.getMessage());
}
assertSame(result, executor.endProcess(asyncResult)); assertSame(result, executor.endProcess(asyncResult));
verify(task, times(1)).call(); verify(task, times(1)).call();
assertTrue(asyncResult.isCompleted()); assertTrue(asyncResult.isCompleted());
// Calling end process a second time while already finished should give the same result // Calling end process a second time while already finished should give the same result
assertSame(result, executor.endProcess(asyncResult)); assertSame(result, executor.endProcess(asyncResult));
verifyNoMoreInteractions(task); verifyNoMoreInteractions(task);
}); });
} }
/** /**
@@ -246,25 +256,28 @@ class ThreadAsyncExecutorTest {
*/ */
@Test @Test
void testNullTask() { void testNullTask() {
assertTimeout(ofMillis(3000), () -> { assertTimeout(
// Instantiate a new executor and start a new 'null' task ... ofMillis(3000),
final var executor = new ThreadAsyncExecutor(); () -> {
final var asyncResult = executor.startProcess(null); // 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'."); assertNotNull(
asyncResult.await(); // Prevent timing issues, and wait until the result is available asyncResult,
assertTrue(asyncResult.isCompleted()); "The AsyncResult should not be 'null', even though the task was 'null'.");
asyncResult.await(); // Prevent timing issues, and wait until the result is available
try { assertTrue(asyncResult.isCompleted());
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());
}
});
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());
}
});
} }
/** /**
@@ -273,32 +286,35 @@ class ThreadAsyncExecutorTest {
*/ */
@Test @Test
void testNullTaskWithCallback() { void testNullTaskWithCallback() {
assertTimeout(ofMillis(3000), () -> { assertTimeout(
// Instantiate a new executor and start a new 'null' task ... ofMillis(3000),
final var executor = new ThreadAsyncExecutor(); () -> {
final var asyncResult = executor.startProcess(null, callback); // 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'."); assertNotNull(
asyncResult.await(); // Prevent timing issues, and wait until the result is available asyncResult,
assertTrue(asyncResult.isCompleted()); "The AsyncResult should not be 'null', even though the task was 'null'.");
verify(callback, times(0)).onComplete(any()); asyncResult.await(); // Prevent timing issues, and wait until the result is available
verify(callback, times(1)).onError(exceptionCaptor.capture()); assertTrue(asyncResult.isCompleted());
verify(callback, times(0)).onComplete(any());
verify(callback, times(1)).onError(exceptionCaptor.capture());
final var exception = exceptionCaptor.getValue(); final var exception = exceptionCaptor.getValue();
assertNotNull(exception); assertNotNull(exception);
assertEquals(NullPointerException.class, exception.getClass()); 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());
}
});
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());
}
});
} }
/** /**
@@ -307,28 +323,27 @@ class ThreadAsyncExecutorTest {
*/ */
@Test @Test
void testNullTaskWithNullCallback() { void testNullTaskWithNullCallback() {
assertTimeout(ofMillis(3000), () -> { assertTimeout(
// Instantiate a new executor and start a new 'null' task ... ofMillis(3000),
final var executor = new ThreadAsyncExecutor(); () -> {
final var asyncResult = executor.startProcess(null, null); // Instantiate a new executor and start a new 'null' task ...
final var executor = new ThreadAsyncExecutor();
final var asyncResult = executor.startProcess(null, null);
assertNotNull( assertNotNull(
asyncResult, asyncResult,
"The AsyncResult should not be 'null', even though the task and callback were 'null'." "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
asyncResult.await(); // Prevent timing issues, and wait until the result is available assertTrue(asyncResult.isCompleted());
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());
}
});
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());
}
});
} }
} }
+8
View File
@@ -34,6 +34,14 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>balking</artifactId> <artifactId>balking</artifactId>
<dependencies> <dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
@@ -26,9 +26,7 @@ package com.iluwatar.balking;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /** An interface to simulate delay while executing some work. */
* An interface to simulate delay while executing some work.
*/
public interface DelayProvider { public interface DelayProvider {
void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task); void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task);
} }
@@ -28,30 +28,26 @@ import java.util.concurrent.TimeUnit;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /** Washing machine class. */
* Washing machine class.
*/
@Slf4j @Slf4j
public class WashingMachine { public class WashingMachine {
private final DelayProvider delayProvider; private final DelayProvider delayProvider;
@Getter @Getter private WashingMachineState washingMachineState;
private WashingMachineState washingMachineState;
/** /** Creates a new instance of WashingMachine. */
* Creates a new instance of WashingMachine.
*/
public WashingMachine() { public WashingMachine() {
this((interval, timeUnit, task) -> { this(
try { (interval, timeUnit, task) -> {
Thread.sleep(timeUnit.toMillis(interval)); try {
} catch (InterruptedException ie) { Thread.sleep(timeUnit.toMillis(interval));
LOGGER.error("", ie); } catch (InterruptedException ie) {
Thread.currentThread().interrupt(); LOGGER.error("", ie);
} Thread.currentThread().interrupt();
task.run(); }
}); task.run();
});
} }
/** /**
@@ -63,9 +59,7 @@ public class WashingMachine {
this.washingMachineState = WashingMachineState.ENABLED; this.washingMachineState = WashingMachineState.ENABLED;
} }
/** /** Method responsible for washing if the object is in appropriate state. */
* Method responsible for washing if the object is in appropriate state.
*/
public void wash() { public void wash() {
synchronized (this) { synchronized (this) {
var machineState = getWashingMachineState(); var machineState = getWashingMachineState();
@@ -81,12 +75,9 @@ public class WashingMachine {
this.delayProvider.executeAfterDelay(50, TimeUnit.MILLISECONDS, this::endOfWashing); this.delayProvider.executeAfterDelay(50, TimeUnit.MILLISECONDS, this::endOfWashing);
} }
/** /** Method is responsible for ending the washing by changing machine state. */
* Method is responsible for ending the washing by changing machine state.
*/
public synchronized void endOfWashing() { public synchronized void endOfWashing() {
washingMachineState = WashingMachineState.ENABLED; washingMachineState = WashingMachineState.ENABLED;
LOGGER.info("{}: Washing completed.", Thread.currentThread().getId()); LOGGER.info("{}: Washing completed.", Thread.currentThread().getId());
} }
} }
@@ -24,27 +24,22 @@
*/ */
package com.iluwatar.balking; package com.iluwatar.balking;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; /** Application test */
/**
* Application test
*/
class AppTest { class AppTest {
/** /**
* Issue: Add at least one assertion to this test case. * 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} * <p>Solution: Inserted assertion to check whether the execution of the main method in {@link
* throws an exception. * App} throws an exception.
*/ */
@Test @Test
void shouldExecuteApplicationWithoutException() { void shouldExecuteApplicationWithoutException() {
assertDoesNotThrow((Executable) App::main); assertDoesNotThrow((Executable) App::main);
} }
}
}
@@ -29,9 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
/** /** Tests for {@link WashingMachine} */
* Tests for {@link WashingMachine}
*/
class WashingMachineTest { class WashingMachineTest {
private final FakeDelayProvider fakeDelayProvider = new FakeDelayProvider(); private final FakeDelayProvider fakeDelayProvider = new FakeDelayProvider();
@@ -69,4 +67,4 @@ class WashingMachineTest {
this.task = task; this.task = task;
} }
} }
} }
+1 -6
View File
@@ -38,7 +38,7 @@
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId> <artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -53,11 +53,6 @@
<version>3.27.3</version> <version>3.27.3</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
+5 -11
View File
@@ -30,17 +30,15 @@ import java.util.List;
/** /**
* The Bloc class is responsible for managing the current state and notifying registered listeners * 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 * whenever the state changes. It implements the ListenerManager interface, allowing listeners to be
* to be added, removed, and notified of state changes. * added, removed, and notified of state changes.
*/ */
public class Bloc implements ListenerManager<State> { public class Bloc implements ListenerManager<State> {
private State currentState; private State currentState;
private final List<StateListener<State>> listeners = new ArrayList<>(); private final List<StateListener<State>> listeners = new ArrayList<>();
/** /** Constructs a new Bloc instance with an initial state of value 0. */
* Constructs a new Bloc instance with an initial state of value 0.
*/
public Bloc() { public Bloc() {
this.currentState = new State(0); this.currentState = new State(0);
} }
@@ -88,16 +86,12 @@ public class Bloc implements ListenerManager<State> {
} }
} }
/** /** Increments the current state value by 1 and notifies listeners of the change. */
* Increments the current state value by 1 and notifies listeners of the change.
*/
public void increment() { public void increment() {
emitState(new State(currentState.value() + 1)); emitState(new State(currentState.value() + 1));
} }
/** /** Decrements the current state value by 1 and notifies listeners of the change. */
* Decrements the current state value by 1 and notifies listeners of the change.
*/
public void decrement() { public void decrement() {
emitState(new State(currentState.value() - 1)); emitState(new State(currentState.value() - 1));
} }
Loaded 100 of 1863 files, more files were not shown because too many files have changed in this diff. Show more