diff --git a/.github/badges/branches.svg b/.github/badges/branches.svg
new file mode 100644
index 0000000..af316de
--- /dev/null
+++ b/.github/badges/branches.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg
new file mode 100644
index 0000000..737c552
--- /dev/null
+++ b/.github/badges/jacoco.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/.github/workflows/merged_master.yml b/.github/workflows/merged_master.yml
new file mode 100644
index 0000000..e3faa84
--- /dev/null
+++ b/.github/workflows/merged_master.yml
@@ -0,0 +1,47 @@
+name: maven build
+
+on:
+ pull_request:
+ types: [closed]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up JDK 21
+ uses: actions/setup-java@v4
+ with:
+ java-version: '21'
+ distribution: 'zulu'
+ cache: maven
+ - name: Build with Maven
+ run: mvn -B package --file pom.xml
+
+ - name: Generate JaCoCo Badge
+ uses: cicirello/jacoco-badge-generator@v2
+ with:
+ generate-branches-badge: true
+
+ - name: Log coverage percentage
+ run: |
+ echo "coverage = ${{ steps.jacoco.outputs.coverage }}"
+ echo "branch coverage = ${{ steps.jacoco.outputs.branches }}"
+
+ - name: Commit the badge (if it changed)
+ run: |
+ if [[ `git status --porcelain` ]]; then
+ git config --global user.name 'Luis'
+ git config --global user.email 'lpenap@users.noreply.github.com'
+ git add -A
+ git commit -m "Autogenerated JaCoCo coverage badge"
+ git push
+ fi
+
+ - name: Upload JaCoCo coverage report
+ uses: actions/upload-artifact@v4
+ with:
+ name: jacoco-report
+ path: target/site/jacoco/
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
new file mode 100644
index 0000000..50500a1
--- /dev/null
+++ b/.github/workflows/pull_request.yml
@@ -0,0 +1,18 @@
+name: PR Build
+
+on:
+ pull_request:
+ branches: ['**']
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up JDK 21
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'zulu'
+ java-version: '21'
+ - name: Build and Test
+ run: ./mvnw -B verify
diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java
index 4b3a893..3fcdf84 100644
--- a/.mvn/wrapper/MavenWrapperDownloader.java
+++ b/.mvn/wrapper/MavenWrapperDownloader.java
@@ -33,7 +33,7 @@ public class MavenWrapperDownloader {
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL =
- "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
+ "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index cd0d451..3c11064 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1 +1 @@
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 8837af0..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-dist: bionic
-services:
- - xvfb
-language: java
-jdk:
- - openjdk11
diff --git a/README.md b/README.md
index 1930a36..d046e0c 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,13 @@
-[](https://codebeat.co/projects/github-com-lpenap-java-patterns-and-constructs-master)
-[](https://travis-ci.org/lpenap/java-patterns-and-constructs)
+[](https://github.com/lpenap/java-patterns-and-constructs/actions/workflows/merged_master.yml)
[](//github.com/lpenap/java-patterns-and-constructs/releases/latest)
+[](https://codebeat.co/projects/github-com-lpenap-java-patterns-and-constructs-master)
+
# Java Design Patterns and Constructs
Collection of patterns and other constructs in Java for educational purposes. (See *Contributing* section if you would like to contribute).
## Quickstart
-* Install a Java 11 implementation (like openjdk11)
+* Install a Java 21 implementation (like openjdk21)
* Clone and run the spring-boot maven goal:
```bash
git clone https://github.com/lpenap/java-patterns-and-constructs
@@ -15,9 +16,15 @@ cd java-patterns-and-constructs
```
## Da List
### Patterns
+* [Abstract Factory](src/main/java/com/penapereira/example/constructs/abstractfactory/)
+* [Adapter](src/main/java/com/penapereira/example/constructs/adapter/)
+* [Decorator](src/main/java/com/penapereira/example/constructs/decorator/)
+* [Factory](src/main/java/com/penapereira/example/constructs/factory/)
* [Factory Method](src/main/java/com/penapereira/example/constructs/factorymethod/)
* [Observer](src/main/java/com/penapereira/example/constructs/observer/)
* [Singleton](src/main/java/com/penapereira/example/constructs/singleton/)
+* [Strategy](src/main/java/com/penapereira/example/constructs/strategy/)
+* [Template Method](src/main/java/com/penapereira/example/constructs/templatemethod/)
### Constructs and Problems
* [Producer/Consumer](src/main/java/com/penapereira/example/constructs/producerconsumer/), a multi-process synchronization problem.
diff --git a/pom.xml b/pom.xml
index 36c0194..ae836ae 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,12 +2,12 @@
4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 2.1.7.RELEASE
-
-
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.5
+
+
com.penapereira
java-patterns-and-constructs
0.0.2
@@ -15,8 +15,8 @@
Collection of patterns and other constructs in Java
- 11
-
+ 21
+
@@ -47,13 +47,67 @@
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
-
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ ${java.version}
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.12
+
+
+
+ prepare-agent
+
+
+
+ generate-code-coverage-report
+ test
+
+ report
+ check
+
+
+
+
+ BUNDLE
+
+
+ INSTRUCTION
+ COVEREDRATIO
+ 0.90
+
+
+
+
+
+ **/*ExampleRunner*
+ **/AppCommandLineRunner*
+ **/ExamplesCommandLineRunner*
+ **/ProducerConsumerExampleRunner*
+ **/Consumer*
+ **/JavaPatternsAndConstructsApplication*
+ **/MainWindow*
+ **/Messages*
+ **/ApplicationProperties*
+ **/HyperlinkMouseListener*
+ **/ObservableAbstract*
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
diff --git a/src/main/java/com/penapereira/example/constructs/abstractfactory/AbstractFactory.java b/src/main/java/com/penapereira/example/constructs/abstractfactory/AbstractFactory.java
new file mode 100644
index 0000000..dc3c24c
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/abstractfactory/AbstractFactory.java
@@ -0,0 +1,6 @@
+package com.penapereira.example.constructs.abstractfactory;
+
+public interface AbstractFactory {
+ ProductA createProductA();
+ ProductB createProductB();
+}
diff --git a/src/main/java/com/penapereira/example/constructs/abstractfactory/AbstractFactoryExampleRunner.java b/src/main/java/com/penapereira/example/constructs/abstractfactory/AbstractFactoryExampleRunner.java
new file mode 100644
index 0000000..74c3dfb
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/abstractfactory/AbstractFactoryExampleRunner.java
@@ -0,0 +1,28 @@
+package com.penapereira.example.constructs.abstractfactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import com.penapereira.example.constructs.app.ExampleRunnerInterface;
+
+@Component
+public class AbstractFactoryExampleRunner implements ExampleRunnerInterface {
+
+ private static final Logger log =
+ LoggerFactory.getLogger(AbstractFactoryExampleRunner.class);
+
+ @Override
+ public void runExample() throws Exception {
+ log.trace("Executing Abstract Factory Pattern Implementation:");
+
+ AbstractFactory factory1 = new Factory1();
+ AbstractFactory factory2 = new Factory2();
+
+ log.trace(" " + factory1.createProductA().name());
+ log.trace(" " + factory1.createProductB().name());
+
+ log.trace(" " + factory2.createProductA().name());
+ log.trace(" " + factory2.createProductB().name());
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/abstractfactory/Factory1.java b/src/main/java/com/penapereira/example/constructs/abstractfactory/Factory1.java
new file mode 100644
index 0000000..a6adccf
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/abstractfactory/Factory1.java
@@ -0,0 +1,14 @@
+package com.penapereira.example.constructs.abstractfactory;
+
+public class Factory1 implements AbstractFactory {
+
+ @Override
+ public ProductA createProductA() {
+ return new ProductA1();
+ }
+
+ @Override
+ public ProductB createProductB() {
+ return new ProductB1();
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/abstractfactory/Factory2.java b/src/main/java/com/penapereira/example/constructs/abstractfactory/Factory2.java
new file mode 100644
index 0000000..997ce00
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/abstractfactory/Factory2.java
@@ -0,0 +1,14 @@
+package com.penapereira.example.constructs.abstractfactory;
+
+public class Factory2 implements AbstractFactory {
+
+ @Override
+ public ProductA createProductA() {
+ return new ProductA2();
+ }
+
+ @Override
+ public ProductB createProductB() {
+ return new ProductB2();
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductA.java b/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductA.java
new file mode 100644
index 0000000..6d60d29
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductA.java
@@ -0,0 +1,5 @@
+package com.penapereira.example.constructs.abstractfactory;
+
+public interface ProductA {
+ String name();
+}
diff --git a/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductA1.java b/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductA1.java
new file mode 100644
index 0000000..0f31eec
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductA1.java
@@ -0,0 +1,9 @@
+package com.penapereira.example.constructs.abstractfactory;
+
+public class ProductA1 implements ProductA {
+
+ @Override
+ public String name() {
+ return "ProductA1";
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductA2.java b/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductA2.java
new file mode 100644
index 0000000..7f6e76e
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductA2.java
@@ -0,0 +1,9 @@
+package com.penapereira.example.constructs.abstractfactory;
+
+public class ProductA2 implements ProductA {
+
+ @Override
+ public String name() {
+ return "ProductA2";
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductB.java b/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductB.java
new file mode 100644
index 0000000..90c8f55
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductB.java
@@ -0,0 +1,5 @@
+package com.penapereira.example.constructs.abstractfactory;
+
+public interface ProductB {
+ String name();
+}
diff --git a/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductB1.java b/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductB1.java
new file mode 100644
index 0000000..95afab8
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductB1.java
@@ -0,0 +1,9 @@
+package com.penapereira.example.constructs.abstractfactory;
+
+public class ProductB1 implements ProductB {
+
+ @Override
+ public String name() {
+ return "ProductB1";
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductB2.java b/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductB2.java
new file mode 100644
index 0000000..c804cbc
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/abstractfactory/ProductB2.java
@@ -0,0 +1,9 @@
+package com.penapereira.example.constructs.abstractfactory;
+
+public class ProductB2 implements ProductB {
+
+ @Override
+ public String name() {
+ return "ProductB2";
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/adapter/Adaptee.java b/src/main/java/com/penapereira/example/constructs/adapter/Adaptee.java
new file mode 100644
index 0000000..93a76be
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/adapter/Adaptee.java
@@ -0,0 +1,7 @@
+package com.penapereira.example.constructs.adapter;
+
+public class Adaptee {
+ public String specificRequest() {
+ return "Adaptee";
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/adapter/Adapter.java b/src/main/java/com/penapereira/example/constructs/adapter/Adapter.java
new file mode 100644
index 0000000..fd50fa2
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/adapter/Adapter.java
@@ -0,0 +1,14 @@
+package com.penapereira.example.constructs.adapter;
+
+public class Adapter implements Target {
+ private final Adaptee adaptee;
+
+ public Adapter(Adaptee adaptee) {
+ this.adaptee = adaptee;
+ }
+
+ @Override
+ public String request() {
+ return "Adapter(" + adaptee.specificRequest() + ")";
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/adapter/AdapterExampleRunner.java b/src/main/java/com/penapereira/example/constructs/adapter/AdapterExampleRunner.java
new file mode 100644
index 0000000..5ab0436
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/adapter/AdapterExampleRunner.java
@@ -0,0 +1,23 @@
+package com.penapereira.example.constructs.adapter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import com.penapereira.example.constructs.app.ExampleRunnerInterface;
+
+@Component
+public class AdapterExampleRunner implements ExampleRunnerInterface {
+
+ private static final Logger log = LoggerFactory.getLogger(AdapterExampleRunner.class);
+
+ @Override
+ public void runExample() throws Exception {
+ log.trace("Executing Adapter Pattern Implementation");
+
+ Adaptee adaptee = new Adaptee();
+ Target target = new Adapter(adaptee);
+
+ log.trace(" " + target.request());
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/adapter/Target.java b/src/main/java/com/penapereira/example/constructs/adapter/Target.java
new file mode 100644
index 0000000..8943acd
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/adapter/Target.java
@@ -0,0 +1,5 @@
+package com.penapereira.example.constructs.adapter;
+
+public interface Target {
+ String request();
+}
diff --git a/src/main/java/com/penapereira/example/constructs/app/properties/Messages.java b/src/main/java/com/penapereira/example/constructs/app/properties/Messages.java
index cc94359..3c46b0a 100644
--- a/src/main/java/com/penapereira/example/constructs/app/properties/Messages.java
+++ b/src/main/java/com/penapereira/example/constructs/app/properties/Messages.java
@@ -16,6 +16,7 @@ public class Messages {
protected String info;
protected String examplesFound;
protected String enableTraceToSeeExamplesDetails;
- protected String enableDebugToSeeExamplesList;
- protected String separator;
+ protected String enableDebugToSeeExamplesList;
+ protected String separator;
+ protected String outputTitle;
}
diff --git a/src/main/java/com/penapereira/example/constructs/app/ui/GuiAppender.java b/src/main/java/com/penapereira/example/constructs/app/ui/GuiAppender.java
new file mode 100644
index 0000000..1056fe7
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/app/ui/GuiAppender.java
@@ -0,0 +1,30 @@
+package com.penapereira.example.constructs.app.ui;
+
+import java.awt.EventQueue;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.AppenderBase;
+
+@Component
+public class GuiAppender extends AppenderBase implements InitializingBean {
+
+ @Autowired
+ private MainWindow mainWindow;
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ ((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).addAppender(this);
+ start();
+ }
+
+ @Override
+ protected void append(ILoggingEvent eventObject) {
+ EventQueue.invokeLater(() -> mainWindow.appendOutput(eventObject.getFormattedMessage() + System.lineSeparator()));
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/app/ui/MainWindow.java b/src/main/java/com/penapereira/example/constructs/app/ui/MainWindow.java
index dbbe964..a112fd3 100644
--- a/src/main/java/com/penapereira/example/constructs/app/ui/MainWindow.java
+++ b/src/main/java/com/penapereira/example/constructs/app/ui/MainWindow.java
@@ -3,6 +3,7 @@
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Font;
+import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.io.IOException;
@@ -10,7 +11,9 @@
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
+import javax.swing.JScrollPane;
import javax.swing.JSeparator;
+import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import org.slf4j.Logger;
@@ -26,7 +29,9 @@ public class MainWindow extends JFrame {
private static final Logger log = LoggerFactory.getLogger(MainWindow.class);
- private static final long serialVersionUID = 1L;
+ private static final long serialVersionUID = 1L;
+
+ private JTextArea outputArea;
@Autowired
Messages msg;
@@ -65,13 +70,23 @@ private void loadIcon() {
}
}
- private JPanel getMainComponent() {
- JPanel mainPanel = new JPanel(new GridLayout(4, 1));
- createCenteredTitle(msg.getGreeting(), mainPanel);
- createCenteredLabelOnPanel(msg.getInfo(), mainPanel);
- createCenteredHyperlink(msg.getHomeUrl(), mainPanel);
- return mainPanel;
- }
+ private JPanel getMainComponent() {
+ JPanel mainPanel = new JPanel(new BorderLayout());
+
+ JPanel infoPanel = new JPanel(new GridLayout(4, 1));
+ createCenteredTitle(msg.getGreeting(), infoPanel);
+ createCenteredLabelOnPanel(msg.getInfo(), infoPanel);
+ createCenteredHyperlink(msg.getHomeUrl(), infoPanel);
+ mainPanel.add(infoPanel, BorderLayout.NORTH);
+
+ outputArea = new JTextArea(10, 40);
+ outputArea.setEditable(false);
+ JScrollPane scrollPane = new JScrollPane(outputArea);
+ scrollPane.setBorder(javax.swing.BorderFactory.createTitledBorder(msg.getOutputTitle()));
+ mainPanel.add(scrollPane, BorderLayout.CENTER);
+
+ return mainPanel;
+ }
private void createCenteredTitle(String text, JPanel panel) {
JLabel title = new JLabel(text, JLabel.CENTER);
@@ -89,10 +104,17 @@ private void createCenteredHyperlink(String text, JPanel panel) {
panel.add(hyperlink);
}
- private void createCenteredLabelOnPanel(String text, JPanel panel) {
- JLabel label = new JLabel(text);
- label.setHorizontalAlignment(JLabel.CENTER);
- panel.add(label);
- }
+ private void createCenteredLabelOnPanel(String text, JPanel panel) {
+ JLabel label = new JLabel(text);
+ label.setHorizontalAlignment(JLabel.CENTER);
+ panel.add(label);
+ }
+
+ public void appendOutput(String text) {
+ if (outputArea != null) {
+ outputArea.append(text);
+ outputArea.setCaretPosition(outputArea.getDocument().getLength());
+ }
+ }
}
diff --git a/src/main/java/com/penapereira/example/constructs/decorator/ComponentIF.java b/src/main/java/com/penapereira/example/constructs/decorator/ComponentIF.java
new file mode 100644
index 0000000..c8822bd
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/decorator/ComponentIF.java
@@ -0,0 +1,5 @@
+package com.penapereira.example.constructs.decorator;
+
+public interface ComponentIF {
+ String operation();
+}
diff --git a/src/main/java/com/penapereira/example/constructs/decorator/ConcreteComponent.java b/src/main/java/com/penapereira/example/constructs/decorator/ConcreteComponent.java
new file mode 100644
index 0000000..b2f5988
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/decorator/ConcreteComponent.java
@@ -0,0 +1,8 @@
+package com.penapereira.example.constructs.decorator;
+
+public class ConcreteComponent implements ComponentIF {
+ @Override
+ public String operation() {
+ return "ConcreteComponent";
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/decorator/ConcreteDecoratorA.java b/src/main/java/com/penapereira/example/constructs/decorator/ConcreteDecoratorA.java
new file mode 100644
index 0000000..df27b9c
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/decorator/ConcreteDecoratorA.java
@@ -0,0 +1,13 @@
+package com.penapereira.example.constructs.decorator;
+
+public class ConcreteDecoratorA extends Decorator {
+
+ public ConcreteDecoratorA(ComponentIF component) {
+ super(component);
+ }
+
+ @Override
+ public String operation() {
+ return "ConcreteDecoratorA(" + component.operation() + ")";
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/decorator/Decorator.java b/src/main/java/com/penapereira/example/constructs/decorator/Decorator.java
new file mode 100644
index 0000000..0236227
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/decorator/Decorator.java
@@ -0,0 +1,9 @@
+package com.penapereira.example.constructs.decorator;
+
+public abstract class Decorator implements ComponentIF {
+ protected final ComponentIF component;
+
+ protected Decorator(ComponentIF component) {
+ this.component = component;
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/decorator/DecoratorExampleRunner.java b/src/main/java/com/penapereira/example/constructs/decorator/DecoratorExampleRunner.java
new file mode 100644
index 0000000..9316a9e
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/decorator/DecoratorExampleRunner.java
@@ -0,0 +1,25 @@
+package com.penapereira.example.constructs.decorator;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import com.penapereira.example.constructs.app.ExampleRunnerInterface;
+
+import com.penapereira.example.constructs.decorator.ComponentIF;
+
+@Component
+public class DecoratorExampleRunner implements ExampleRunnerInterface {
+
+ private static final Logger log = LoggerFactory.getLogger(DecoratorExampleRunner.class);
+
+ @Override
+ public void runExample() throws Exception {
+ log.trace("Executing Decorator Pattern Implementation");
+
+ ComponentIF component = new ConcreteComponent();
+ ComponentIF decorated = new ConcreteDecoratorA(component);
+
+ log.trace(" " + decorated.operation());
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/factory/ConcreteProductA.java b/src/main/java/com/penapereira/example/constructs/factory/ConcreteProductA.java
new file mode 100644
index 0000000..c12ba5d
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/factory/ConcreteProductA.java
@@ -0,0 +1,9 @@
+package com.penapereira.example.constructs.factory;
+
+public class ConcreteProductA implements Product {
+
+ @Override
+ public String name() {
+ return "Concrete Product A";
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/factory/ConcreteProductB.java b/src/main/java/com/penapereira/example/constructs/factory/ConcreteProductB.java
new file mode 100644
index 0000000..7648cd6
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/factory/ConcreteProductB.java
@@ -0,0 +1,9 @@
+package com.penapereira.example.constructs.factory;
+
+public class ConcreteProductB implements Product {
+
+ @Override
+ public String name() {
+ return "Concrete Product B";
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/factory/FactoryExampleRunner.java b/src/main/java/com/penapereira/example/constructs/factory/FactoryExampleRunner.java
new file mode 100644
index 0000000..21fec97
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/factory/FactoryExampleRunner.java
@@ -0,0 +1,26 @@
+package com.penapereira.example.constructs.factory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import com.penapereira.example.constructs.app.ExampleRunnerInterface;
+
+@Component
+public class FactoryExampleRunner implements ExampleRunnerInterface {
+
+ private static final Logger log = LoggerFactory.getLogger(FactoryExampleRunner.class);
+
+ @Override
+ public void runExample() throws Exception {
+ log.trace("Executing Factory Pattern Implementation:");
+
+ ProductFactory factory = new ProductFactory();
+
+ Product productA = factory.createProduct("A");
+ Product productB = factory.createProduct("B");
+
+ log.trace(" " + productA.name());
+ log.trace(" " + productB.name());
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/factory/Product.java b/src/main/java/com/penapereira/example/constructs/factory/Product.java
new file mode 100644
index 0000000..9f9ef09
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/factory/Product.java
@@ -0,0 +1,5 @@
+package com.penapereira.example.constructs.factory;
+
+public interface Product {
+ String name();
+}
diff --git a/src/main/java/com/penapereira/example/constructs/factory/ProductFactory.java b/src/main/java/com/penapereira/example/constructs/factory/ProductFactory.java
new file mode 100644
index 0000000..ac4c4be
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/factory/ProductFactory.java
@@ -0,0 +1,12 @@
+package com.penapereira.example.constructs.factory;
+
+public class ProductFactory {
+
+ public Product createProduct(String type) {
+ return switch (type) {
+ case "A" -> new ConcreteProductA();
+ case "B" -> new ConcreteProductB();
+ default -> throw new IllegalArgumentException("Unknown type: " + type);
+ };
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/templatemethod/AbstractClass.java b/src/main/java/com/penapereira/example/constructs/templatemethod/AbstractClass.java
new file mode 100644
index 0000000..89e9712
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/templatemethod/AbstractClass.java
@@ -0,0 +1,12 @@
+package com.penapereira.example.constructs.templatemethod;
+
+public abstract class AbstractClass {
+
+ protected abstract String stepOne();
+
+ protected abstract String stepTwo();
+
+ public final String templateMethod() {
+ return stepOne() + " then " + stepTwo();
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/templatemethod/ConcreteClassA.java b/src/main/java/com/penapereira/example/constructs/templatemethod/ConcreteClassA.java
new file mode 100644
index 0000000..30ed916
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/templatemethod/ConcreteClassA.java
@@ -0,0 +1,14 @@
+package com.penapereira.example.constructs.templatemethod;
+
+public class ConcreteClassA extends AbstractClass {
+
+ @Override
+ protected String stepOne() {
+ return "A step one";
+ }
+
+ @Override
+ protected String stepTwo() {
+ return "A step two";
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/templatemethod/ConcreteClassB.java b/src/main/java/com/penapereira/example/constructs/templatemethod/ConcreteClassB.java
new file mode 100644
index 0000000..3242514
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/templatemethod/ConcreteClassB.java
@@ -0,0 +1,14 @@
+package com.penapereira.example.constructs.templatemethod;
+
+public class ConcreteClassB extends AbstractClass {
+
+ @Override
+ protected String stepOne() {
+ return "B step one";
+ }
+
+ @Override
+ protected String stepTwo() {
+ return "B step two";
+ }
+}
diff --git a/src/main/java/com/penapereira/example/constructs/templatemethod/TemplateMethodExampleRunner.java b/src/main/java/com/penapereira/example/constructs/templatemethod/TemplateMethodExampleRunner.java
new file mode 100644
index 0000000..5bbcaed
--- /dev/null
+++ b/src/main/java/com/penapereira/example/constructs/templatemethod/TemplateMethodExampleRunner.java
@@ -0,0 +1,24 @@
+package com.penapereira.example.constructs.templatemethod;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import com.penapereira.example.constructs.app.ExampleRunnerInterface;
+
+@Component
+public class TemplateMethodExampleRunner implements ExampleRunnerInterface {
+
+ private static final Logger log = LoggerFactory.getLogger(TemplateMethodExampleRunner.class);
+
+ @Override
+ public void runExample() throws Exception {
+ log.trace("Executing Template Method Pattern Implementation");
+
+ AbstractClass a = new ConcreteClassA();
+ log.trace(" " + a.templateMethod());
+
+ AbstractClass b = new ConcreteClassB();
+ log.trace(" " + b.templateMethod());
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index e5a6e1c..44ac060 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -18,5 +18,6 @@ msg.examplesFound=Implemented examples found
msg.enableTraceToSeeExamplesDetails= [*] Please enable TRACE log level if you want to see examples output
msg.enableDebugToSeeExamplesList= [*] Please enable DEBUG log level if you want to see the examples list
+msg.outputTitle=Output
msg.separator=-------------------------------------------------------------
\ No newline at end of file
diff --git a/src/test/java/com/penapereira/example/constructs/CustomSpringBootContextLoader.java b/src/test/java/com/penapereira/example/constructs/CustomSpringBootContextLoader.java
deleted file mode 100644
index f285e6f..0000000
--- a/src/test/java/com/penapereira/example/constructs/CustomSpringBootContextLoader.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.penapereira.example.constructs;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.test.context.SpringBootContextLoader;
-
-public class CustomSpringBootContextLoader extends SpringBootContextLoader {
- @Override
- protected SpringApplication getSpringApplication() {
- SpringApplication application = super.getSpringApplication();
- application.setHeadless(false);
- return application;
- }
-}
\ No newline at end of file
diff --git a/src/test/java/com/penapereira/example/constructs/JavaPatternsAndConstructsApplicationTests.java b/src/test/java/com/penapereira/example/constructs/JavaPatternsAndConstructsApplicationTests.java
deleted file mode 100644
index a7de7a9..0000000
--- a/src/test/java/com/penapereira/example/constructs/JavaPatternsAndConstructsApplicationTests.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.penapereira.example.constructs;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringRunner;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
-@ContextConfiguration(loader = CustomSpringBootContextLoader.class)
-public class JavaPatternsAndConstructsApplicationTests {
-
- @Test
- public void contextLoads() {
- }
-
-}
diff --git a/src/test/java/com/penapereira/example/constructs/abstractfactory/AbstractFactoryExampleRunnerTests.java b/src/test/java/com/penapereira/example/constructs/abstractfactory/AbstractFactoryExampleRunnerTests.java
new file mode 100644
index 0000000..0d91e57
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/abstractfactory/AbstractFactoryExampleRunnerTests.java
@@ -0,0 +1,10 @@
+package com.penapereira.example.constructs.abstractfactory;
+
+import org.junit.jupiter.api.Test;
+
+class AbstractFactoryExampleRunnerTests {
+ @Test
+ void runExampleRuns() throws Exception {
+ new AbstractFactoryExampleRunner().runExample();
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/abstractfactory/AbstractFactoryTests.java b/src/test/java/com/penapereira/example/constructs/abstractfactory/AbstractFactoryTests.java
new file mode 100644
index 0000000..39eac76
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/abstractfactory/AbstractFactoryTests.java
@@ -0,0 +1,16 @@
+package com.penapereira.example.constructs.abstractfactory;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class AbstractFactoryTests {
+ @Test
+ void factoriesProduceCorrectProducts() {
+ AbstractFactory f1 = new Factory1();
+ AbstractFactory f2 = new Factory2();
+ assertEquals("ProductA1", f1.createProductA().name());
+ assertEquals("ProductB1", f1.createProductB().name());
+ assertEquals("ProductA2", f2.createProductA().name());
+ assertEquals("ProductB2", f2.createProductB().name());
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/adapter/AdapterExampleRunnerTests.java b/src/test/java/com/penapereira/example/constructs/adapter/AdapterExampleRunnerTests.java
new file mode 100644
index 0000000..58e73d2
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/adapter/AdapterExampleRunnerTests.java
@@ -0,0 +1,10 @@
+package com.penapereira.example.constructs.adapter;
+
+import org.junit.jupiter.api.Test;
+
+class AdapterExampleRunnerTests {
+ @Test
+ void runExampleRuns() throws Exception {
+ new AdapterExampleRunner().runExample();
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/app/properties/ApplicationPropertiesTests.java b/src/test/java/com/penapereira/example/constructs/app/properties/ApplicationPropertiesTests.java
new file mode 100644
index 0000000..00ba77a
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/app/properties/ApplicationPropertiesTests.java
@@ -0,0 +1,39 @@
+package com.penapereira.example.constructs.app.properties;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.core.io.ClassPathResource;
+
+class ApplicationPropertiesTests {
+ @Test
+ void settersAndGettersWork() {
+ ApplicationProperties props = new ApplicationProperties();
+ props.setWindowMarginX(10);
+ props.setWindowMarginY(20);
+ props.setLinkColor("#123456");
+ props.setLinkColorHover("#abcdef");
+ props.setAppIcon(new ClassPathResource("icon.png"));
+
+ assertEquals(10, props.getWindowMarginX());
+ assertEquals(20, props.getWindowMarginY());
+ assertEquals("#123456", props.getLinkColor());
+ assertEquals("#abcdef", props.getLinkColorHover());
+ assertEquals("icon.png", props.getAppIcon().getFilename());
+
+ String s = props.toString();
+ assertTrue(s.contains("123456"));
+ assertNotEquals(0, props.hashCode());
+ assertEquals(props, props);
+
+ ApplicationProperties other = new ApplicationProperties();
+ other.setWindowMarginX(10);
+ other.setWindowMarginY(20);
+ other.setLinkColor("#123456");
+ other.setLinkColorHover("#abcdef");
+ other.setAppIcon(new ClassPathResource("icon.png"));
+ assertEquals(props, other);
+ assertEquals(props.hashCode(), other.hashCode());
+ assertNotEquals(props, "bar");
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/app/properties/MessagesTests.java b/src/test/java/com/penapereira/example/constructs/app/properties/MessagesTests.java
new file mode 100644
index 0000000..723bbe6
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/app/properties/MessagesTests.java
@@ -0,0 +1,50 @@
+package com.penapereira.example.constructs.app.properties;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+class MessagesTests {
+ @Test
+ void settersAndGettersWork() {
+ Messages m = new Messages();
+ m.setGreeting("G");
+ m.setWindowTitle("T");
+ m.setHomeUrl("U");
+ m.setInfo("I");
+ m.setExamplesFound("E");
+ m.setEnableTraceToSeeExamplesDetails("TD");
+ m.setEnableDebugToSeeExamplesList("DL");
+ m.setSeparator("-");
+ m.setOutputTitle("OT");
+
+ assertEquals("G", m.getGreeting());
+ assertEquals("T", m.getWindowTitle());
+ assertEquals("U", m.getHomeUrl());
+ assertEquals("I", m.getInfo());
+ assertEquals("E", m.getExamplesFound());
+ assertEquals("TD", m.getEnableTraceToSeeExamplesDetails());
+ assertEquals("DL", m.getEnableDebugToSeeExamplesList());
+ assertEquals("-", m.getSeparator());
+ assertEquals("OT", m.getOutputTitle());
+
+ String s = m.toString();
+ assertTrue(s.contains("G"));
+ assertNotEquals(0, m.hashCode());
+ assertEquals(m, m);
+
+ Messages other = new Messages();
+ other.setGreeting("G");
+ other.setWindowTitle("T");
+ other.setHomeUrl("U");
+ other.setInfo("I");
+ other.setExamplesFound("E");
+ other.setEnableTraceToSeeExamplesDetails("TD");
+ other.setEnableDebugToSeeExamplesList("DL");
+ other.setSeparator("-");
+ other.setOutputTitle("OT");
+ assertEquals(m, other);
+ assertEquals(m.hashCode(), other.hashCode());
+ assertNotEquals(m, "foo");
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/app/ui/GuiAppenderTests.java b/src/test/java/com/penapereira/example/constructs/app/ui/GuiAppenderTests.java
new file mode 100644
index 0000000..598d2d9
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/app/ui/GuiAppenderTests.java
@@ -0,0 +1,44 @@
+package com.penapereira.example.constructs.app.ui;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.awt.EventQueue;
+import java.lang.reflect.Field;
+
+import javax.swing.JTextArea;
+
+import org.junit.jupiter.api.Test;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.LoggingEvent;
+
+class GuiAppenderTests {
+ @Test
+ void appendsMessageToTextArea() throws Exception {
+ // Allocate a MainWindow instance without triggering the JFrame constructor
+ Field theUnsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
+ theUnsafeField.setAccessible(true);
+ sun.misc.Unsafe unsafe = (sun.misc.Unsafe) theUnsafeField.get(null);
+
+ MainWindow window = (MainWindow) unsafe.allocateInstance(MainWindow.class);
+ JTextArea area = new JTextArea();
+ Field f = MainWindow.class.getDeclaredField("outputArea");
+ f.setAccessible(true);
+ f.set(window, area);
+
+ GuiAppender appender = new GuiAppender();
+ Field mw = GuiAppender.class.getDeclaredField("mainWindow");
+ mw.setAccessible(true);
+ mw.set(appender, window);
+ appender.start();
+
+ LoggingEvent event = new LoggingEvent();
+ event.setLevel(Level.INFO);
+ event.setMessage("test message");
+
+ appender.doAppend(event);
+ EventQueue.invokeAndWait(() -> {});
+
+ assertEquals("test message" + System.lineSeparator(), area.getText());
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/app/ui/HyperlinkMouseListenerTests.java b/src/test/java/com/penapereira/example/constructs/app/ui/HyperlinkMouseListenerTests.java
new file mode 100644
index 0000000..150228c
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/app/ui/HyperlinkMouseListenerTests.java
@@ -0,0 +1,33 @@
+package com.penapereira.example.constructs.app.ui;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.awt.Color;
+import java.awt.event.MouseEvent;
+
+import javax.swing.JLabel;
+
+import org.junit.jupiter.api.Test;
+
+import com.penapereira.example.constructs.app.properties.ApplicationProperties;
+
+class HyperlinkMouseListenerTests {
+ @Test
+ void hyperlinkChangesColorAndText() {
+ ApplicationProperties props = new ApplicationProperties();
+ props.setLinkColor("#000000");
+ props.setLinkColorHover("#ffffff");
+ HyperlinkMouseListener listener = new HyperlinkMouseListener(props);
+ JLabel label = new JLabel("http://example.com");
+
+ MouseEvent enter = new MouseEvent(label, MouseEvent.MOUSE_ENTERED, 0, 0, 0, 0, 1, false);
+ listener.mouseEntered(enter);
+ assertTrue(label.getText().contains(""));
+ assertEquals(Color.decode(props.getLinkColorHover()), label.getForeground());
+
+ MouseEvent exit = new MouseEvent(label, MouseEvent.MOUSE_EXITED, 0, 0, 0, 0, 1, false);
+ listener.mouseExited(exit);
+ assertEquals("http://example.com", label.getText());
+ assertEquals(Color.decode(props.getLinkColor()), label.getForeground());
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/decorator/DecoratorExampleRunnerTests.java b/src/test/java/com/penapereira/example/constructs/decorator/DecoratorExampleRunnerTests.java
new file mode 100644
index 0000000..675a8f0
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/decorator/DecoratorExampleRunnerTests.java
@@ -0,0 +1,10 @@
+package com.penapereira.example.constructs.decorator;
+
+import org.junit.jupiter.api.Test;
+
+class DecoratorExampleRunnerTests {
+ @Test
+ void runExampleRuns() throws Exception {
+ new DecoratorExampleRunner().runExample();
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/factory/FactoryExampleRunnerTests.java b/src/test/java/com/penapereira/example/constructs/factory/FactoryExampleRunnerTests.java
new file mode 100644
index 0000000..df9940b
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/factory/FactoryExampleRunnerTests.java
@@ -0,0 +1,10 @@
+package com.penapereira.example.constructs.factory;
+
+import org.junit.jupiter.api.Test;
+
+class FactoryExampleRunnerTests {
+ @Test
+ void runExampleRuns() throws Exception {
+ new FactoryExampleRunner().runExample();
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/factory/FactoryTests.java b/src/test/java/com/penapereira/example/constructs/factory/FactoryTests.java
new file mode 100644
index 0000000..e77b299
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/factory/FactoryTests.java
@@ -0,0 +1,15 @@
+package com.penapereira.example.constructs.factory;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class FactoryTests {
+ @Test
+ void factoryCreatesConcreteProducts() {
+ ProductFactory factory = new ProductFactory();
+ Product a = factory.createProduct("A");
+ Product b = factory.createProduct("B");
+ assertEquals("Concrete Product A", a.name());
+ assertEquals("Concrete Product B", b.name());
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/factorymethod/FactoryMethodExampleRunnerTests.java b/src/test/java/com/penapereira/example/constructs/factorymethod/FactoryMethodExampleRunnerTests.java
new file mode 100644
index 0000000..7f4b712
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/factorymethod/FactoryMethodExampleRunnerTests.java
@@ -0,0 +1,10 @@
+package com.penapereira.example.constructs.factorymethod;
+
+import org.junit.jupiter.api.Test;
+
+class FactoryMethodExampleRunnerTests {
+ @Test
+ void runExampleRuns() throws Exception {
+ new FactoryMethodExampleRunner().runExample();
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/factorymethod/FactoryMethodTests.java b/src/test/java/com/penapereira/example/constructs/factorymethod/FactoryMethodTests.java
new file mode 100644
index 0000000..b87ba00
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/factorymethod/FactoryMethodTests.java
@@ -0,0 +1,16 @@
+package com.penapereira.example.constructs.factorymethod;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class FactoryMethodTests {
+ @Test
+ void productsReturnName() {
+ GenericProduct a = new ConcreteProductA();
+ GenericProduct b = new ConcreteProductB();
+ assertEquals("ConcretepProduct A", a.factoryMethod());
+ assertEquals("ConcretepProduct B", b.factoryMethod());
+ a.build();
+ b.build();
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/observer/ObserverExampleRunnerTests.java b/src/test/java/com/penapereira/example/constructs/observer/ObserverExampleRunnerTests.java
new file mode 100644
index 0000000..4f4db53
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/observer/ObserverExampleRunnerTests.java
@@ -0,0 +1,10 @@
+package com.penapereira.example.constructs.observer;
+
+import org.junit.jupiter.api.Test;
+
+class ObserverExampleRunnerTests {
+ @Test
+ void runExampleRuns() {
+ new ObserverExampleRunner().runExample();
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/observer/ObserverTests.java b/src/test/java/com/penapereira/example/constructs/observer/ObserverTests.java
new file mode 100644
index 0000000..b095c25
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/observer/ObserverTests.java
@@ -0,0 +1,34 @@
+package com.penapereira.example.constructs.observer;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.beans.PropertyChangeEvent;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.jupiter.api.Test;
+
+class ObserverTests {
+ @Test
+ void observerReceivesEvent() {
+ Observable subject = new Observable();
+ AtomicReference received = new AtomicReference<>();
+ subject.addPropertyChangeListener(received::set);
+ for (int i = 0; i < 5 && received.get() == null; i++) {
+ subject.doSomethingWith(5);
+ }
+ assertNotNull(received.get());
+ assertEquals("myProperty", received.get().getPropertyName());
+ assertEquals(5, received.get().getOldValue());
+ assertTrue(((int) received.get().getNewValue()) > 5);
+ }
+
+ @Test
+ void listenerCanBeRemoved() {
+ Observable subject = new Observable();
+ Observer obs = new Observer();
+ subject.addPropertyChangeListener(obs);
+ subject.removePropertyChangeListener(obs);
+ subject.doSomethingWith(1); // should not throw
+ assertEquals(0, subject.getSupport().getPropertyChangeListeners().length);
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/producerconsumer/ProducerTests.java b/src/test/java/com/penapereira/example/constructs/producerconsumer/ProducerTests.java
new file mode 100644
index 0000000..f95e11e
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/producerconsumer/ProducerTests.java
@@ -0,0 +1,23 @@
+package com.penapereira.example.constructs.producerconsumer;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingDeque;
+
+import org.junit.jupiter.api.Test;
+
+class ProducerTests {
+ @Test
+ void producerAddsElements() throws InterruptedException {
+ BlockingQueue queue = new LinkedBlockingDeque<>(3);
+ Producer producer = new Producer(queue);
+ Thread t = new Thread(producer);
+ t.start();
+ t.join();
+ assertEquals(3, queue.size());
+ assertEquals(1, queue.take());
+ assertEquals(2, queue.take());
+ assertEquals(3, queue.take());
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/singleton/SingletonExampleRunnerTests.java b/src/test/java/com/penapereira/example/constructs/singleton/SingletonExampleRunnerTests.java
new file mode 100644
index 0000000..4d45834
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/singleton/SingletonExampleRunnerTests.java
@@ -0,0 +1,10 @@
+package com.penapereira.example.constructs.singleton;
+
+import org.junit.jupiter.api.Test;
+
+class SingletonExampleRunnerTests {
+ @Test
+ void runExampleRuns() throws Exception {
+ new SingletonExampleRunner().runExample();
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/singleton/SingletonTests.java b/src/test/java/com/penapereira/example/constructs/singleton/SingletonTests.java
new file mode 100644
index 0000000..b245bdf
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/singleton/SingletonTests.java
@@ -0,0 +1,13 @@
+package com.penapereira.example.constructs.singleton;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class SingletonTests {
+ @Test
+ void sameInstanceReturned() {
+ Singleton first = Singleton.instance();
+ Singleton second = Singleton.instance();
+ assertSame(first, second);
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/strategy/StrategyExampleRunnerTests.java b/src/test/java/com/penapereira/example/constructs/strategy/StrategyExampleRunnerTests.java
new file mode 100644
index 0000000..d305de4
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/strategy/StrategyExampleRunnerTests.java
@@ -0,0 +1,10 @@
+package com.penapereira.example.constructs.strategy;
+
+import org.junit.jupiter.api.Test;
+
+class StrategyExampleRunnerTests {
+ @Test
+ void runExampleRuns() throws Exception {
+ new StrategyExampleRunner().runExample();
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/strategy/StrategyTests.java b/src/test/java/com/penapereira/example/constructs/strategy/StrategyTests.java
new file mode 100644
index 0000000..582a192
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/strategy/StrategyTests.java
@@ -0,0 +1,14 @@
+package com.penapereira.example.constructs.strategy;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class StrategyTests {
+ @Test
+ void contextOperationUsesStrategy() {
+ Context ctx = new Context(new StrategyImpl1());
+ assertEquals("Operation with --> Algorithm from Strategy Implementation 1", ctx.operation());
+ ctx.setStrategy(new StrategyImpl2());
+ assertEquals("Operation with ==> Algorithm from Strategy Implementation 2", ctx.operation());
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/templatemethod/TemplateMethodExampleRunnerTests.java b/src/test/java/com/penapereira/example/constructs/templatemethod/TemplateMethodExampleRunnerTests.java
new file mode 100644
index 0000000..d9159e6
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/templatemethod/TemplateMethodExampleRunnerTests.java
@@ -0,0 +1,10 @@
+package com.penapereira.example.constructs.templatemethod;
+
+import org.junit.jupiter.api.Test;
+
+class TemplateMethodExampleRunnerTests {
+ @Test
+ void runExampleRuns() throws Exception {
+ new TemplateMethodExampleRunner().runExample();
+ }
+}
diff --git a/src/test/java/com/penapereira/example/constructs/templatemethod/TemplateMethodTests.java b/src/test/java/com/penapereira/example/constructs/templatemethod/TemplateMethodTests.java
new file mode 100644
index 0000000..5cebd6b
--- /dev/null
+++ b/src/test/java/com/penapereira/example/constructs/templatemethod/TemplateMethodTests.java
@@ -0,0 +1,14 @@
+package com.penapereira.example.constructs.templatemethod;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class TemplateMethodTests {
+ @Test
+ void templateMethodRunsSteps() {
+ AbstractClass a = new ConcreteClassA();
+ assertEquals("A step one then A step two", a.templateMethod());
+ AbstractClass b = new ConcreteClassB();
+ assertEquals("B step one then B step two", b.templateMethod());
+ }
+}