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

Some files were not shown because too many files have changed in this diff Show More