mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-05-14 06:58:54 +00:00
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:
@@ -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>
|
||||
|
||||
+7
-10
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-5
@@ -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
|
||||
}
|
||||
|
||||
+14
-13
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+3
-5
@@ -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[] {}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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[] {}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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[] {}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+16
-18
@@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+8
-7
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+3
-5
@@ -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> {}
|
||||
|
||||
+1
-3
@@ -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 {
|
||||
|
||||
+1
-5
@@ -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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
+7
-11
@@ -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);
|
||||
}
|
||||
|
||||
+2
-6
@@ -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> {}
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+53
-68
@@ -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());
|
||||
|
||||
@@ -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>
|
||||
|
||||
+12
-15
@@ -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
|
||||
*/
|
||||
|
||||
+3
-5
@@ -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;
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+14
-16
@@ -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();
|
||||
}
|
||||
|
||||
+6
-10
@@ -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[] {}));
|
||||
}
|
||||
}
|
||||
|
||||
+199
-184
@@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user